Files
junhong_cmp_fiber/openspec/specs/order-expiration/spec.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

8.2 KiB
Raw Blame History

Order Expiration

Purpose

自动管理订单的超时失效,确保待支付订单在超时后自动取消,防止"僵尸订单"堆积,并自动释放已冻结的资源(如钱包余额)。

This capability supports:

  • 订单超时时间配置和管理
  • 定时扫描和自动取消超时订单
  • 钱包余额自动解冻
  • 过期订单查询和筛选

ADDED Requirements

Requirement: 订单过期时间字段

系统 SHALL 为每个订单设置过期时间字段(expires_at),用于判断订单是否超时。

字段定义

  • expires_at订单过期时间TIMESTAMP可为 NULL
  • 创建时自动设置:expires_at = created_at + 30分钟(仅待支付订单)
  • 已支付/已取消/已退款订单的 expires_at 为 NULL

索引设计

  • 复合索引:idx_order_expires(expires_at, payment_status) 优化定时任务查询

Scenario: 创建待支付订单时设置过期时间

  • WHEN 用户创建订单,支付方式为 wechat 或 alipay订单状态为待支付payment_status = 1
  • THEN 系统设置 expires_at = created_at + 30分钟

Scenario: 创建钱包支付订单(后台)不设置过期时间

  • WHEN 代理在后台创建订单,支付方式为 wallet订单立即支付成功payment_status = 2
  • THEN 系统不设置 expires_at,字段值为 NULL

Scenario: 订单支付成功后清除过期时间

  • WHEN 待支付订单支付成功状态变更为已支付payment_status = 2
  • THEN 系统将 expires_at 设置为 NULL

Scenario: 订单取消后清除过期时间

  • WHEN 订单被取消payment_status = 3
  • THEN 系统将 expires_at 设置为 NULL

Requirement: 订单超时自动取消

系统 SHALL 通过定时任务自动扫描并取消超时订单。任务每分钟执行一次,批量处理超时订单。

任务配置

  • 任务类型:TaskTypeOrderExpire = "order:expire"
  • 执行频率:每分钟
  • 单批处理量:最多 100 条
  • 超时时间:OrderExpireTimeout = 30 * time.Minute

任务逻辑

  1. 查询条件:expires_at <= NOW() AND payment_status = 1
  2. 批量取消订单:更新 payment_status = 3expires_at = NULL
  3. 钱包余额解冻(如果订单涉及钱包预扣)
  4. 记录日志

Scenario: 定时任务扫描超时订单

  • WHEN 定时任务执行,当前时间为 2026-02-28 10:30:00
  • THEN 系统查询 expires_at <= '2026-02-28 10:30:00' AND payment_status = 1 的订单,最多 100 条

Scenario: 批量取消超时订单

  • WHEN 查询到 50 条超时订单
  • THEN 系统批量更新订单状态为已取消payment_status = 3expires_at = NULL

Scenario: 钱包余额解冻(混合支付)

  • WHEN 超时订单使用了混合支付,钱包预扣 2000 分
  • THEN 系统解冻钱包余额 2000 分(frozen_balance 减少 2000

Scenario: 钱包余额解冻纯钱包支付H5 端)

  • WHEN 超时订单使用了钱包支付H5 端创建待支付订单),钱包预扣 3000 分
  • THEN 系统解冻钱包余额 3000 分

Scenario: 无需解冻钱包(在线支付)

  • WHEN 超时订单使用了纯在线支付wechat/alipay没有钱包预扣
  • THEN 系统不执行钱包解冻操作

Scenario: 任务执行日志

  • WHEN 定时任务执行完成
  • THEN 系统记录日志:处理订单数量、解冻钱包次数、执行耗时

Requirement: 订单过期状态查询

系统 SHALL 支持按过期状态筛选订单,便于运营人员查询和分析超时订单。

