226 lines
7.2 KiB
Markdown
226 lines
7.2 KiB
Markdown
---
|
||
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"
|
||
---
|
||
|
||
<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 注入 asynqClient(CRITICAL-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 ./...`,确认编译通过,提交独立 commit(per 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>
|