---
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 ✅
