package task import ( "context" "time" "github.com/bytedance/sonic" "github.com/hibiken/asynq" "go.uber.org/zap" operationsreportapp "github.com/break/junhong_cmp_fiber/internal/application/operationsreport" domainreport "github.com/break/junhong_cmp_fiber/internal/domain/operationsreport" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // OperationsReportSnapshotPayload 是每日运营报表快照任务的载荷。 // // 载荷只含单一目标上海自然日。仓库内没有该任务的入队点(不提供补跑接口), // 带载荷的调用只能来自运维侧 asynq 控制台或临时程序,此时目标日由载荷固定。 // 定时调度**不带载荷**(asynq 的 Scheduler 只能注册静态 Task,无法按次生成 payload), // 此时目标日 = 处理时刻所在上海自然日的前一天。 // // 「重试沿用同一目标日期」的依据不是「沿用同一份载荷」(空载荷会在重试时刻重新推导), // 而是「cron 时点 + 有界重试窗口」:默认重试延迟为 n^4+15+rand(0..29)*(n+1) 秒 // (asynq v0.25.1 server.go 的 DefaultRetryDelayFunc,本仓库 pkg/queue/server.go 显式采用), // MaxRetry(3) 的三次重试合计 ≤ 236 秒,叠加 Timeout(30m) 仍远小于一个上海自然日; // 服务端重试路径不经过唯一锁,因此 23 小时去重窗口不会吞掉失败重试。 // 调整 cron 时点、MaxRetry 或 Timeout 必须重新评估该前提。 type OperationsReportSnapshotPayload struct { SnapshotDate string `json:"snapshot_date"` } // OperationsReportSnapshotHandler 每日运营报表快照任务处理器。 // 处理器是薄壳:解析目标日期后调用生成用例,口径与幂等全部在用例内闭合。 type OperationsReportSnapshotHandler struct { generator *operationsreportapp.Generator logger *zap.Logger } // NewOperationsReportSnapshotHandler 创建每日运营报表快照任务处理器。 func NewOperationsReportSnapshotHandler(generator *operationsreportapp.Generator, logger *zap.Logger) *OperationsReportSnapshotHandler { return &OperationsReportSnapshotHandler{generator: generator, logger: logger} } // Handle 生成当日(或载荷指定的)运营报表日报快照。 func (h *OperationsReportSnapshotHandler) Handle(ctx context.Context, task *asynq.Task) error { if h == nil || h.generator == nil { return errors.New(errors.CodeInternalError, "运营报表快照生成用例未配置") } snapshotDate, err := ResolveOperationsReportSnapshotDate(task, time.Now()) if err != nil { return err } if err := h.generator.Generate(ctx, snapshotDate); err != nil { if h.logger != nil { h.logger.Error("运营报表日报快照生成失败", zap.String("snapshot_date", domainreport.FormatSnapshotDay(snapshotDate)), zap.Error(err)) } return err } return nil } // ResolveOperationsReportSnapshotDate 解析本次生成的目标上海自然日。 // // 载荷为空(定时调度)时取 now 所在上海自然日的前一自然日; // 载荷给出目标日期时必须为 yyyy-MM-dd,非法载荷直接失败,不静默退化为「当天」。 func ResolveOperationsReportSnapshotDate(task *asynq.Task, now time.Time) (time.Time, error) { if task != nil { payload := task.Payload() if len(payload) > 0 { var parsed OperationsReportSnapshotPayload if err := sonic.Unmarshal(payload, &parsed); err != nil { return time.Time{}, errors.Wrap(errors.CodeInvalidParam, err, "运营报表快照任务载荷格式不正确") } if parsed.SnapshotDate != "" { day, err := domainreport.ParseSnapshotDay(parsed.SnapshotDate) if err != nil { return time.Time{}, errors.New(errors.CodeInvalidParam, "运营报表快照任务的目标日期必须为 yyyy-MM-dd 格式的上海自然日") } return day, nil } } } return domainreport.PreviousDay(now), nil }