topfans/docs/superpowers/plans/2026-07-21-daily-task-config-driven-impl.md
zerosaturation 89d5d12450 fix(task): backfill id=1/2 trigger_event + cleanup misleading NULL comments
daily_browse_asset (id=2) / daily_login (id=1) 之前永远不触发,根因:
2026_07_21_004 迁移只 backfill 了 id=3/4 的 trigger_event=task_key,
id=1/2 留 NULL。但 ProcessTaskEvent 引擎按 trigger_event = ? 严格匹配
(spec §4 单一隔离单元),NULL 不会命中,前端 reportEvent 后引擎跑空
循环、daily-tasks 任务永远 pending。

修复:
- 新增 2026_07_27_005 迁移:回填 id=1/2 的 trigger_event=task_key(幂等)
- 修订原 004 迁移的错误注释('trigger_event 留 NULL'误导后人)
- model/task_models.go:删除 'NULL 仍走 def.TaskKey 兜底' 不实承诺
- impl plan §37/39:修订错描述('暂不依赖 trigger_event 列')
- docker/init-db.sql:124668/124671:重建 dev DB 时也带 trigger_event
- daily_task_repo_test.go:补 2 个回归测试
  - NullTriggerEvent_NotMatched:trigger_event=NULL 时查不到
  - TriggerEventEqualsTaskKey_Matches:trigger_event=task_key 时能命中

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 16:36:40 +08:00

24 KiB
Raw Blame History

Daily-Task 配置驱动改造 — 实施计划

配套文档docs/superpowers/specs/2026-07-21-daily-task-config-driven-design.md(设计方案,已审过 12 处修复) 状态:待实施(计划已批准) 工作量:约 1 周8 阶段 × 0.5~1.5d

Context

每日任务系统当前完成判定硬编码在前端(Header.vuedaily_loginasset-detail.vuedaily_browse_asset,原 exhibition.vue 错位实现待迁移),后端用 def.TaskKey == eventType 内联匹配并带 TODO 注释,无法表达"次数 / 多事件"语义。daily_minttask_definitions 中 active 但无后端 emitdaily_place_asset 同样 active 但全仓库无对应 emit 点,用户永远无法完成(已确认本地库 user_daily_task_progress 中 0 行)—— 属悬空任务。

目标:把完成判定搬到后端 ProcessTaskEvent 引擎(方案 A单列 trigger_event + target_count),加 MQ task:event 统一事件流,让新增每日任务只需 SQL 配置而不改前端 / 后端代码。

预期结果:

  • 加新每日任务 = SQL 一行(INSERT INTO task_definitions ...+ 后端业务源头 emit 一次;前端零改动
  • 修复 daily_mint / daily_place_asset 悬空
  • 现有 4 个每日任务行为不变(前端保留 Header.vue / asset-detail.vue / myWorks.vuereportEvent 调用;exhibition.vue 错位调用删除)

用户已确认的关键决策

问题 决定
上线策略 全量上线(不加 feature flag / 灰度)
前端 daily_browse_asset 缓存 当前无缓存,依赖后端幂等(status='completed' 后跳过)
assetService MQ 依赖 新增 mq.Init + EnqueueTaskEvent helper(沿用 galleryService 模式)

实施步骤8 阶段,可独立 commit

Phase A — 数据库 schema 变更0.5d

新建 migration backend/migrations/2026_07_21_004_daily_task_trigger.sql

  • ALTER TABLE task_definitions ADD COLUMN trigger_event varchar(64) + ADD COLUMN target_count int NOT NULL DEFAULT 1
  • ALTER TABLE user_daily_task_progress ADD COLUMN progress int NOT NULL DEFAULT 0
  • BackfillUPDATE task_definitions SET trigger_event='daily_mint', target_count=1 WHERE id=3 AND task_type='daily' + 同上 WHERE id=4 AND task_key='daily_place_asset'
  • ★ 修订 2026-07-27原计划只 backfill id=3/4 是错的——daily_login (id=1) / daily_browse_asset (id=2) 虽走前端 reportEvent但 ProcessTaskEvent 引擎按 trigger_event = ? 严格匹配spec §4 单一隔离单元NULL 不会命中daily-tasks 任务永远 pending。补充迁移 backend/migrations/2026_07_27_005_daily_task_browse_login_trigger.sql 回填 id=1/2 的 trigger_event=task_key。onboarding_complete (id=5) 不属于 daily留 NULL。

同步 docker/init-db.sqlline 3564 task_definitions CREATE TABLE、line 3744 user_daily_task_progress CREATE TABLE、line 124667 COPY 数据):加新列;为新 COPY 行加 trigger_event / target_countid=1,2,3,4 填对应 task_key + 1id=5 onboarding 留 NULL

