--- phase: 01-p0 plan: 02 type: execute wave: 2 depends_on: - 01-01 files_modified: - internal/task/polling_handler.go - pkg/queue/handler.go autonomous: true requirements: - CRITICAL-03 must_haves: truths: - "PollingHandler struct 有 asynqClient *asynq.Client 字段" - "NewPollingHandler 构造函数接受 asynqClient 参数" - "triggerFirstRealnameActivation 使用 asynqClient.EnqueueContext 入队,不再 RPush" - "_ = task 这行被删除" - "registerPollingHandlers 传入 h.asynqClient 给 NewPollingHandler" artifacts: - path: internal/task/polling_handler.go provides: "PollingHandler 注入 asynq.Client,triggerFirstRealnameActivation 正确入队" contains: "asynqClient.EnqueueContext" - path: pkg/queue/handler.go provides: "registerPollingHandlers 传入 asynqClient" contains: "h.asynqClient" key_links: - from: pkg/queue/handler.go to: internal/task/polling_handler.go via: "NewPollingHandler(h.db, h.redis, h.asynqClient, ...)" pattern: "NewPollingHandler" - from: internal/task/polling_handler.go to: asynq.Client via: "h.asynqClient.EnqueueContext(ctx, task)" pattern: "asynqClient\\.EnqueueContext" --- 修复实名激活任务断链(CRITICAL-03):为 PollingHandler 注入 asynq.Client,删除 RPush 降级方案,改用正式 Asynq 入队。 Purpose: 首次实名后自动激活"囤货套餐"的完整链路当前完全断裂——task 被 `_ = task` 丢弃,实际走 Redis RPush 降级,但无消费者消费这个列表。修复后套餐激活任务正常进入 Asynq 队列被消费。 Output: - polling_handler.go: 新增 asynqClient 字段,triggerFirstRealnameActivation 改为 EnqueueContext - pkg/queue/handler.go: registerPollingHandlers 传入 h.asynqClient @$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md @$HOME/.config/opencode/get-shit-done/templates/summary.md @.planning/phases/01-p0/01-01-SUMMARY.md @.planning/phases/01-p0/01-CONTEXT.md # 修复规格书(方案 A-3,第 165-299 行) @.sisyphus/plans/修正业务-完整方案.md ```go // pkg/queue/handler.go Handler struct(已存在) type Handler struct { db *gorm.DB redis *redis.Client asynqClient *asynq.Client // ← 已有此字段,直接传给 NewPollingHandler // ... } // registerPollingHandlers 当前调用(需修改) pollingHandler := task.NewPollingHandler( h.db, h.redis, // h.asynqClient ← 缺少此行 h.gatewayClient, h.workerResult.Services.UsageService, h.logger, ) ``` ```go // 当前降级代码(需删除 RPush 部分,直接用 EnqueueContext) task := asynq.NewTask(constants.TaskTypePackageFirstActivation, payloadBytes, ...) activationKey := constants.RedisPollingManualQueueKey(constants.TaskTypePackageFirstActivation) h.redis.RPush(ctx, activationKey, string(payloadBytes)) // ← 删掉 _ = task // ← 删掉 ``` Task 1: PollingHandler 注入 asynqClient(CRITICAL-03) internal/task/polling_handler.go, pkg/queue/handler.go **步骤 1:修改 `internal/task/polling_handler.go` struct 定义** 在 PollingHandler struct(第 31-42 行附近)新增字段: ```go type PollingHandler struct { db *gorm.DB redis *redis.Client asynqClient *asynq.Client // 新增 gatewayClient *gateway.Client // ... 其余字段不变 } ``` **步骤 2:修改 NewPollingHandler 构造函数签名(第 44 行附近)** 新增 `asynqClient *asynq.Client` 参数(位置:redis 之后,gatewayClient 之前): ```go func NewPollingHandler( db *gorm.DB, redis *redis.Client, asynqClient *asynq.Client, // 新增 gatewayClient *gateway.Client, usageService *packagepkg.UsageService, logger *zap.Logger, ) *PollingHandler { return &PollingHandler{ db: db, redis: redis, asynqClient: asynqClient, // 新增 gatewayClient: gatewayClient, // ... 其余字段不变 } } ``` **步骤 3:修改 triggerFirstRealnameActivation(第 1100 行附近)** 删除 RPush 降级代码,改为 EnqueueContext: ```go // 删除这两行: // activationKey := constants.RedisPollingManualQueueKey(constants.TaskTypePackageFirstActivation) // h.redis.RPush(ctx, activationKey, string(payloadBytes)) // _ = task // 替换为: if _, err := h.asynqClient.EnqueueContext(ctx, task); err != nil { h.logger.Warn("提交首次实名激活任务失败", zap.Uint("package_usage_id", pkg.ID), zap.Error(err)) continue } ``` 注意:`task` 变量已在上方声明,不需要重新创建。 **步骤 4:修改 `pkg/queue/handler.go:registerPollingHandlers`(第 151 行附近)** 在 NewPollingHandler 调用中补充 `h.asynqClient` 参数: ```go pollingHandler := task.NewPollingHandler( h.db, h.redis, h.asynqClient, // 新增,Handler 结构体已有此字段 h.gatewayClient, h.workerResult.Services.UsageService, h.logger, ) ``` 修改完成后执行 `go build ./...`,确认编译通过,提交独立 commit(per D-05)。 go build ./... && rg "_ = task" internal/task/polling_handler.go && echo "FAIL: _ = task 仍存在" || echo "PASS: _ = task 已删除" - PollingHandler struct 含 asynqClient 字段 - NewPollingHandler 接受 asynqClient 参数 - triggerFirstRealnameActivation 使用 asynqClient.EnqueueContext,不再 RPush - _ = task 这行已被删除 - pkg/queue/handler.go 传入 h.asynqClient - go build ./... 编译通过 整体验收: ```bash # 1. 编译验证 go build ./... # 2. 确认降级代码已删除 rg "_ = task" internal/task/polling_handler.go rg "RPush.*TaskTypePackageFirstActivation\|RedisPollingManualQueueKey" internal/task/polling_handler.go # 3. 确认正确入队代码存在 rg "asynqClient.EnqueueContext" internal/task/polling_handler.go # 4. 确认 registerPollingHandlers 传了 asynqClient rg "NewPollingHandler" pkg/queue/handler.go ``` 人工验收(参见修正业务-完整方案.md A-3 验收清单): - Worker 日志:首次实名触发时应出现实名激活任务入队日志 - DBHub:`SELECT id, status, pending_realname_activation, activated_at FROM tb_package_usage WHERE pending_realname_activation=true ORDER BY id DESC LIMIT 20;` 1. `go build ./...` 编译通过 2. PollingHandler 有 asynqClient 字段且被正确初始化 3. triggerFirstRealnameActivation 使用 EnqueueContext,不再 RPush 4. `_ = task` 行不存在 5. 独立 commit 已提交 完成后创建 `.planning/phases/01-p0/01-02-SUMMARY.md`,记录: - 修改文件及改动说明 - 关键代码对比(修改前后) - 编译验证结果 - CRITICAL-03 完成状态