From 252ae14aef901fd5a85dc1c7f6b408ea69fdeb7d Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Mon, 14 Sep 2026 22:08:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A6=BB=E7=BA=BFLicense=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=88=A0=E9=99=A4=EF=BC=9B=E5=90=8C=E6=AD=A5=E5=BE=AA?= =?UTF-8?q?=E7=8E=AF=E5=8A=A0=E5=9B=BA=EF=BC=88=E5=90=AF=E5=8A=A8=E5=8D=B3?= =?UTF-8?q?=E8=B7=91/pending=E8=87=AA=E5=8A=A8=E6=8F=A1=E6=89=8B/=E9=98=B2?= =?UTF-8?q?=E9=87=8D=E5=85=A5/panic=E6=81=A2=E5=A4=8D=EF=BC=8C=E7=A7=BB?= =?UTF-8?q?=E8=87=B3main=E5=90=AF=E5=8A=A8=EF=BC=89=EF=BC=9B=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=97=B6=E9=97=B4=E6=98=BE=E7=A4=BA=E5=88=B0=E7=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/service/sync/sync_exchange.go | 36 ++++++++++++++++--- backend/main.go | 4 +++ backend/router/router.go | 3 -- frontend/src/views/OfflineLicenses.vue | 34 ++++++++++++++++-- frontend/src/views/Sync.vue | 13 ++++++- 5 files changed, 79 insertions(+), 11 deletions(-) diff --git a/backend/internal/service/sync/sync_exchange.go b/backend/internal/service/sync/sync_exchange.go index 1d015ed..9cfae39 100644 --- a/backend/internal/service/sync/sync_exchange.go +++ b/backend/internal/service/sync/sync_exchange.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "log" + "sync/atomic" "time" "backend/internal/dto" @@ -194,24 +195,49 @@ func formatApplied(m map[string]int) string { // ============ 定时循环 ============ -// StartSyncLoop 启动宽松节奏的定时双向同步(默认 10 分钟一轮,失败顺延下轮重试) // Logan.Li +// StartSyncLoop 启动宽松节奏的定时双向同步(默认 10 分钟一轮)。 +// 行为:启动即先跑一轮;active 节点直接交换;pending 节点先自动握手(两侧 +// 均已配置同钥时自动激活,无需人工点击);上一轮未结束时本轮跳过(防堆积)。 // Logan.Li func (s *Service) StartSyncLoop(intervalMinutes int) { if intervalMinutes <= 0 { intervalMinutes = 10 } go func() { - ticker := time.NewTicker(time.Duration(intervalMinutes) * time.Minute) - defer ticker.Stop() - for range ticker.C { + var running int32 + runOnce := func() { + if !atomic.CompareAndSwapInt32(&running, 0, 1) { + return // 上一轮仍在进行,跳过本轮防堆积 // Logan.Li + } + defer atomic.StoreInt32(&running, 0) + defer func() { + if r := recover(); r != nil { + log.Printf("[同步] 本轮异常恢复: %v", r) + } + }() + var peers []model.PeerNode - s.db.Where("status = ?", model.PeerStatusActive).Find(&peers) + s.db.Where("status IN ?", []string{model.PeerStatusActive, model.PeerStatusPending}).Find(&peers) for _, peer := range peers { p := peer + // pending 先尝试握手(对端未配置/密钥不一致时静默失败,下轮再试) // Logan.Li + if p.Status == model.PeerStatusPending { + if _, err := s.TestHandshake(p.ID); err != nil { + continue + } + s.db.First(&p, p.ID) + } if _, err := s.ExchangeOut(&p); err != nil { log.Printf("[同步] 节点 %s 本轮失败: %v", peer.Name, err) } } } + + runOnce() // 启动即先跑一轮,不等首个间隔 // Logan.Li + ticker := time.NewTicker(time.Duration(intervalMinutes) * time.Minute) + defer ticker.Stop() + for range ticker.C { + runOnce() + } }() log.Printf("[同步] 定时双向同步已启动(每 %d 分钟)", intervalMinutes) } diff --git a/backend/main.go b/backend/main.go index 8d4e059..5c91c2f 100644 --- a/backend/main.go +++ b/backend/main.go @@ -4,6 +4,7 @@ import ( "backend/config" "backend/database" licenseservice "backend/internal/service/license" + syncservice "backend/internal/service/sync" "backend/router" "context" "log" @@ -27,6 +28,9 @@ func main() { // 启动在线状态清扫(超时未心跳的激活记录置为离线) // Logan.Li licenseservice.StartOnlineSweeper(db, cfg.OnlineTTLMinutes) + // 启动节点定时双向同步(宽松节奏,默认 10 分钟) // Logan.Li + syncservice.NewService(db, cfg).StartSyncLoop(cfg.SyncIntervalMinutes) + // 初始化路由 // Logan.Li r := router.SetupRouter(db, cfg) diff --git a/backend/router/router.go b/backend/router/router.go index 397815c..a7a4a0c 100644 --- a/backend/router/router.go +++ b/backend/router/router.go @@ -96,9 +96,6 @@ func SetupRouter(db *gorm.DB, cfg *config.Config) *gin.Engine { settingController := setting.NewSettingController(db) syncController := synccontroller.NewController(db, cfg) - // 启动节点定时双向同步(宽松节奏,默认 10 分钟) // Logan.Li - syncController.Svc().StartSyncLoop(cfg.SyncIntervalMinutes) - api := r.Group("/api") { // ============ 节点互联(对端调用,HMAC 签名认证,非 JWT) ============ diff --git a/frontend/src/views/OfflineLicenses.vue b/frontend/src/views/OfflineLicenses.vue index e560c3c..edaf0fe 100644 --- a/frontend/src/views/OfflineLicenses.vue +++ b/frontend/src/views/OfflineLicenses.vue @@ -9,6 +9,9 @@
+ + 批量删除{{ selectedRows.length ? `(${selectedRows.length})` : '' }} + 签发离线序列号
@@ -21,7 +24,8 @@ style="margin-bottom: 12px" /> - + +