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 @@