Critical files

  • backend/migrations/2026_07_21_004_daily_task_trigger.sql(新)
  • docker/init-db.sqlline 3564, 3744, 124664 三处同步)

验证psql -f backend/migrations/2026_07_21_004_daily_task_trigger.sql + \d task_definitions \d user_daily_task_progress 确认列存在 + backfill 4 行值正确


Phase B — GORM model + Repository 层0.5d

修改 backend/services/taskService/model/task_models.go

  • TaskDefinitionTriggerEvent string + TargetCount int 字段
  • UserDailyTaskProgressProgress int 字段

修改 backend/services/taskService/repository/daily_task_repo.go

  • DailyTaskRepository interface 新增 IncrementProgress(progress *UserDailyTaskProgress, def *TaskDefinition) error(实现位于 line ~140
    • 事务内 UPDATE user_daily_task_progress SET progress = progress + 1, status=?, completed_at=? WHERE id=? AND status='pending'(避免 Save 全量覆写)
  • ListActiveDailyTaskDefinitions 查询条件追加 trigger_event = ? 参数化line 111 起WHERE 子句改为 Where("trigger_event = ? AND ...", eventType, ...)
  • ResetAllDailyTasksUpdates map 加 "progress": 0line 131
  • 接口签名变更:现有 ListActiveDailyTaskDefinitions(starID) 改为 ListActiveDailyTaskDefinitions(starID, eventType string) —— 单一参数变化全仓调用点同步grep 验证;只有 daily_task_service.go 调用)

Critical files

  • backend/services/taskService/model/task_models.go
  • backend/services/taskService/repository/daily_task_repo.go
  • backend/services/taskService/service/daily_task_service.goListActiveDailyTaskDefinitions 调用点同步)

验证cd backend/services/taskService && go build ./...;手动 INSERT INTO task_definitions (..., trigger_event='test_xxx', ...) + 调 ListActiveDailyTaskDefinitions(1, "test_xxx") 返回结果对得上


Phase C — 事件目录 + MQ 基础设施0.5d

新建 backend/pkg/mq/tasks/task_events.go

package tasks

const (
    EventDailyLogin       = "daily_login"
    EventDailyBrowseAsset = "daily_browse_asset"
    EventDailyMint        = "daily_mint"
    EventDailyPlaceAsset  = "daily_place_asset"
)

type TaskEventPayload struct {
    UserID    int64  `json:"user_id"`
    StarID    int64  `json:"star_id"`
    EventType string `json:"event_type"`  // 取自 EventDailyXxx 常量
}

修改 backend/pkg/mq/tasks/registry.go

  • TypeTaskEvent = "task:event" 常量(与现有 TypeRevenueExhibition 风格一致)

Critical files

  • backend/pkg/mq/tasks/task_events.go(新)
  • backend/pkg/mq/tasks/registry.go(追加 1 行常量)

验证go build ./...grep -rn "EventDailyMint\|TypeTaskEvent" backend/ 确认可被 import


Phase D — 完成引擎 ProcessTaskEvent1.5d

