## 1. 数据库迁移

- [x] 1.1 创建迁移文件 `migrations/000069_add_order_expiration.up.sql`：添加 `expires_at` 字段和部分复合索引 `idx_order_expires(expires_at, payment_status)`
- [x] 1.2 创建回滚文件 `migrations/000069_add_order_expiration.down.sql`：删除索引和字段
- [ ] 1.3 执行迁移验证：运行 `migrate up` 并检查表结构，确认字段和索引创建成功
- [ ] 1.4 测试回滚：运行 `migrate down` 并验证字段和索引删除成功，然后重新 `migrate up`

## 2. 常量定义

- [x] 2.1 在 `pkg/constants/constants.go` 中添加订单超时时间常量 `OrderExpireTimeout = 30 * time.Minute`
- [x] 2.2 在 `pkg/constants/constants.go` 中添加任务类型常量 `TaskTypeOrderExpire = "order:expire"`
- [x] 2.3 在 `pkg/constants/constants.go` 中添加批量处理数量常量 `OrderExpireBatchSize = 100`
- [x] 2.4 验证编译：运行 `go build ./...` 确认无编译错误

## 3. Model 层修改

- [x] 3.1 在 `internal/model/order.go` 中的 `Order` 结构体添加 `ExpiresAt *time.Time` 字段（指针类型，支持 NULL）
- [x] 3.2 在 `internal/model/dto/order_dto.go` 中的 `OrderResponse` 添加 `ExpiresAt *time.Time` 和 `IsExpired bool` 字段
- [x] 3.3 验证编译：运行 `go build ./internal/model/...` 确认无编译错误

## 4. Store 层新增方法

- [x] 4.1 在 `internal/store/postgres/order_store.go` 添加 `FindExpiredOrders(ctx, limit int) ([]*model.Order, error)` 方法：查询 `expires_at <= NOW() AND payment_status = 1` 的订单
- [x] 4.2 在 `internal/store/postgres/order_store.go` 的 `UpdatePaymentStatus()` 方法中添加 `expiresAt *time.Time` 参数，支持更新过期时间
- [x] 4.3 验证编译：运行 `go build ./internal/store/...` 确认无编译错误
- [ ] 4.4 使用 PostgreSQL MCP 工具验证查询：执行 `FindExpiredOrders` 的 SQL，确认索引使用正确且查询耗时 < 50ms

## 5. Service 层修改 - 订单创建

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

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

- [x] 6.1 重构 `Cancel()` 方法为内部 `cancelOrder()` 方法：添加钱包解冻逻辑（判断支付方式，计算解冻金额）
- [x] 6.2 在 `cancelOrder()` 方法中添加事务处理：订单状态更新（`payment_status = 5`, `expires_at = nil`）和钱包解冻在同一事务
- [x] 6.3 创建 `unfreezeWalletForCancel()` 方法：代理钱包通过 UnfreezeBalanceWithTx、卡钱包通过 frozen_balance 更新
- [x] 6.4 验证编译：运行 `go build ./internal/service/order/...` 确认无编译错误

## 7. Service 层新增方法 - 批量取消超时订单

- [x] 7.1 在 `internal/service/order/service.go` 添加 `CancelExpiredOrders(ctx context.Context) (int, error)` 方法
- [x] 7.2 实现 `CancelExpiredOrders()` 逻辑：调用 `FindExpiredOrders()` 查询超时订单（最多 100 条）
- [x] 7.3 实现批量取消逻辑：遍历订单，调用 `cancelOrder()` 方法（复用钱包解冻逻辑）
- [x] 7.4 添加日志记录：处理订单数量、解冻钱包次数、执行耗时
- [x] 7.5 验证编译：运行 `go build ./internal/service/order/...` 确认无编译错误

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

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

## 9. Task 层新增定时任务

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

## 10. Worker 注册定时任务 Handler

- [x] 10.1 在 `pkg/queue/handler.go` 的 `RegisterHandlers()` 方法中调用 `registerOrderExpireHandler()`
- [x] 10.2 实现 `registerOrderExpireHandler()` 方法：创建 `OrderExpireHandler` 并注册到 `mux.HandleFunc(constants.TaskTypeOrderExpire, ...)`
- [x] 10.3 验证编译：运行 `go build ./pkg/queue/...` 确认无编译错误

