All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m40s
- 归档 OpenSpec 变更到 archive 目录 - 创建 2 个新的主规范文件:commission-trigger 和 order-commission-snapshot - 实现订单佣金快照字段和支付自动触发 - 确保事务一致性,所有佣金操作在同一事务内完成 - 提取成本价计算为公共工具函数
4.7 KiB
4.7 KiB
commission-trigger Specification
Purpose
TBD - created by archiving change fix-commission-calculation-trigger-and-snapshot. Update Purpose after archive.
Requirements
Requirement: 支付成功后自动入队佣金计算任务
系统 SHALL 在订单首次支付成功时自动 enqueue 佣金计算异步任务(commission:calculate),确保佣金及时发放。
触发条件:
- 订单从"待支付"变为"已支付"(首次成功支付)
- 订单
commission_status为pending(未计算)
任务参数:
- 任务类型:
commission:calculate - Payload:
{"order_id": <订单ID>}
Scenario: 首次支付成功触发计算
- WHEN 订单支付成功,订单状态从"待支付"变为"已支付"
- THEN 系统自动 enqueue
commission:calculate任务,payload 包含订单 ID - AND 订单
commission_status保持为pending(任务执行后才更新为calculated)
Scenario: 重复支付不重复触发
- WHEN 订单已经是"已支付"状态,再次收到支付成功通知(幂等场景)
- THEN 系统不重复 enqueue 佣金计算任务
- AND 日志记录"订单已支付,跳过重复入队"
Scenario: 已计算佣金的订单不触发
- WHEN 订单
commission_status为calculated(已计算) - THEN 系统跳过入队操作
- AND 日志记录"订单佣金已计算,跳过入队"
Requirement: 入队失败不影响支付主链路
系统 SHALL 确保佣金任务入队失败时不回滚订单支付成功状态,保障主业务链路稳定。
失败处理策略:
- 入队失败时记录 ERROR 级别日志(包含订单 ID、失败原因)
- 订单状态保持为"已支付",不回滚
- 订单
commission_status保持为pending,允许后续补偿
补偿机制:
- 后台补偿任务扫描
commission_status=pending且已支付的订单 - 人工触发佣金计算(后台接口)
- 定时任务重试入队(可选)
Scenario: 入队失败记录日志
- WHEN 佣金任务入队失败(队列服务不可用或网络超时)
- THEN 系统记录 ERROR 日志,包含订单 ID、失败原因、队列配置信息
- AND 订单支付状态保持为"已支付",不回滚
Scenario: 失败后允许补偿
- WHEN 后台补偿任务扫描到
commission_status=pending且payment_status=paid的订单 - THEN 系统可重新 enqueue 佣金计算任务或直接执行计算
- AND 避免佣金永久丢失
Requirement: 佣金计算任务幂等性
系统 SHALL 确保佣金计算任务可重复执行,不重复发放佣金。
幂等检查:
- 任务执行前检查订单
commission_status - 如果已为
calculated,跳过计算并返回成功
状态更新:
- 计算完成后将订单
commission_status更新为calculated - 状态更新与佣金记录创建在同一事务中
Scenario: 任务重复执行跳过计算
- WHEN 佣金计算任务执行时,订单
commission_status已为calculated - THEN 系统跳过佣金计算和钱包入账操作
- AND 任务返回成功(避免 Asynq 重试)
- AND 日志记录"订单佣金已计算,跳过执行"
Scenario: 并发任务只有一个成功
- WHEN 同一订单的佣金计算任务被重复入队,两个 worker 并发执行
- THEN 第一个任务成功完成计算并更新状态为
calculated - AND 第二个任务检查到状态已为
calculated,跳过计算
Scenario: 任务失败可安全重试
- WHEN 佣金计算任务执行失败(数据库异常、钱包服务不可用)
- THEN Asynq 自动重试任务
- AND 重试时幂等检查确保不重复发放佣金
Requirement: 队列客户端依赖注入
系统 SHALL 通过依赖注入方式将队列客户端注入到订单服务,遵循现有 bootstrap 架构。
注入位置:
internal/service/order/service.go的Service结构体- 添加
queueClient *asynq.Client字段
注入方式:
- 在
internal/bootstrap/services.go中初始化订单服务时传入队列客户端 - 队列客户端在
bootstrap.Bootstrap()中统一创建
Scenario: 订单服务接收队列客户端
- WHEN 系统启动时执行
bootstrap.Bootstrap() - THEN 订单服务(
order.Service)通过构造函数接收队列客户端实例 - AND 队列客户端可在服务内部调用
Enqueue()方法
Scenario: 支付成功时调用队列客户端
- WHEN 订单支付成功,订单服务执行入队操作
- THEN 系统通过注入的队列客户端调用
Enqueue("commission:calculate", payload) - AND 不在服务内部直接创建队列客户端(遵循依赖注入原则)