查询条件(新增):

  • is_expired(布尔值):
    • true:查询已过期的待支付订单(expires_at <= NOW() AND payment_status = 1
    • false:查询未过期的待支付订单(expires_at > NOW() AND payment_status = 1
    • 不传:不按过期状态筛选

Scenario: 查询已过期的待支付订单

  • WHEN 运营人员查询订单列表,筛选 is_expired = true
  • THEN 系统返回 expires_at <= NOW() AND payment_status = 1 的订单列表

Scenario: 查询未过期的待支付订单

  • WHEN 运营人员查询订单列表,筛选 is_expired = false
  • THEN 系统返回 expires_at > NOW() AND payment_status = 1 的订单列表

Scenario: 订单详情显示过期状态

  • WHEN 查询订单详情,订单为待支付且已超时
  • THEN 响应包含 is_expired = trueexpires_at 字段显示过期时间

Scenario: 订单列表响应包含过期时间

  • WHEN 查询订单列表
  • THEN 每个订单响应包含 expires_at 字段(可为 NULL

Requirement: 钱包余额解冻逻辑

系统 SHALL 在订单取消(手动或自动)时,根据支付方式自动解冻钱包余额。

解冻规则

  • 钱包支付H5 端待支付订单):解冻 total_amount
  • 混合支付:解冻 wallet_payment_amount
  • 纯在线支付:无需解冻
  • 后台钱包一步支付:无需解冻(订单创建时已完成支付)

Scenario: 手动取消订单,解冻钱包

  • WHEN 用户手动取消待支付订单,订单使用混合支付,钱包预扣 2000 分
  • THEN 系统解冻钱包余额 2000 分,订单状态变更为已取消

Scenario: 自动取消订单,解冻钱包

  • WHEN 定时任务自动取消超时订单,订单使用钱包支付,钱包预扣 3000 分
  • THEN 系统解冻钱包余额 3000 分,订单状态变更为已取消

Scenario: 取消订单,无钱包预扣

  • WHEN 用户取消待支付订单订单使用纯在线支付wechat
  • THEN 系统不执行钱包解冻操作

Scenario: 钱包解冻事务保证

  • WHEN 订单取消涉及钱包解冻
  • THEN 订单状态更新和钱包余额解冻在同一事务中完成,任一失败则全部回滚

Requirement: 超时配置常量

系统 SHALL 定义订单超时相关常量,统一管理超时时间和任务类型。

常量定义pkg/constants/constants.go

  • OrderExpireTimeout = 30 * time.Minute订单超时时间30 分钟)
  • TaskTypeOrderExpire = "order:expire":订单超时取消任务类型

Scenario: 使用常量设置过期时间

  • WHEN 创建待支付订单
  • THEN 系统使用 constants.OrderExpireTimeout 计算 expires_at

Scenario: 使用常量注册任务

  • WHEN 注册 Asynq 定时任务
  • THEN 系统使用 constants.TaskTypeOrderExpire 作为任务类型

Requirement: 性能优化

系统 SHALL 通过索引优化和批量处理确保超时任务的性能符合要求。

性能指标

  • 定时任务查询耗时 < 50ms
  • 单批次处理耗时 < 5s
  • 单批处理量100 条

优化措施

  • 使用复合索引 idx_order_expires(expires_at, payment_status) 优化查询
  • 批量更新订单状态(单 SQL 语句)
  • 钱包解冻支持批量操作(单事务)

Scenario: 复合索引优化查询

  • WHEN 定时任务查询超时订单
  • THEN 数据库使用 idx_order_expires 索引,查询耗时 < 50ms

Scenario: 批量处理限制

  • WHEN 超时订单数量超过 100 条
  • THEN 系统单次最多处理 100 条,剩余订单下次执行时处理

Scenario: 任务执行时间限制

  • WHEN 定时任务执行
  • THEN 单批次处理耗时 < 5s包括查询、更新、解冻、日志记录

Requirement: 数据库迁移

系统 SHALL 提供数据库迁移脚本,添加 expires_at 字段和索引。

迁移内容

  • 添加字段:ALTER TABLE tb_order ADD COLUMN expires_at TIMESTAMP NULL COMMENT '订单过期时间'
  • 添加索引:CREATE INDEX idx_order_expires ON tb_order(expires_at, payment_status)

回滚脚本

  • 删除索引:DROP INDEX idx_order_expires ON tb_order
  • 删除字段:ALTER TABLE tb_order DROP COLUMN expires_at

Scenario: 迁移脚本执行成功

  • WHEN 执行 migrate up
  • THEN tb_order 表新增 expires_at 字段和 idx_order_expires 索引

Scenario: 回滚脚本执行成功

  • WHEN 执行 migrate down
  • THEN tb_order 表删除 expires_at 字段和 idx_order_expires 索引

Scenario: 迁移对现有数据的影响

  • WHEN 执行迁移脚本
  • THEN 已存在的订单 expires_at 字段值为 NULL不影响现有业务