## 11. Worker 创建和启动 Asynq Scheduler

- [x] 11.1 在 `cmd/worker/main.go` 中创建 Asynq Scheduler 实例：`asynq.NewScheduler(redisOpt, &asynq.SchedulerOpts{Location: time.Local})`
- [x] 11.2 注册订单超时周期任务：`scheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeOrderExpire, nil))`
- [x] 11.3 启动 Scheduler：`go func() { asynqScheduler.Run() }()`，并在 shutdown 中调用 `asynqScheduler.Shutdown()`
- [x] 11.4 验证编译：运行 `go build ./cmd/worker/...` 确认无编译错误

## 12. Handler 层修改 - DTO 响应

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

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

- [x] 13.1 修改 `internal/model/dto/order_dto.go` 的 `ListOrderRequest` 添加 `IsExpired *bool` 查询参数（可选）
- [x] 13.2 修改 `internal/store/postgres/order_store.go` 的 `List()` 方法：添加过期状态筛选条件
- [x] 12.3 验证编译：运行 `go build ./...` 确认无编译错误

## 14. 功能验证 - 订单创建

- [x] 14.1 启动 API 服务，使用 Postman/curl 创建待支付订单（H5 端，支付方式 wechat），验证 `expires_at` 字段设置正确（约 `now + 30min`）
- [x] 14.2 使用 PostgreSQL MCP 工具查询订单：`SELECT id, expires_at, payment_status FROM tb_order WHERE id = ?`，确认 `expires_at` 不为 NULL
- [x] 14.3 创建后台钱包支付订单，验证 `expires_at` 为 NULL（订单立即支付成功）

## 15. 功能验证 - 订单取消和钱包解冻

- [x] 15.1 创建混合支付待支付订单（钱包预扣 2000 分），使用 PostgreSQL MCP 查询钱包冻结余额
- [x] 15.2 调用取消订单 API，验证订单状态变更为已取消（`payment_status = 3`），`expires_at` 变更为 NULL
- [x] 15.3 使用 PostgreSQL MCP 查询钱包：确认冻结余额减少 2000 分
- [x] 15.4 创建纯在线支付订单（wechat），取消订单，确认不执行钱包解冻操作

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

- [x] 16.1 创建待支付订单（wechat），确认 `expires_at` 不为 NULL
- [x] 16.2 模拟第三方支付回调成功，验证订单状态变更为已支付（`payment_status = 2`），`expires_at` 变更为 NULL
- [x] 16.3 使用 PostgreSQL MCP 查询订单：`SELECT id, expires_at, payment_status FROM tb_order WHERE id = ?`，确认 `expires_at` 为 NULL

## 17. 功能验证 - 定时任务自动取消

- [x] 17.1 使用 PostgreSQL MCP 手动修改订单的 `expires_at` 为过去时间：`UPDATE tb_order SET expires_at = NOW() - INTERVAL '1 minute' WHERE id = ?`
- [x] 17.2 启动 Worker 服务，等待 1 分钟后检查日志，确认定时任务执行成功
- [x] 17.3 使用 PostgreSQL MCP 查询订单：确认订单状态变更为已取消，`expires_at` 变更为 NULL
- [x] 17.4 如果是混合支付订单，使用 PostgreSQL MCP 查询钱包：确认冻结余额解冻

## 18. 功能验证 - 查询过期状态

- [x] 18.1 使用 Postman/curl 调用订单列表 API，筛选 `is_expired = true`，验证返回已过期的待支付订单
- [x] 18.2 调用订单列表 API，筛选 `is_expired = false`，验证返回未过期的待支付订单
- [x] 18.3 调用订单详情 API，验证响应包含 `is_expired` 字段且计算正确

## 19. 性能验证

- [x] 19.1 使用 PostgreSQL MCP 的 `explain_query` 工具分析 `FindExpiredOrders` 查询：确认使用 `idx_order_expires` 索引
- [x] 19.2 验证查询耗时：在订单数量 > 10000 的情况下，查询耗时 < 50ms
- [x] 19.3 验证定时任务处理耗时：单批次处理 100 条订单，总耗时 < 5s
- [x] 19.4 使用 PostgreSQL MCP 检查数据库连接池状态：确认无连接池阻塞

## 20. 错误处理验证