修改 backend/services/taskService/service/daily_task_service.go

  1. 接口 DailyTaskServiceline 20-25新增方法签名

    ProcessTaskEvent(ctx context.Context, userID, starID int64, eventType string) (*TaskEventResult, error)
    
  2. 新增 structline 27 后):

    type TaskEventResult struct {
        CompletedTaskKeys []string
    }
    
  3. 重写 ReportEvent 方法line 99-165删除原内联 def.TaskKey != eventType 循环,整体替换为委托给 ProcessTaskEvent

    func (s *dailyTaskService) ReportEvent(ctx context.Context, userID, starID int64, eventType string) (*pb.ReportEventResponse, error) {
        result, err := s.ProcessTaskEvent(ctx, userID, starID, eventType)
        if err != nil {
            return &pb.ReportEventResponse{Base: &pbCommon.BaseResponse{Code: uint32(codes.Internal)}, Success: false}, err
        }
        taskKey := ""
        if len(result.CompletedTaskKeys) > 0 {
            taskKey = result.CompletedTaskKeys[0]
        }
        return &pb.ReportEventResponse{
            Base:          &pbCommon.BaseResponse{Code: uint32(codes.OK)},
            Success:       true,
            TaskKey:       taskKey,
            TaskCompleted: len(result.CompletedTaskKeys) > 0,
            Message:       "任务完成",
        }, nil
    }
    
  4. 新增 ProcessTaskEvent 方法line ~166 后):

    func (s *dailyTaskService) ProcessTaskEvent(ctx context.Context, userID, starID int64, eventType string) (*TaskEventResult, error) {
        logger.Logger.Info("ProcessTaskEvent", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("event_type", eventType))
        defs, err := s.dailyRepo.ListActiveDailyTaskDefinitions(starID, eventType) // ★ Phase B 改后的签名
        if err != nil {
            return nil, err
        }
        result := &TaskEventResult{}
        for _, def := range defs {
            progress, err := s.dailyRepo.GetOrCreateDailyProgress(userID, starID, def.TaskKey, def)
            if err != nil {
                logger.Logger.Error("ProcessTaskEvent: get/create failed", zap.Error(err), zap.String("task_key", def.TaskKey))
                continue
            }
            if progress.Status == "completed" || progress.Status == "claimed" {
                continue // 当天幂等
            }
            if err := s.dailyRepo.IncrementProgress(progress, def); err != nil {
                logger.Logger.Error("ProcessTaskEvent: increment failed", zap.Error(err))
                continue
            }
            if progress.Progress >= def.TargetCount {
                progress.Status = "completed"
                now := time.Now().UnixMilli()
                progress.CompletedAt = &now
                if err := s.dailyRepo.UpdateDailyProgress(progress); err != nil {
                    logger.Logger.Error("ProcessTaskEvent: status update failed", zap.Error(err))
                    continue
                }
                result.CompletedTaskKeys = append(result.CompletedTaskKeys, def.TaskKey)
            }
        }
        return result, nil
    }
    

修改 backend/services/taskService/provider/task_mobile_provider.go line 62

  • return p.dailySvc.ReportEvent(...) 保持不变provider 仍叫 ReportEvent,内部委托给 ProcessTaskEvent

Critical files

  • backend/services/taskService/service/daily_task_service.go
  • backend/services/taskService/provider/task_mobile_provider.go(不改,确认接口稳定即可)

验证go build ./...单测Phase G覆盖 5 个 case


Phase E — MQ consumer 注册0.5d

修改 backend/services/taskService/mq/consumer.go

func newHandleTaskEvent(dailySvc service.DailyTaskService) adapter.TaskHandler {
    return func(ctx context.Context, t *adapter.Task) error {
        var p tasks.TaskEventPayload
        if err := tasks.UnmarshalPayload(t.Payload, &p); err != nil {
            return fmt.Errorf("unmarshal: %w", err)
        }
        if _, err := dailySvc.ProcessTaskEvent(ctx, p.UserID, p.StarID, p.EventType); err != nil {
            logger.Logger.Error("handle task:event: ProcessTaskEvent failed",
                zap.String("event_type", p.EventType), zap.Error(err))
            return err
        }
        return nil
    }
}

修改 RegisterHandlers 签名line 26增加 dailySvc service.DailyTaskService 参数

func RegisterHandlers(revenueSvc service.RevenueService, dailySvc service.DailyTaskService) error {
    // ... 现有注册保留
    if err := tc.RegisterTask(tasks.TypeTaskEvent, newHandleTaskEvent(dailySvc), adapter.TaskRegisterOptions{
        MaxRetry: 3,
        Queue:    queueconsts.QueueDefault,
    }); err != nil {
        return fmt.Errorf("register task:event: %w", err)
    }
}

修改 backend/services/taskService/main.go line 195

  • taskmq.RegisterHandlers(revenueSvc) 改为 taskmq.RegisterHandlers(revenueSvc, dailySvc)dailySvc 已在 line 161 构造)

Critical files

  • backend/services/taskService/mq/consumer.go
  • backend/services/taskService/main.goline 195

验证go build ./...;本地起 MQ + 手 enqueue task:event 消息,确认 taskService 消费


Phase F — 触发源 emit 接入1d

修订2026-07-27daily_place_asset 改为前端 emitmyWorks.vue placeAssetToGalleryApi 成功后);daily_browse_assetexhibition.vue 迁移到 asset-detail.vuegalleryService 不再需要新增 emit 代码。

F.1 assetService铸造成功 emit daily_mint — 后端)

新建 backend/services/assetService/mq/producer.go

package mq

