Files
junhong_cmp_fiber/openspec/changes/implement-order-expiration/tasks.md
huang 5bb0ff0ddf
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m54s
fix: 修复代理钱包订单创建逻辑,拆分后台/H5端下单方法并归档变更
- 拆分订单创建为 CreateAdminOrder(后台一步支付)和 CreateH5Order(H5 两步支付)
- 新增 CreateAdminOrderRequest DTO,后台仅允许 wallet/offline 支付方式
- 同步 delta specs 到主规格(order-payment 更新 + admin-order-creation 新增)
- 归档 fix-agent-wallet-order-creation 变更
- 新增 implement-order-expiration 变更提案
2026-02-28 16:31:31 +08:00

185 lines
13 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.
## 1. 数据库迁移
- [ ] 1.1 创建迁移文件 `migrations/000xxx_add_order_expiration.up.sql`:添加 `expires_at` 字段和复合索引 `idx_order_expires(expires_at, payment_status)`
- [ ] 1.2 创建回滚文件 `migrations/000xxx_add_order_expiration.down.sql`:删除索引和字段
- [ ] 1.3 执行迁移验证:运行 `migrate up` 并检查表结构,确认字段和索引创建成功
- [ ] 1.4 测试回滚:运行 `migrate down` 并验证字段和索引删除成功,然后重新 `migrate up`
## 2. 常量定义
- [ ] 2.1 在 `pkg/constants/constants.go` 中添加订单超时时间常量 `OrderExpireTimeout = 30 * time.Minute`
- [ ] 2.2 在 `pkg/constants/constants.go` 中添加任务类型常量 `TaskTypeOrderExpire = "order:expire"`
- [ ] 2.3 在 `pkg/constants/constants.go` 中添加批量处理数量常量 `OrderExpireBatchSize = 100`
- [ ] 2.4 验证编译:运行 `go build ./...` 确认无编译错误
## 3. Model 层修改
- [ ] 3.1 在 `internal/model/order.go` 中的 `Order` 结构体添加 `ExpiresAt *time.Time` 字段(指针类型,支持 NULL
- [ ] 3.2 在 `internal/model/dto/order_dto.go` 中的 `OrderResponse` 添加 `ExpiresAt *time.Time``IsExpired bool` 字段
- [ ] 3.3 验证编译:运行 `go build ./internal/model/...` 确认无编译错误
## 4. Store 层新增方法
- [ ] 4.1 在 `internal/store/postgres/order_store.go` 添加 `FindExpiredOrders(ctx, limit int) ([]*model.Order, error)` 方法:查询 `expires_at <= NOW() AND payment_status = 1` 的订单
- [ ] 4.2 在 `internal/store/postgres/order_store.go``UpdatePaymentStatus()` 方法中添加 `expiresAt *time.Time` 参数,支持更新过期时间
- [ ] 4.3 验证编译:运行 `go build ./internal/store/...` 确认无编译错误
- [ ] 4.4 使用 PostgreSQL MCP 工具验证查询:执行 `FindExpiredOrders` 的 SQL确认索引使用正确且查询耗时 < 50ms
## 5. Service 层修改 - 订单创建
- [ ] 5.1 修改 `internal/service/order/service.go``Create()` 方法:待支付订单设置 `expires_at = now + 30min`
- [ ] 5.2 修改 `Create()` 方法:后台钱包一步支付订单和线下支付订单 `expires_at = nil`
- [ ] 5.3 验证编译:运行 `go build ./internal/service/order/...` 确认无编译错误
## 6. Service 层修改 - 订单取消和钱包解冻
- [ ] 6.1 修改 `internal/service/order/service.go``Cancel()` 方法:添加钱包解冻逻辑(判断支付方式,计算解冻金额)
- [ ] 6.2 在 `Cancel()` 方法中添加事务处理:订单状态更新(`payment_status = 3`, `expires_at = nil`)和钱包解冻在同一事务
- [ ] 6.3 在 `Cancel()` 方法中添加解冻规则判断逻辑钱包支付H5、混合支付需解冻纯在线支付不解冻
- [ ] 6.4 验证编译:运行 `go build ./internal/service/order/...` 确认无编译错误
## 7. Service 层新增方法 - 批量取消超时订单
- [ ] 7.1 在 `internal/service/order/service.go` 添加 `CancelExpiredOrders(ctx context.Context) (int, error)` 方法
- [ ] 7.2 实现 `CancelExpiredOrders()` 逻辑:调用 `FindExpiredOrders()` 查询超时订单(最多 100 条)
- [ ] 7.3 实现批量取消逻辑:遍历订单,调用 `Cancel()` 方法(复用钱包解冻逻辑)
- [ ] 7.4 添加日志记录:处理订单数量、解冻钱包次数、执行耗时
- [ ] 7.5 验证编译:运行 `go build ./internal/service/order/...` 确认无编译错误
## 8. Service 层修改 - 支付成功清除过期时间
- [ ] 8.1 修改 `internal/service/order/service.go``WalletPay()` 方法:调用 `UpdatePaymentStatus()` 时传入 `expiresAt = nil`
- [ ] 8.2 修改 `HandlePaymentCallback()` 方法:调用 `UpdatePaymentStatus()` 时传入 `expiresAt = nil`
- [ ] 8.3 验证编译:运行 `go build ./internal/service/order/...` 确认无编译错误
## 9. Task 层新增定时任务
- [ ] 9.1 创建 `internal/task/order_expire.go` 文件,定义 `OrderExpireHandler` 结构体
- [ ] 9.2 实现 `NewOrderExpireHandler()` 构造函数,依赖注入 `db`, `orderService`, `logger`
- [ ] 9.3 实现 `HandleOrderExpire(ctx context.Context, task *asynq.Task) error` 方法,调用 `orderService.CancelExpiredOrders()`
- [ ] 9.4 添加错误处理和重试逻辑:可重试错误返回 `err`,不可重试错误返回 `asynq.SkipRetry`
- [ ] 9.5 添加日志记录:任务开始、成功处理订单数、失败错误
- [ ] 9.6 验证编译:运行 `go build ./internal/task/...` 确认无编译错误
## 10. Worker 注册定时任务 Handler
- [ ] 10.1 在 `pkg/queue/handler.go``RegisterHandlers()` 方法中调用 `registerOrderExpireHandler()`
- [ ] 10.2 实现 `registerOrderExpireHandler()` 方法:创建 `OrderExpireHandler` 并注册到 `mux.HandleFunc(constants.TaskTypeOrderExpire, ...)`
- [ ] 10.3 验证编译:运行 `go build ./pkg/queue/...` 确认无编译错误
## 11. Worker 创建和启动 Asynq Scheduler
- [ ] 11.1 在 `cmd/worker/main.go` 中创建 Asynq Scheduler 实例:`asynq.NewScheduler(redisOpt, &asynq.SchedulerOpts{Location: time.Local})`
- [ ] 11.2 注册订单超时周期任务:`scheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeOrderExpire, nil), asynq.Queue(constants.QueueDefault))`
- [ ] 11.3 启动 Scheduler`scheduler.Start()`,并在 defer 中调用 `scheduler.Shutdown()`
- [ ] 11.4 验证编译:运行 `go build ./cmd/worker/...` 确认无编译错误
## 12. Handler 层修改 - DTO 响应
- [ ] 12.1 修改 `internal/handler/admin/order.go``internal/handler/h5/order.go` 的订单响应构建逻辑:添加 `ExpiresAt` 字段
- [ ] 12.2 实现 `IsExpired` 动态计算逻辑:`if expiresAt != nil && paymentStatus == 1 { isExpired = now.After(expiresAt) }`
- [ ] 12.3 验证编译:运行 `go build ./internal/handler/...` 确认无编译错误
## 13. Handler 层修改 - 查询过期状态
- [ ] 13.1 修改 `internal/model/dto/order_dto.go``ListOrderRequest` 添加 `IsExpired *bool` 查询参数(可选)
- [ ] 13.2 修改 `internal/store/postgres/order_store.go``List()` 方法:添加过期状态筛选条件(`is_expired = true` 映射为 `expires_at <= NOW() AND payment_status = 1`
- [ ] 12.3 验证编译:运行 `go build ./...` 确认无编译错误
## 14. 功能验证 - 订单创建
- [ ] 14.1 启动 API 服务,使用 Postman/curl 创建待支付订单H5 端,支付方式 wechat验证 `expires_at` 字段设置正确(约 `now + 30min`
- [ ] 14.2 使用 PostgreSQL MCP 工具查询订单:`SELECT id, expires_at, payment_status FROM tb_order WHERE id = ?`,确认 `expires_at` 不为 NULL
- [ ] 14.3 创建后台钱包支付订单,验证 `expires_at` 为 NULL订单立即支付成功
## 15. 功能验证 - 订单取消和钱包解冻
- [ ] 15.1 创建混合支付待支付订单(钱包预扣 2000 分),使用 PostgreSQL MCP 查询钱包冻结余额
- [ ] 15.2 调用取消订单 API验证订单状态变更为已取消`payment_status = 3``expires_at` 变更为 NULL
- [ ] 15.3 使用 PostgreSQL MCP 查询钱包:确认冻结余额减少 2000 分
- [ ] 15.4 创建纯在线支付订单wechat取消订单确认不执行钱包解冻操作
## 16. 功能验证 - 支付成功清除过期时间
- [ ] 16.1 创建待支付订单wechat确认 `expires_at` 不为 NULL
- [ ] 16.2 模拟第三方支付回调成功,验证订单状态变更为已支付(`payment_status = 2``expires_at` 变更为 NULL
- [ ] 16.3 使用 PostgreSQL MCP 查询订单:`SELECT id, expires_at, payment_status FROM tb_order WHERE id = ?`,确认 `expires_at` 为 NULL
## 17. 功能验证 - 定时任务自动取消
- [ ] 17.1 使用 PostgreSQL MCP 手动修改订单的 `expires_at` 为过去时间:`UPDATE tb_order SET expires_at = NOW() - INTERVAL '1 minute' WHERE id = ?`
- [ ] 17.2 启动 Worker 服务,等待 1 分钟后检查日志,确认定时任务执行成功
- [ ] 17.3 使用 PostgreSQL MCP 查询订单:确认订单状态变更为已取消,`expires_at` 变更为 NULL
- [ ] 17.4 如果是混合支付订单,使用 PostgreSQL MCP 查询钱包:确认冻结余额解冻
## 18. 功能验证 - 查询过期状态
- [ ] 18.1 使用 Postman/curl 调用订单列表 API筛选 `is_expired = true`,验证返回已过期的待支付订单
- [ ] 18.2 调用订单列表 API筛选 `is_expired = false`,验证返回未过期的待支付订单
- [ ] 18.3 调用订单详情 API验证响应包含 `is_expired` 字段且计算正确
## 19. 性能验证
- [ ] 19.1 使用 PostgreSQL MCP 的 `explain_query` 工具分析 `FindExpiredOrders` 查询:确认使用 `idx_order_expires` 索引
- [ ] 19.2 验证查询耗时:在订单数量 > 10000 的情况下,查询耗时 < 50ms
- [ ] 19.3 验证定时任务处理耗时:单批次处理 100 条订单,总耗时 < 5s
- [ ] 19.4 使用 PostgreSQL MCP 检查数据库连接池状态:确认无连接池阻塞
## 20. 错误处理验证
- [ ] 20.1 模拟数据库连接失败场景确认定时任务返回可重试错误Asynq 自动重试
- [ ] 20.2 模拟钱包不存在场景:确认订单取消失败,事务回滚,订单状态不变
- [ ] 20.3 模拟冻结余额不足场景:确认订单取消失败,事务回滚,记录错误日志
- [ ] 20.4 检查日志:确认所有错误场景都记录了详细日志(包含订单 ID、错误原因
## 21. 代码质量检查
- [ ] 21.1 运行 `gofmt -s -w .` 格式化代码
- [ ] 21.2 运行 `go vet ./...` 检查代码问题
- [ ] 21.3 运行 `go build ./...` 确认全部编译通过
- [ ] 21.4 检查所有新增代码的中文注释:确认符合注释规范(导出符号有文档注释,复杂逻辑有实现注释)
## 22. 文档更新
- [ ] 22.1 创建功能总结文档 `docs/order-expiration/功能总结.md`:说明超时机制、钱包解冻、查询过期状态
- [ ] 22.2 更新 `README.md`:在"已实现功能"部分添加"订单超时自动失效"
- [ ] 22.3 更新 `openspec/specs/iot-order/spec.md`:同步 delta spec 到主规格文档(归档后)
- [ ] 22.4 更新 `openspec/specs/order-payment/spec.md`:同步 delta spec 到主规格文档(归档后)
## 23. 最终验证
- [ ] 23.1 在开发环境完整测试一次完整流程:创建订单 → 超时自动取消 → 钱包解冻
- [ ] 23.2 检查所有日志输出确认日志级别正确Info/Error日志内容完整
- [ ] 23.3 检查数据库:确认无脏数据(如订单已取消但钱包未解冻)
- [ ] 23.4 使用 Postman 导出 API 测试用例集(包含订单创建、取消、查询过期状态)
## 24. 重构现有定时任务为 Asynq Scheduler
- [ ] 24.1 在 `pkg/constants/constants.go` 中添加告警检查任务类型常量 `TaskTypeAlertCheck = "alert:check"`
- [ ] 24.2 在 `pkg/constants/constants.go` 中添加数据清理任务类型常量 `TaskTypeDataCleanup = "data:cleanup"`
- [ ] 24.3 创建 `internal/task/alert_check.go` 文件,定义 `AlertCheckHandler` 结构体
- [ ] 24.4 实现 `NewAlertCheckHandler()` 构造函数,依赖注入 `alertService`, `logger`
- [ ] 24.5 实现 `HandleAlertCheck(ctx context.Context, task *asynq.Task) error` 方法,调用 `alertService.CheckAlerts()`
- [ ] 24.6 创建 `internal/task/data_cleanup.go` 文件,定义 `DataCleanupHandler` 结构体
- [ ] 24.7 实现 `NewDataCleanupHandler()` 构造函数,依赖注入 `cleanupService`, `logger`
- [ ] 24.8 实现 `HandleDataCleanup(ctx context.Context, task *asynq.Task) error` 方法,调用 `cleanupService.RunScheduledCleanup()`
- [ ] 24.9 在 `pkg/queue/handler.go``RegisterHandlers()` 方法中调用 `registerAlertCheckHandler()`
- [ ] 24.10 实现 `registerAlertCheckHandler()` 方法:创建 `AlertCheckHandler` 并注册到 `mux.HandleFunc(constants.TaskTypeAlertCheck, ...)`
- [ ] 24.11 在 `pkg/queue/handler.go``RegisterHandlers()` 方法中调用 `registerDataCleanupHandler()`
- [ ] 24.12 实现 `registerDataCleanupHandler()` 方法:创建 `DataCleanupHandler` 并注册到 `mux.HandleFunc(constants.TaskTypeDataCleanup, ...)`
- [ ] 24.13 在 `cmd/worker/main.go` 的 Asynq Scheduler 中注册告警检查周期任务:`scheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeAlertCheck, nil))`
- [ ] 24.14 在 `cmd/worker/main.go` 的 Asynq Scheduler 中注册数据清理周期任务:`scheduler.Register("0 2 * * *", asynq.NewTask(constants.TaskTypeDataCleanup, nil))` 每天凌晨2点
- [ ] 24.15 移除 `cmd/worker/main.go` 中的 `startAlertChecker` 函数定义(第 239-265 行)
- [ ] 24.16 移除 `cmd/worker/main.go` 中的 `startCleanupScheduler` 函数定义(第 267-303 行)
- [ ] 24.17 移除 `cmd/worker/main.go` 中对 `startAlertChecker``startCleanupScheduler` 的调用和相关代码
- [ ] 24.18 验证编译:运行 `go build ./cmd/worker/...` 确认无编译错误
- [ ] 24.19 验证编译:运行 `go build ./internal/task/...` 确认无编译错误
- [ ] 24.20 验证编译:运行 `go build ./pkg/queue/...` 确认无编译错误
## 25. 提交和归档
- [ ] 25.1 使用 `/commit` 创建 Git commit提交消息"实现订单超时自动失效机制并重构定时任务为 Asynq Scheduler"
- [ ] 25.2 使用 `/opsx:verify` 验证实现与规格一致
- [ ] 25.3 使用 `/opsx:archive` 归档变更,同步 delta specs 到主规格文档
- [ ] 25.4 确认归档后 `openspec/specs/iot-order/spec.md``openspec/specs/order-payment/spec.md` 已更新