docs(01-p0): 创建 Phase 1 P0 紧急修复执行计划(5 个 Plan,涵盖 CRITICAL-01~08)

This commit is contained in:
2026-03-27 22:35:28 +08:00
parent 5862018e7c
commit f48f11beb4
6 changed files with 1169 additions and 2 deletions

View File

@@ -0,0 +1,225 @@
---
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.ClienttriggerFirstRealnameActivation 正确入队"
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"
---
<objective>
修复实名激活任务断链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
</objective>
<execution_context>
@$HOME/.config/opencode/get-shit-done/workflows/execute-plan.md
@$HOME/.config/opencode/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/phases/01-p0/01-01-SUMMARY.md
@.planning/phases/01-p0/01-CONTEXT.md
# 修复规格书(方案 A-3第 165-299 行)
@.sisyphus/plans/修正业务-完整方案.md
<interfaces>
<!-- pkg/queue/handler.go Handler struct 已有 asynqClient 字段 -->
```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,
)
```
<!-- polling_handler.go 当前错误代码triggerFirstRealnameActivation 第 1100 行) -->
```go
// 当前降级代码(需删除 RPush 部分,直接用 EnqueueContext
task := asynq.NewTask(constants.TaskTypePackageFirstActivation, payloadBytes, ...)
activationKey := constants.RedisPollingManualQueueKey(constants.TaskTypePackageFirstActivation)
h.redis.RPush(ctx, activationKey, string(payloadBytes)) // ← 删掉
_ = task // ← 删掉
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: PollingHandler 注入 asynqClientCRITICAL-03</name>
<files>internal/task/polling_handler.go, pkg/queue/handler.go</files>
<action>
**步骤 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 ./...`,确认编译通过,提交独立 commitper D-05
</action>
<verify>
<automated>go build ./... && rg "_ = task" internal/task/polling_handler.go && echo "FAIL: _ = task 仍存在" || echo "PASS: _ = task 已删除"</automated>
</verify>
<done>
- PollingHandler struct 含 asynqClient 字段
- NewPollingHandler 接受 asynqClient 参数
- triggerFirstRealnameActivation 使用 asynqClient.EnqueueContext不再 RPush
- _ = task 这行已被删除
- pkg/queue/handler.go 传入 h.asynqClient
- go build ./... 编译通过
</done>
</task>
</tasks>
<verification>
整体验收:
```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;`
</verification>
<success_criteria>
1. `go build ./...` 编译通过
2. PollingHandler 有 asynqClient 字段且被正确初始化
3. triggerFirstRealnameActivation 使用 EnqueueContext不再 RPush
4. `_ = task` 行不存在
5. 独立 commit 已提交
</success_criteria>
<output>
完成后创建 `.planning/phases/01-p0/01-02-SUMMARY.md`,记录:
- 修改文件及改动说明
- 关键代码对比(修改前后)
- 编译验证结果
- CRITICAL-03 完成状态
</output>