import (
    "context"
    "github.com/topfans/backend/pkg/logger"
    "github.com/topfans/backend/pkg/mq/adapter"
    "github.com/topfans/backend/pkg/mq/tasks"
    "go.uber.org/zap"
)

// EnqueueTaskEvent 业务事件 emit修复 daily_mint 悬空)
func EnqueueTaskEvent(ctx context.Context, userID, starID int64, eventType string) error {
    payload, err := tasks.MarshalToPayload(tasks.TaskEventPayload{
        UserID: userID, StarID: starID, EventType: eventType,
    })
    if err != nil { return err }
    task := adapter.Task{
        Type:    tasks.TypeTaskEvent,
        Payload: payload,
        MaxRetry: 3,
    }
    if _, err := adapter.Get().TaskProducer().Enqueue(ctx, task); err != nil {
        logger.Logger.Warn("EnqueueTaskEvent failed", zap.Error(err), zap.String("event_type", eventType))
        return err
    }
    return nil
}

修改 backend/services/assetService/main.go

  • 新增 4 个 flag参照 galleryService line 46-49mqRedisAddr / mqRedisDB / mqRedisPassword / mqConcurrency
  • 新增 3 个 importpkg/mq / pkg/mq/asynq / pkg/queue/consts
  • 新增 mq.Init(mq.Config{...}) 块(参照 galleryService line 213-236只 init 不注册 handlerassetService 是 producer-only

修改 backend/services/assetService/service/mint_service.go

  • 新增 import assetmq "github.com/topfans/backend/services/assetService/mq"

  • line 603statistic.Get().TrackEvent(...) 之后,return response, nil 之前)插入:

    // 业务事件:触发 daily_mint 每日任务
    if err := assetmq.EnqueueTaskEvent(context.Background(), userID, starID, "daily_mint"); err != nil {
        logger.Logger.Warn("emit task:event daily_mint failed", zap.Error(err))
    }
    
  • 同样补到 finalizeMintOrderRecoveryline 1324reconcileOneMintOrder 补单成功分支,避免 reconcile 路径漏发

F.2 前端 emit 接入(替换原 galleryService emit 方案)

frontend/pages/asset-detail/asset-detail.vue:在 onLoad 钩子line 851 之后)新增:

// 触发每日首次浏览藏品任务
const starIdBrowse = uni.getStorageSync('star_id') || 1
reportEvent('daily_browse_asset', starIdBrowse).catch(err => {
    console.error('上报浏览事件失败:', err)
})

顶部 import 加 import { reportEvent } from '@/utils/task-api.js'

frontend/pages/profile/myWorks.vue:在 placeAssetToGalleryApi 成功后line 347 后)插入:

// 触发每日首次上架任务
const starIdPlace = uni.getStorageSync('star_id') || 1
reportEvent('daily_place_asset', starIdPlace).catch(err => {
    console.error('上报上架事件失败:', err)
})

顶部 import 加 import { reportEvent } from '@/utils/task-api.js'

frontend/pages/exhibition/exhibition.vue:删除 line 1564-1568 错位的 reportEvent('daily_browse_asset', starId) 调用块(含上方注释 "上报每日首次预览事件"),并删除顶部 import { reportEvent } from '@/utils/task-api.js'(如该文件不再使用 reportEvent

Critical files

  • backend/services/assetService/mq/producer.go(新)
  • backend/services/assetService/main.go
  • backend/services/assetService/service/mint_service.go
  • frontend/pages/asset-detail/asset-detail.vue
  • frontend/pages/profile/myWorks.vue
  • frontend/pages/exhibition/exhibition.vue

验证

  • 本地 dev 环境铸造一个藏品 → 后端日志看到 EnqueueTaskEvent daily_mint → Asynq Web UI 队列收到 task:event → taskService consumer 处理 → user_daily_task_progressdaily_mint 进度 +1
  • 上架一枚藏品 → 前端 myWorks.vuereportEvent('daily_place_asset') → taskService 后端 daily_place_asset 进度 +1
  • 进入任一藏品详情页 → 前端 asset-detail.vuereportEvent('daily_browse_asset')daily_browse_asset 进度 +1

Phase G — 单测覆盖1d

新建 backend/services/taskService/service/daily_task_service_test.go(参照 like_bet_repo_test.go + revenue_service_test.go 的混合模式):

  • TestMain + setupTestDB + cleanupTestRecordssentinel user_id < 0, star_id = -77, task_key 前缀 test_xxx_
  • db.AutoMigrate(&model.TaskDefinition{}, &model.UserDailyTaskProgress{}) 准备 schema
  • 5 个 case覆盖 spec §8 测试清单):
    1. TestProcessTaskEvent_FirstCompletiontarget=1 + emit daily_login → status=completed
    2. TestProcessTaskEvent_Countingtarget=3 + emit 3 次 daily_browse_asset → progress=1,2,3 → 第三次 completed第四次 no-op
    3. TestProcessTaskEvent_AlreadyCompletedemit 时 status='completed' → skip
    4. TestProcessTaskEvent_UnknownEventemit "test_unknown_event" → no-op, no error
    5. TestProcessTaskEvent_GlobalAndStarSpecificstar_id=NULL 定义 + star_id=87 定义并存 → emit 时两者都 +1
  • 每个 case 末尾 defer cleanupTestRecords 清理 sentinel 行