- [x] 20.1 模拟数据库连接失败场景：确认定时任务返回可重试错误，Asynq 自动重试
- [x] 20.2 模拟钱包不存在场景：确认订单取消失败，事务回滚，订单状态不变
- [x] 20.3 模拟冻结余额不足场景：确认订单取消失败，事务回滚，记录错误日志
- [x] 20.4 检查日志：确认所有错误场景都记录了详细日志（包含订单 ID、错误原因）

## 21. 代码质量检查

- [x] 21.1 运行 `gofmt -s -w .` 格式化代码
- [x] 21.2 运行 `go vet ./...` 检查代码问题
- [x] 21.3 运行 `go build ./...` 确认全部编译通过
- [x] 21.4 检查所有新增代码的中文注释：确认符合注释规范

## 22. 文档更新

- [x] 22.1 创建功能总结文档 `docs/order-expiration/功能总结.md`：说明超时机制、钱包解冻、查询过期状态
- [x] 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. 最终验证

- [x] 23.1 在开发环境完整测试一次完整流程：创建订单 → 超时自动取消 → 钱包解冻
- [x] 23.2 检查所有日志输出：确认日志级别正确（Info/Error），日志内容完整
- [x] 23.3 检查数据库：确认无脏数据（如订单已取消但钱包未解冻）
- [x] 23.4 使用 Postman 导出 API 测试用例集（包含订单创建、取消、查询过期状态）

## 24. 重构现有定时任务为 Asynq Scheduler

- [x] 24.1 在 `pkg/constants/constants.go` 中添加告警检查任务类型常量 `TaskTypeAlertCheck = "alert:check"`
- [x] 24.2 在 `pkg/constants/constants.go` 中添加数据清理任务类型常量 `TaskTypeDataCleanup = "data:cleanup"`
- [x] 24.3 创建 `internal/task/alert_check.go` 文件，定义 `AlertCheckHandler` 结构体
- [x] 24.4 实现 `NewAlertCheckHandler()` 构造函数，依赖注入 `alertService`, `logger`
- [x] 24.5 实现 `HandleAlertCheck(ctx context.Context, task *asynq.Task) error` 方法，调用 `alertService.CheckAlerts()`
- [x] 24.6 创建 `internal/task/data_cleanup.go` 文件，定义 `DataCleanupHandler` 结构体
- [x] 24.7 实现 `NewDataCleanupHandler()` 构造函数，依赖注入 `cleanupService`, `logger`
- [x] 24.8 实现 `HandleDataCleanup(ctx context.Context, task *asynq.Task) error` 方法，调用 `cleanupService.RunScheduledCleanup()`
- [x] 24.9 在 `pkg/queue/handler.go` 的 `RegisterHandlers()` 方法中调用 `registerAlertCheckHandler()`
- [x] 24.10 实现 `registerAlertCheckHandler()` 方法：创建 `AlertCheckHandler` 并注册到 `mux.HandleFunc(constants.TaskTypeAlertCheck, ...)`
- [x] 24.11 在 `pkg/queue/handler.go` 的 `RegisterHandlers()` 方法中调用 `registerDataCleanupHandler()`
- [x] 24.12 实现 `registerDataCleanupHandler()` 方法：创建 `DataCleanupHandler` 并注册到 `mux.HandleFunc(constants.TaskTypeDataCleanup, ...)`
- [x] 24.13 在 `cmd/worker/main.go` 的 Asynq Scheduler 中注册告警检查周期任务：`scheduler.Register("@every 1m", asynq.NewTask(constants.TaskTypeAlertCheck, nil))`
- [x] 24.14 在 `cmd/worker/main.go` 的 Asynq Scheduler 中注册数据清理周期任务：`scheduler.Register("0 2 * * *", asynq.NewTask(constants.TaskTypeDataCleanup, nil))`
- [x] 24.15 移除 `cmd/worker/main.go` 中的 `startAlertChecker` 函数定义
- [x] 24.16 移除 `cmd/worker/main.go` 中的 `startCleanupScheduler` 函数定义
- [x] 24.17 移除 `cmd/worker/main.go` 中对 `startAlertChecker` 和 `startCleanupScheduler` 的调用和相关代码
- [x] 24.18 验证编译：运行 `go build ./cmd/worker/...` 确认无编译错误
- [x] 24.19 验证编译：运行 `go build ./internal/task/...` 确认无编译错误
- [x] 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` 已更新
