--- phase: 01-p0 plan: 02 subsystem: task/polling tags: [bug-fix, asynq, realname-activation, critical] one_liner: "修复 PollingHandler 缺失 asynq.Client 导致实名激活任务进入 RPush 降级而非 Asynq 队列的断链问题" dependency_graph: requires: - 01-01 (CRITICAL-02 实名状态常量统一) provides: - CRITICAL-03: PollingHandler 正确通过 Asynq 入队实名激活任务 affects: - internal/task/polling_handler.go - pkg/queue/handler.go tech_stack: added: [] patterns: - "依赖注入:通过构造函数参数传入 asynq.Client" - "asynq.Client.EnqueueContext 正式入队" key_files: modified: - internal/task/polling_handler.go - pkg/queue/handler.go decisions: - "删除 RPush 降级方案及 _ = task 废弃代码,直接使用 EnqueueContext 入队" - "asynqClient 参数位置:redis 之后、gatewayClient 之前,与其他 Handler 构造函数风格一致" metrics: duration: "3min" completed: "2026-03-27" tasks: 1 files: 2 requirements: - CRITICAL-03 --- # Phase 01 Plan 02: PollingHandler 注入 asynq.Client 修复实名激活断链 Summary ## Objective 修复 CRITICAL-03:`triggerFirstRealnameActivation` 因缺少 `asynq.Client` 注入,降级使用 Redis RPush 将任务写入无消费者的列表,导致首次实名后套餐激活任务永远不被消费。 ## What Was Built ### 修改文件 **`internal/task/polling_handler.go`** 1. `PollingHandler` struct 新增 `asynqClient *asynq.Client` 字段 2. `NewPollingHandler` 构造函数新增 `asynqClient *asynq.Client` 参数(位置:redis 之后,gatewayClient 之前) 3. `triggerFirstRealnameActivation` 改为 `h.asynqClient.EnqueueContext(ctx, task)`,删除 RPush 降级代码和 `_ = task` **`pkg/queue/handler.go`** `registerPollingHandlers` 调用 `task.NewPollingHandler` 时补充 `h.asynqClient` 参数(Handler struct 已有此字段,直接传入) ### 关键代码对比 **修改前(降级代码):** ```go // polling_handler.go type PollingHandler struct { db *gorm.DB redis *redis.Client // asynqClient 缺失 gatewayClient *gateway.Client ... } // triggerFirstRealnameActivation 内 task := asynq.NewTask(...) activationKey := constants.RedisPollingManualQueueKey(constants.TaskTypePackageFirstActivation) h.redis.RPush(ctx, activationKey, string(payloadBytes)) // 写入无消费者的列表 _ = task // task 被丢弃 ``` **修改后(正确入队):** ```go // polling_handler.go type PollingHandler struct { db *gorm.DB redis *redis.Client asynqClient *asynq.Client // 新增,用于提交 Asynq 任务 gatewayClient *gateway.Client ... } // triggerFirstRealnameActivation 内 task := asynq.NewTask(...) if _, err := h.asynqClient.EnqueueContext(ctx, task); err != nil { h.logger.Warn("提交首次实名激活任务失败", ...) continue } ``` ## Verification Results ``` ✅ go build ./... — 编译通过 ✅ rg "_ = task" internal/task/polling_handler.go → PASS: _ = task 已删除 ✅ rg "asynqClient.EnqueueContext" internal/task/polling_handler.go → PASS: EnqueueContext 已存在 ✅ registerPollingHandlers 传入 h.asynqClient 参数 ``` ## Commits | Task | Commit | Files | |------|--------|-------| | Task 1: PollingHandler 注入 asynqClient(CRITICAL-03)| `20b4b6d` | internal/task/polling_handler.go, pkg/queue/handler.go | ## Deviations from Plan None - 计划执行完全按规格书执行,无偏差。 ## CRITICAL-03 完成状态 ✅ **CRITICAL-03 已修复** - PollingHandler struct 含 asynqClient 字段 ✅ - NewPollingHandler 接受 asynqClient 参数 ✅ - triggerFirstRealnameActivation 使用 EnqueueContext,不再 RPush ✅ - `_ = task` 已删除 ✅ - pkg/queue/handler.go 传入 h.asynqClient ✅ - go build ./... 编译通过 ✅ ## Self-Check: PASSED - `internal/task/polling_handler.go` — FOUND ✅ - `pkg/queue/handler.go` — FOUND ✅ - Commit `20b4b6d` — FOUND ✅