新建 backend/services/taskService/repository/daily_task_repo_test.go

  • TestIncrementProgresspending → progress=1 → completed
  • TestIncrementProgress_AlreadyCompletedstatus='completed' → UPDATE 行 0 影响
  • TestResetAllDailyTaskscompleted/claimed → pending, progress=0
  • TestListActiveDailyTaskDefinitions_WithTriggerEventtrigger_event='daily_login' 只返回对应行

Critical files

  • backend/services/taskService/service/daily_task_service_test.go(新)
  • backend/services/taskService/repository/daily_task_repo_test.go(新)

验证cd backend/services/taskService && go test ./...;要求本地 dev PostgreSQL 可连(localhost:15432,与 .env.dev 一致),否则 t.Skipf


Phase H — 前端清理 + 部署0.5d

修改 frontend/pages/tasks/daily-tasks.vue line 62-63 + 闭合 <text>:删除 task-progress 渲染段(约 3 行proto 字段不存在(已确认)

部署顺序

  1. 数据库 migration 先上线(ALTER TABLE 无破坏,列加 + 默认值)
  2. 后端 3 个服务滚动重启taskService / assetService / galleryService
  3. 前端发版(删 3 行死代码)

回滚预案

  • migration 回滚:ALTER TABLE task_definitions DROP COLUMN trigger_event, DROP COLUMN target_countALTER TABLE user_daily_task_progress DROP COLUMN progress
  • 代码回滚git revert 各 commit
  • 数据无损:所有新列都有默认值 + backfill 已固化

Critical files

  • frontend/pages/tasks/daily-tasks.vue(删除 line 62-63 + 闭合标签)

关键文件清单(汇总)

新增

  • backend/migrations/2026_07_21_004_daily_task_trigger.sql
  • backend/pkg/mq/tasks/task_events.go
  • backend/services/assetService/mq/producer.go
  • backend/services/taskService/service/daily_task_service_test.go
  • backend/services/taskService/repository/daily_task_repo_test.go

修改

  • docker/init-db.sql
  • backend/services/taskService/model/task_models.go
  • backend/services/taskService/repository/daily_task_repo.go
  • backend/services/taskService/service/daily_task_service.go
  • backend/services/taskService/mq/consumer.go
  • backend/services/taskService/main.go
  • backend/services/assetService/main.go
  • backend/services/assetService/service/mint_service.go
  • frontend/pages/asset-detail/asset-detail.vue(新增 reportEvent('daily_browse_asset')
  • frontend/pages/profile/myWorks.vue(新增 reportEvent('daily_place_asset')
  • frontend/pages/exhibition/exhibition.vue删除错位的 reportEvent('daily_browse_asset')
  • frontend/pages/tasks/daily-tasks.vue

不修改(确认无变化)

  • backend/services/taskService/provider/task_mobile_provider.goline 62 保留 ReportEvent 调用,内部委托已在新 daily_task_service.go 完成)
  • backend/services/taskService/worker/daily_reset_worker.go(已通过 repo 层 ResetAllDailyTasksprogress: 0 间接修复)
  • backend/services/galleryService/mq/producer.go不修改daily_place_asset 改为前端 emit不需要新增 EnqueueTaskEvent helper
  • backend/services/galleryService/service/exhibition_service.go不修改
  • backend/services/galleryService/main.go不修改
  • frontend/utils/task-api.jsfrontend/pages/components/Header.vue(保留前端 reportEvent 调用)

验证方法(端到端)

  1. 本地 dev 环境全流程

    # Phase A
    psql -h localhost -p 15432 -U postgres -d top-fans -f backend/migrations/2026_07_21_004_daily_task_trigger.sql
    
    # Phase B-F
    cd backend && go build ./...
    # 重启 taskService / assetService / galleryServicedev 环境)
    
    # Phase G
    cd backend/services/taskService && go test ./...
    
    # Phase H
    # uniapp 编译 + 模拟器测试
    
  2. 手测 daily_mint 端到端

    • App 内铸造一个藏品 → 后端日志查 [asset.mint] emit task:event daily_mint
    • Asynq Web UIhttp://localhost:8081/queues)查 default 队列出现 task:event 任务
    • taskService 日志查 [task:event] ProcessTaskEvent ... task_completed: daily_mint
    • SELECT * FROM user_daily_task_progress WHERE task_key='daily_mint' 应有 status='completed' 行
  3. 手测 daily_place_asset 端到端:上架一个藏品到展位 → 同样流程

  4. 手测 daily_login前端 reportEvent 路径):打开 App 进 home → task-api.js/api/v1/tasks/report-event → 后端 ReportEvent RPC 委托 ProcessTaskEventdaily_login 进度更新

  5. 手测 daily_browse_asset:进入任一藏品详情页 → asset-detail.vuereportEvent('daily_browse_asset') → 后端处理 → 进度更新

  6. 重置验证:手动调 ResetAllDailyTasks 或等 05:00 Asia/Shanghai → progress=0, status='pending'(查询验证)

  7. 回滚演练:本地先演练 migration 回滚 SQL确认无破坏


风险与缓解

风险 概率 缓解
MQ emit 失败导致任务永远完不成 Emit 走 Asynq MaxRetry=3前端可重新触发daily_login / daily_browse_assetEnqueueTaskEvent 失败仅 Warn 不阻塞主路径
Backfill 数据偏差 Migration 用显式 WHERE id IN (3,4) 而非全表 UPDATE手动验证 4 行值
assetService 新增 MQ 依赖增加启动失败面 mq.Init 失败仅 Warn 日志(仿 galleryService不影响 RPC失败时 daily_mint 永远完不成,但任务保留 active 状态待修复后补发
IncrementProgress 事务并发导致进度丢失 GORM 默认事务隔离;如需严格一致后续 Stage 2 加 SELECT FOR UPDATE
Reset 时 progress 字段未加导致 daily 重置不全 高(已规避) Phase B ResetAllDailyTasks 必须同步加 progress: 0
daily_browse_asset 重复触发(同一天进入详情页多次) 后端 completed 后跳过;无前端缓存依赖,行为正确

任务清单commit 粒度建议)

按 8 阶段独立 commit每个 commit 通过 go build + 单测:

  1. feat(task): migration + backfill daily_task trigger_event (Phase A)
  2. feat(task): GORM model + repository schema support (Phase B)
  3. feat(mq): task:event task type + business event constants (Phase C)
  4. feat(task): ProcessTaskEvent engine + ReportEvent delegation (Phase D)
  5. feat(task): register task:event MQ consumer (Phase E)
  6. feat(asset,frontend): emit task:event on mint success + frontend daily_place/browse_asset (Phase F)
  7. test(task): ProcessTaskEvent + IncrementProgress coverage (Phase G)
  8. chore(frontend): remove dead task-progress rendering (Phase H)

每个 commit 完成后跑 go build ./... + go test ./...,全绿后再继续。


关键参考(已读过)

  • 设计文档:docs/superpowers/specs/2026-07-21-daily-task-config-driven-design.md12 处修复已落地)
  • backend/services/taskService/service/daily_task_service.go:99-165 —— 当前 ReportEvent 内联实现(待删除)
  • backend/pkg/mq/tasks/registry.go:30-88 —— 现有 TaskType 常量风格
  • backend/services/galleryService/mq/producer.go:62-99 —— EnqueueExhibitSettled 模式EnqueueTaskEvent 参考)
  • backend/services/userService/mq/consumer.go:36-47 —— RegisterHandlers 模式task:event handler 参考)
  • backend/services/taskService/repository/like_bet_repo_test.go:16-78 —— 测试模式TestMain + setupTestDB + sentinel cleanup
  • docker/init-db.sql:124664-124670 —— task_definitions 现有 5 行 seedid=1,2,3,4 是 daily

不在本次范围(明确延后)

  • proto 加 progress / target_count 字段 + 前端展示进度条spec §9.1
  • 计数型任务 distinct 去重spec §5、§7.1
  • A→B 升级task_triggers 表 + JSON 规则spec §7
  • 用户服务 daily_login 后端化(当前 daily_login 必须前端,因为"每日首次活跃"语义后端观测不到)