Files
junhong_cmp_fiber/openspec/changes/archive/2025-07-27-implement-order-expiration/tasks.md
huang e661b59bb9
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m58s
feat: 实现订单超时自动取消功能,支持钱包余额解冻和 Asynq Scheduler 统一调度
- 新增 expires_at 字段和复合索引,待支付订单 30 分钟超时自动取消
- 实现 cancelOrder/unfreezeWalletForCancel 钱包余额解冻逻辑
- 创建 Asynq 定时任务(order_expire/alert_check/data_cleanup)
- 将原有 time.Ticker 轮询迁移至 Asynq Scheduler 统一调度
- 同步 delta specs 到 main specs 并归档变更
2026-02-28 17:16:15 +08:00

13 KiB
Raw Blame History

1. 数据库迁移

  • 1.1 创建迁移文件 migrations/000069_add_order_expiration.up.sql:添加 expires_at 字段和部分复合索引 idx_order_expires(expires_at, payment_status)
  • 1.2 创建回滚文件 migrations/000069_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.TimeIsExpired 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.goUpdatePaymentStatus() 方法中添加 expiresAt *time.Time 参数,支持更新过期时间
  • 4.3 验证编译:运行 go build ./internal/store/... 确认无编译错误
  • 4.4 使用 PostgreSQL MCP 工具验证查询:执行 FindExpiredOrders 的 SQL确认索引使用正确且查询耗时 < 50ms

5. Service 层修改 - 订单创建

  • 5.1 修改 internal/service/order/service.goCreateH5Order() 方法:待支付订单设置 expires_at = now + 30min
  • 5.2 修改 CreateH5Order() 方法:钱包支付和线下支付订单 expires_at = nil
  • 5.3 验证编译:运行 go build ./internal/service/order/... 确认无编译错误

6. Service 层修改 - 订单取消和钱包解冻

  • 6.1 重构 Cancel() 方法为内部 cancelOrder() 方法:添加钱包解冻逻辑(判断支付方式,计算解冻金额)
  • 6.2 在 cancelOrder() 方法中添加事务处理:订单状态更新(payment_status = 5, expires_at = nil)和钱包解冻在同一事务
  • 6.3 创建 unfreezeWalletForCancel() 方法:代理钱包通过 UnfreezeBalanceWithTx、卡钱包通过 frozen_balance 更新
  • 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 实现批量取消逻辑:遍历订单,调用 cancelOrder() 方法(复用钱包解冻逻辑)
  • 7.4 添加日志记录:处理订单数量、解冻钱包次数、执行耗时
  • 7.5 验证编译:运行 go build ./internal/service/order/... 确认无编译错误

8. Service 层修改 - 支付成功清除过期时间

  • 8.1 修改 WalletPay() 方法:支付成功时在 Updates map 中设置 "expires_at": nil
  • 8.2 修改 HandlePaymentCallback() 方法:支付成功时在 Updates map 中设置 "expires_at": nil
  • 8.3 验证编译:运行 go build ./internal/service/order/... 确认无编译错误

9. Task 层新增定时任务

  • 9.1 创建 internal/task/order_expire.go 文件,定义 OrderExpireHandler 结构体(使用局部 OrderExpirer 接口避免循环依赖)
  • 9.2 实现 NewOrderExpireHandler() 构造函数,依赖注入 orderExpirer, logger
  • 9.3 实现 HandleOrderExpire(ctx context.Context, task *asynq.Task) error 方法,调用 orderExpirer.CancelExpiredOrders()
  • 9.4 添加错误处理和重试逻辑:可重试错误返回 err
  • 9.5 添加日志记录:任务失败错误、成功处理订单数
  • 9.6 验证编译:运行 go build ./internal/task/... 确认无编译错误

10. Worker 注册定时任务 Handler

  • 10.1 在 pkg/queue/handler.goRegisterHandlers() 方法中调用 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))
  • 11.3 启动 Schedulergo func() { asynqScheduler.Run() }(),并在 shutdown 中调用 asynqScheduler.Shutdown()
  • 11.4 验证编译:运行 go build ./cmd/worker/... 确认无编译错误

12. Handler 层修改 - DTO 响应

  • 12.1 订单响应构建逻辑在 service 层 buildOrderResponse() 中实现,已添加 ExpiresAt 字段
  • 12.2 实现 IsExpired 动态计算逻辑:在 buildOrderResponse() 中判断 expiresAt != nil && paymentStatus == 1 && now.After(expiresAt)
  • 12.3 验证编译:运行 go build ./internal/handler/... 确认无编译错误

13. Handler 层修改 - 查询过期状态

  • 13.1 修改 internal/model/dto/order_dto.goListOrderRequest 添加 IsExpired *bool 查询参数(可选)
  • 13.2 修改 internal/store/postgres/order_store.goList() 方法:添加过期状态筛选条件
  • 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 = 3expires_at 变更为 NULL
  • 15.3 使用 PostgreSQL MCP 查询钱包:确认冻结余额减少 2000 分
  • 15.4 创建纯在线支付订单wechat取消订单确认不执行钱包解冻操作

16. 功能验证 - 支付成功清除过期时间

  • 16.1 创建待支付订单wechat确认 expires_at 不为 NULL
  • 16.2 模拟第三方支付回调成功,验证订单状态变更为已支付(payment_status = 2expires_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.goRegisterHandlers() 方法中调用 registerAlertCheckHandler()
  • 24.10 实现 registerAlertCheckHandler() 方法:创建 AlertCheckHandler 并注册到 mux.HandleFunc(constants.TaskTypeAlertCheck, ...)
  • 24.11 在 pkg/queue/handler.goRegisterHandlers() 方法中调用 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))
  • 24.15 移除 cmd/worker/main.go 中的 startAlertChecker 函数定义
  • 24.16 移除 cmd/worker/main.go 中的 startCleanupScheduler 函数定义
  • 24.17 移除 cmd/worker/main.go 中对 startAlertCheckerstartCleanupScheduler 的调用和相关代码
  • 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.mdopenspec/specs/order-payment/spec.md 已更新