package polling import ( "context" "strconv" "time" "github.com/bytedance/sonic" "github.com/hibiken/asynq" "github.com/redis/go-redis/v9" "go.uber.org/zap" "github.com/break/junhong_cmp_fiber/pkg/constants" ) // RecalculateDuePayload 是重算到期时间异步任务的载荷。 type RecalculateDuePayload struct { TaskID string `json:"task_id"` CarrierID *uint `json:"carrier_id,omitempty"` } // RecalculateTaskHandler 是重算到期时间的 Asynq 任务处理器;进度写入 Redis 供后台查询。 type RecalculateTaskHandler struct { recalculator *DueRecalculator redis *redis.Client logger *zap.Logger } // NewRecalculateTaskHandler 创建重算任务处理器。 func NewRecalculateTaskHandler(recalculator *DueRecalculator, redisClient *redis.Client, logger *zap.Logger) *RecalculateTaskHandler { if logger == nil { logger = zap.NewNop() } return &RecalculateTaskHandler{recalculator: recalculator, redis: redisClient, logger: logger} } // Handle 执行重算;失败写入进度并返回 SkipRetry,避免按指数退避反复全量扫描。 func (h *RecalculateTaskHandler) Handle(ctx context.Context, task *asynq.Task) error { var payload RecalculateDuePayload if err := sonic.Unmarshal(task.Payload(), &payload); err != nil || payload.TaskID == "" { return asynq.SkipRetry } key := constants.RedisPollingRecalcProgressKey(payload.TaskID) h.write(key, map[string]any{"status": constants.PollingRecalcStatusRunning, "started_at": time.Now().Format(time.RFC3339)}) progress, err := h.recalculator.Run(ctx, payload.CarrierID, func(p RecalculateProgress) { h.write(key, map[string]any{"total": p.Total, "processed": p.Processed, "advanced": p.Advanced}) }) fields := map[string]any{"total": progress.Total, "processed": progress.Processed, "advanced": progress.Advanced, "finished_at": time.Now().Format(time.RFC3339)} if err != nil { fields["status"] = constants.PollingRecalcStatusFailed fields["error"] = "重算到期时间失败,请稍后重试" h.write(key, fields) h.logger.Error("重算轮询到期时间失败", zap.String("task_id", payload.TaskID), zap.Error(err)) return asynq.SkipRetry } fields["status"] = constants.PollingRecalcStatusCompleted h.write(key, fields) h.logger.Info("重算轮询到期时间完成", zap.String("task_id", payload.TaskID), zap.Int("processed", progress.Processed), zap.Int("advanced", progress.Advanced)) return nil } func (h *RecalculateTaskHandler) write(key string, fields map[string]any) { if h.redis == nil { return } ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() pipe := h.redis.Pipeline() pipe.HSet(ctx, key, fields) pipe.Expire(ctx, key, constants.PollingRecalcProgressTTLSeconds*time.Second) if _, err := pipe.Exec(ctx); err != nil { h.logger.Warn("写重算到期时间进度失败", zap.String("key", key), zap.Error(err)) } } // carrierText 把可选运营商账户转为进度展示文本。 func carrierText(carrierID *uint) string { if carrierID == nil { return "" } return strconv.FormatUint(uint64(*carrierID), 10) }