Files
junhong_cmp_fiber/.planning/phases/01-p0/01-03-PLAN.md

315 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 01-p0
plan: 03
type: execute
wave: 2
depends_on:
- 01-01
files_modified:
- internal/service/recharge/service.go
- internal/bootstrap/services.go
- pkg/queue/handler.go
- internal/task/auto_purchase.go
autonomous: true
requirements:
- CRITICAL-04
- CRITICAL-05
must_haves:
truths:
- "recharge.Service struct 有 queueClient *queue.Client 字段"
- "HandlePaymentCallback 在充值成功后,若 LinkedPackageIDs 非空,入队 TaskTypeAutoPurchaseAfterRecharge"
- "AutoPurchaseHandler struct 有 asynqClient *asynq.Client 字段"
- "AutoPurchaseHandler.ProcessTask 事务提交成功后入队 TaskTypeCommission"
- "pkg/queue/handler.go 注册了 AutoPurchaseHandlerregisterAutoPurchaseHandler 被调用)"
- "internal/bootstrap/services.go 的 recharge.New() 传入 queueClient 参数"
artifacts:
- path: internal/service/recharge/service.go
provides: "queueClient 注入HandlePaymentCallback 触发自动购包"
contains: "queueClient.EnqueueTask"
- path: pkg/queue/handler.go
provides: "registerAutoPurchaseHandler 方法和注册调用"
contains: "registerAutoPurchaseHandler"
- path: internal/task/auto_purchase.go
provides: "asynqClient 注入ProcessTask 触发佣金"
contains: "TaskTypeCommission"
- path: internal/bootstrap/services.go
provides: "recharge.New() 传入 queueClient"
contains: "queueClient"
key_links:
- from: internal/service/recharge/service.go
to: pkg/queue/handler.go
via: "queueClient.EnqueueTask(TaskTypeAutoPurchaseAfterRecharge)"
pattern: "TaskTypeAutoPurchaseAfterRecharge"
- from: internal/task/auto_purchase.go
to: asynq.Client
via: "h.asynqClient.EnqueueContext(ctx, commissionTask)"
pattern: "TaskTypeCommission"
---
<objective>
修复充值回调后自动购包触发CRITICAL-04和自动购包后佣金链补全CRITICAL-05
Purpose: C 端"强充绑套餐"功能当前完全断裂——充值成功后无任何代码触发自动购包任务;即使购包完成,佣金链也不会触发。本 Plan 打通完整链路:充值回调 → 自动购包任务入队 → 执行购包 → 佣金计算任务入队。
Output:
- recharge/service.go: 注入 queueClientHandlePaymentCallback 触发自动购包入队
- auto_purchase.go: 注入 asynqClientProcessTask 事务成功后触发佣金入队
- pkg/queue/handler.go: 注册 AutoPurchaseHandler
- bootstrap/services.go: recharge.New() 传入 queueClient
</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-4第 301-575 行)
@.sisyphus/plans/修正业务-完整方案.md
<interfaces>
<!-- 关键事实(来自规格书和 CONTEXT.md -->
<!--
- pkg/queue/Client 有 EnqueueTask(ctx, taskType, payload, opts...) 方法 (pkg/queue/client.go:38)
适合 Service 层调用(自动 Marshal
- pkg/queue/handler.go Handler struct 持有 asynqClient *asynq.Client
适合 Worker 任务处理器使用(直接 EnqueueContext
- TaskTypeAutoPurchaseAfterRecharge = "task:auto_purchase_after_recharge" 常量已存在
- AutoPurchaseHandler 已在 internal/task/auto_purchase.go但未在 handler.go 注册
- QueueClient 已在 internal/bootstrap/dependencies.go 定义,已传给 Order/IotCardImport/DeviceImport
- AssetRechargeRecord.LinkedPackageIDs 字段internal/model/asset_wallet.go:91JSONB
-->
<!-- recharge.Service 当前结构(需新增 queueClient -->
```go
// internal/service/recharge/service.go当前无 queueClient
type Service struct {
db *gorm.DB
// ... 其他字段
}
func New(db *gorm.DB, /* ... */) *Service {
return &Service{db: db, /* ... */}
}
```
<!-- auto_purchase.go NewAutoPurchaseHandler 当前签名(需新增 asynqClient -->
<!-- 注意:规格书里的 nil 列表可能与当前签名不完全一致 -->
<!-- 务必先读实际文件确认参数列表,再决定如何传入 asynqClient -->
<!-- 常量(直接使用) -->
```go
const (
TaskTypeAutoPurchaseAfterRecharge = "task:auto_purchase_after_recharge"
TaskTypeCommission = "..." // 已存在,从 pkg/constants/constants.go 确认
QueueDefault = "..."
QueueLow = "..." // 佣金任务用低优先级队列
)
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: recharge.Service 注入 queueClient触发自动购包CRITICAL-04 Part 1</name>
<files>internal/service/recharge/service.go, internal/bootstrap/services.go</files>
<action>
**步骤 1先读 `internal/service/recharge/service.go` 确认当前 struct 和 New() 参数列表。**
在 Service struct 新增字段:
```go
type Service struct {
db *gorm.DB
// ... 其余字段不变 ...
queueClient *queue.Client // 新增
}
```
在 New() 新增参数(位于最后):
```go
func New(
db *gorm.DB,
// ... 其余参数 ...
queueClient *queue.Client, // 新增
) *Service {
return &Service{
// ... 其余字段 ...
queueClient: queueClient,
}
}
```
确认 import 中有 `"pkgpath/pkg/queue"`(按实际包路径补充)。
**步骤 2在 HandlePaymentCallback 充值成功后添加自动购包入队(第 272 行附近)**
在事务成功返回、记录充值成功日志之前,添加:
```go
// 充值成功后,如有关联套餐则触发自动购包(强充绑套餐功能)
linkedIDs := recharge.LinkedPackageIDs
if len(linkedIDs) > 0 && string(linkedIDs) != "[]" && string(linkedIDs) != "null" {
if s.queueClient != nil {
taskPayload := task.AutoPurchasePayload{RechargeRecordID: recharge.ID}
if err := s.queueClient.EnqueueTask(ctx, constants.TaskTypeAutoPurchaseAfterRecharge, taskPayload,
asynq.Queue(constants.QueueDefault),
asynq.MaxRetry(3),
); err != nil {
s.logger.Error("自动购包任务入队失败",
zap.Uint("recharge_id", recharge.ID),
zap.Error(err))
// 不影响充值成功,仅记录日志
}
}
}
```
**步骤 3修改 `internal/bootstrap/services.go`**
找到 `recharge.New(...)` 调用位置(执行 `rg "recharge.New\|recharge\.New" internal/bootstrap/`),在参数列表末尾补充 `deps.QueueClient`
```go
rechargeSvc := recharge.New(
deps.DB,
// ... 其他已有参数 ...
deps.QueueClient, // 新增
)
```
`QueueClient` 已在 `dependencies.go:24` 定义,无需新增字段。
完成后 `go build ./...`,提交 commitper D-05
</action>
<verify>
<automated>go build ./... && rg "queueClient" internal/service/recharge/service.go | grep -c "queueClient" | grep -v "^0$" && echo "PASS: queueClient 已注入" || echo "FAIL"</automated>
</verify>
<done>
- recharge.Service struct 含 queueClient 字段
- HandlePaymentCallback 在充值成功、LinkedPackageIDs 非空时触发入队
- bootstrap/services.go 的 recharge.New() 传入 deps.QueueClient
- go build ./... 编译通过
</done>
</task>
<task type="auto">
<name>Task 2: AutoPurchaseHandler 注入 asynqClient + 注册 + 触发佣金CRITICAL-04 Part 2 + CRITICAL-05</name>
<files>internal/task/auto_purchase.go, pkg/queue/handler.go</files>
<action>
**步骤 1先读 `internal/task/auto_purchase.go` 确认 AutoPurchaseHandler struct 和 NewAutoPurchaseHandler 的真实参数列表。**
在 AutoPurchaseHandler struct 新增字段:
```go
type AutoPurchaseHandler struct {
// ... 已有字段 ...
asynqClient *asynq.Client // 新增,用于事务后触发佣金任务
}
```
在 NewAutoPurchaseHandler 函数签名中新增 `asynqClient *asynq.Client` 参数(位于 logger 参数之前),并在返回时赋值 `asynqClient: asynqClient`
**⚠️ 重要:** 不要盲目照搬规格书的 nil 列表。必须按实际签名决定在哪里插入 `asynqClient`
**步骤 2修改 ProcessTask事务成功后触发佣金CRITICAL-05**
`ProcessTask()` 函数中:
① 在事务前声明 `var createdOrderID uint`
② 在事务内创建 order 后赋值:`createdOrderID = order.ID`
③ 事务提交成功 `return nil` 后,添加佣金任务入队:
```go
// 事务提交成功后,触发佣金计算(不在事务内,防止任务提交后事务回滚的数据一致性问题)
if h.asynqClient != nil && createdOrderID > 0 {
payloadBytes, err := sonic.Marshal(map[string]any{"order_id": createdOrderID})
if err != nil {
h.logger.Warn("佣金任务载荷序列化失败",
zap.Uint("order_id", createdOrderID),
zap.Error(err))
} else {
commissionTask := asynq.NewTask(constants.TaskTypeCommission, payloadBytes,
asynq.Queue(constants.QueueLow),
asynq.MaxRetry(3),
)
if _, err := h.asynqClient.EnqueueContext(ctx, commissionTask); err != nil {
h.logger.Warn("自动购包后提交佣金任务失败",
zap.Uint("order_id", createdOrderID),
zap.Error(err))
}
}
}
```
注意:使用 `sonic.Marshal`(项目规范,不用 `encoding/json`)。
**步骤 3修改 `pkg/queue/handler.go` 注册 AutoPurchaseHandler**
新增方法 `registerAutoPurchaseHandler()`,并在 `RegisterHandlers()` 中调用:
```go
func (h *Handler) registerAutoPurchaseHandler() {
autoPurchaseHandler := task.NewAutoPurchaseHandler(
// 按 auto_purchase.go 实际签名传参
// 在合适位置传 h.asynqClient
// 其他 store 参数若已有传参规律则沿用;若为 nil 则查看其他 handler 的注册方式
)
h.mux.HandleFunc(constants.TaskTypeAutoPurchaseAfterRecharge, autoPurchaseHandler.ProcessTask)
h.logger.Info("注册自动购包任务处理器", zap.String("task_type", constants.TaskTypeAutoPurchaseAfterRecharge))
}
```
`RegisterHandlers()` 中调用:`h.registerAutoPurchaseHandler()`
**参考现有 handler 注册**:先读 `pkg/queue/handler.go` 中其他 register 方法(如 registerCommissionHandler确认 store 参数传递规律。
完成后 `go build ./...`,提交 commitper D-05
</action>
<verify>
<automated>go build ./... && rg "TaskTypeAutoPurchaseAfterRecharge" pkg/queue/handler.go | grep "HandleFunc" && echo "PASS: AutoPurchaseHandler 已注册" || echo "FAIL: 未注册"</automated>
</verify>
<done>
- AutoPurchaseHandler struct 含 asynqClient 字段
- ProcessTask 事务成功后入队 TaskTypeCommission不在事务内
- pkg/queue/handler.go 注册了 AutoPurchaseHandler
- RegisterHandlers() 调用了 registerAutoPurchaseHandler()
- go build ./... 编译通过
</done>
</task>
</tasks>
<verification>
整体验收:
```bash
# 1. 编译验证
go build ./...
# 2. 确认入队代码存在
rg "TaskTypeAutoPurchaseAfterRecharge" internal/service/recharge/service.go
rg "TaskTypeCommission" internal/task/auto_purchase.go
rg "registerAutoPurchaseHandler\|TaskTypeAutoPurchaseAfterRecharge" pkg/queue/handler.go
# 3. 确认 bootstrap 传入了 queueClient
rg "recharge.New\|recharge\.New" internal/bootstrap/services.go
```
人工验收(参见修正业务-完整方案.md A-4 验收清单):
- DBHub 验证顺序tb_asset_recharge_record(auto_purchase_status=success) → tb_order(自动购包订单) → tb_package_usage(套餐激活) → tb_commission_record(佣金记录)
</verification>
<success_criteria>
1. `go build ./...` 编译通过,无错误
2. recharge.Service.HandlePaymentCallback 在 LinkedPackageIDs 非空时触发自动购包入队
3. AutoPurchaseHandler.ProcessTask 事务成功后触发佣金入队(事务外)
4. AutoPurchaseHandler 在 RegisterHandlers 中注册
5. bootstrap/services.go recharge.New() 传入 queueClient
6. 两个独立 commit 已提交CRITICAL-04 一个CRITICAL-05 一个)
</success_criteria>
<output>
完成后创建 `.planning/phases/01-p0/01-03-SUMMARY.md`,记录:
- 修改文件及改动说明
- 关键代码路径(充值回调 → 入队 → 消费 → 佣金)
- 编译验证结果
- CRITICAL-04 和 CRITICAL-05 完成状态
</output>