All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m22s
- 使用条件更新实现支付状态原子转换(pending -> paid) - 重复请求返回幂等成功,不再重复激活套餐 - 新增 tb_package_usage 唯一索引(order_id, package_id) - 新增幂等性和异常状态测试,测试覆盖率 71.7% - 归档 OpenSpec 变更 fix-order-activation-idempotency
3.0 KiB
3.0 KiB
订单激活幂等性修复 - 实现任务
1. 状态原子转换
- 1.1 在
internal/service/order/service.go中修改钱包支付和支付回调逻辑- 将支付状态更新改为条件更新:
WHERE id = ? AND payment_status = ?(只允许 pending -> paid) - 使用
result.RowsAffected == 0检查是否已被其他请求处理
- 将支付状态更新改为条件更新:
- 1.2 实现重复请求处理逻辑
- 当
RowsAffected == 0时,重新查询订单当前状态 - 订单状态为
model.PaymentStatusPaid:返回nil(幂等成功) - 订单状态为
model.PaymentStatusCancelled:返回errors.New(errors.CodeInvalidStatus, "订单已取消,无法支付") - 订单状态为
model.PaymentStatusRefunded:返回errors.New(errors.CodeInvalidStatus, "订单已退款,无法支付")
- 当
2. 激活幂等与事务一致性
- 2.1 调整
activatePackage调用时机- 只在条件更新成功(
RowsAffected > 0)后执行 - 幂等场景(订单已支付)直接返回,不再调用
activatePackage
- 只在条件更新成功(
- 2.2 审查
activatePackage内部数据访问- 确保所有订单明细、套餐信息查询使用事务
tx参数 - 避免使用非事务的
s.db或s.orderStore直接查询(应使用tx包装的 Store)
- 确保所有订单明细、套餐信息查询使用事务
3. 防御性约束(可选)
- 3.1 创建数据库迁移文件
- 在
migrations/目录创建迁移文件对(up/down) - 文件命名:
000033_add_unique_index_package_usage_order_package.up.sql - 文件命名:
000033_add_unique_index_package_usage_order_package.down.sql - Up 内容:
CREATE UNIQUE INDEX idx_package_usage_order_package ON tb_package_usage(order_id, package_id) WHERE deleted_at IS NULL; - Down 内容:
DROP INDEX IF EXISTS idx_package_usage_order_package;
- 在
- 3.2 代码中处理唯一冲突
- 在
activatePackage插入tb_package_usage前,先查询是否已存在 - 若已存在:记录警告日志,返回成功(防御性幂等)
- 若插入失败且为唯一冲突错误:返回成功(数据库层防御)
- 在
4. 测试与验证
- 4.1 新增集成测试到
internal/service/order/service_test.go- 幂等支付测试:串行多次调用
WalletPay,验证只生成一条PackageUsage记录 - 重复回调测试:连续多次调用支付回调,验证返回成功且不重复插入
- 已取消订单支付:创建已取消订单,调用支付接口,验证返回错误码 1050
- 已取消订单回调:创建已取消订单,调用支付回调,验证返回错误码 1050
- 幂等支付测试:串行多次调用
- 4.2 运行测试并验证
- 执行
source .env.local && go test -v ./internal/service/order/... - 确保所有测试通过
- 测试覆盖率:71.7%
- 执行
5. 文档更新
- 5.1 创建功能总结文档
- 创建目录:
docs/fix-order-activation-idempotency/ - 创建文件:
docs/fix-order-activation-idempotency/功能总结.md - 内容包含:问题背景、解决方案、技术实现、测试验证
- 创建目录:
- 5.2 更新 README.md(如有必要)
- 内部幂等性修复,无需更新 README.md