package user import ( "context" "log" "strings" "time" userdomain "hyapp/services/user-service/internal/domain/user" ) const ( defaultRegionRebuildPollInterval = 5 * time.Second defaultRegionRebuildLockTTL = 30 * time.Second defaultRegionRebuildBatchSize = 500 ) // RegionRebuildWorkerOptions 控制 user_region_rebuild_tasks 的后台消费节奏。 type RegionRebuildWorkerOptions struct { // WorkerID 写入 task locked_by,便于排查某个节点是否卡住任务。 WorkerID string // PollInterval 是没有任务或单轮处理后下一次扫描的间隔。 PollInterval time.Duration // LockTTL 是单个 task claim 后允许本节点持有的最长时间,过期可被其他节点接管。 LockTTL time.Duration // BatchSize 是单次最多更新的 users 数量,避免大国家全量更新长事务。 BatchSize int } func normalizeRegionRebuildWorkerOptions(options RegionRebuildWorkerOptions) RegionRebuildWorkerOptions { options.WorkerID = strings.TrimSpace(options.WorkerID) if options.WorkerID == "" { options.WorkerID = "user-region-rebuild" } if options.PollInterval <= 0 { options.PollInterval = defaultRegionRebuildPollInterval } if options.LockTTL <= 0 { options.LockTTL = defaultRegionRebuildLockTTL } if options.BatchSize <= 0 { options.BatchSize = defaultRegionRebuildBatchSize } return options } // RunRegionRebuildWorker 持续消费 user_region_rebuild_tasks,保证管理端映射变更最终刷新历史用户快照。 func (s *Service) RunRegionRebuildWorker(ctx context.Context, options RegionRebuildWorkerOptions) { options = normalizeRegionRebuildWorkerOptions(options) if s.regionRebuildRepository == nil { log.Printf("worker=user_region_rebuild status=stopped reason=%q", "repository is not configured") return } _, _ = s.ProcessNextRegionRebuildTask(ctx, options) ticker := time.NewTicker(options.PollInterval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: _, _ = s.ProcessNextRegionRebuildTask(ctx, options) } } } // ProcessNextRegionRebuildTask 处理一个待重算任务;错误只影响本轮,worker 下一轮继续扫描。 func (s *Service) ProcessNextRegionRebuildTask(ctx context.Context, options RegionRebuildWorkerOptions) (userdomain.RegionRebuildProcessResult, error) { options = normalizeRegionRebuildWorkerOptions(options) if s.regionRebuildRepository == nil { return userdomain.RegionRebuildProcessResult{NoTask: true}, nil } result, err := s.regionRebuildRepository.ProcessNextRegionRebuildTask(ctx, options.WorkerID, s.now().UnixMilli(), options.LockTTL, options.BatchSize) if err != nil { log.Printf("worker=user_region_rebuild worker_id=%s status=failed error=%q", options.WorkerID, err.Error()) return result, err } if result.NoTask { return result, nil } log.Printf("worker=user_region_rebuild worker_id=%s task_id=%d country=%s target_region_id=%d status=%s processed_users=%d cursor_user_id=%d", options.WorkerID, result.TaskID, result.CountryCode, result.TargetRegionID, result.Status, result.ProcessedUsers, result.CursorUserID) return result, nil }