Files
huang 2b3a9cb33f
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m19s
feat: 新增套餐使用记录实付金额快照字段 paid_amount
- 新增迁移 000129:tb_package_usage 添加 paid_amount BIGINT 字段,存量数据通过 JOIN tb_order 回填
- PackageUsage Model 新增 PaidAmount *int64 字段
- 4 个写入点(order service 主套餐/加油包、auto_purchase 主套餐/加油包)赋值 order.ActualPaidAmount
- AssetPackageResponse DTO 新增 paid_amount 字段
- GetCurrentPackage / GetPackages 填充 paid_amount,接口直接返回购买价格无需 JOIN 订单表
2026-04-18 10:19:21 +08:00

4.1 KiB
Raw Blame History

Design: add-paid-amount-snapshot-to-package-usage

Context

当前 tb_package_usage 表在套餐激活时快照了 package_name,但未快照购买实付金额。paid_amount 字段需要从 tb_order.actual_paid_amount 获取,目前只能通过 order_id JOIN 订单表实现。

当前 PackageUsage 创建路径有两条,各含主套餐和加油包,共 4 个写入点

  1. internal/service/order/service.goactivateMainPackage() / activateAddonPackage()(支付后激活)
  2. internal/task/auto_purchase.goactivateMainPackage() / activateAddonPackage()C 端充值自动购包)

时序保证:两条路径创建 PackageUsage 时,order.ActualPaidAmount 均已赋值:

  • 钱包支付 / 自动购包Order 创建时即写入 actual_paid_amount
  • 微信 / 支付宝:支付回调 HandlePaymentCallback 先更新 actual_paid_amount,再调用 activatePackage()
  • 线下支付:actual_paid_amount = nil(无实际收款,字段允许 null

约束:禁止外键、禁止 GORM 关联(项目规范),不能依赖 JOIN 查询。

Goals / Non-Goals

Goals

  • tb_package_usage 中存储购买时的实付金额快照
  • 使资产套餐两个接口current-package / packages直接返回 paid_amount,无需 JOIN
  • 存量数据通过迁移 SQL 回填JOIN 仅在一次性迁移中使用)

Non-Goals

  • 不修改订单表结构
  • 不改变套餐购买业务流程
  • 不修改 C 端 / H5 相关接口
  • 不修改其他展示场景(如订单列表、分佣等)中的价格来源

Decisions

决策 1快照字段还是 JOIN 查询

选择:快照字段(在 tb_package_usage 新增 paid_amount

理由

  • 项目规范禁止 GORM 关联JOIN 需手动实现,读频繁时性能代价不可忽视
  • 快照语义更清晰:记录的是"购买时的价格",而非"当前订单的价格"
  • package_name 已有先例,模式一致

备选方案JOIN tb_order — 被否决,原因是高频读场景性能差、与快照设计原则不符。

决策 2字段类型

选择BIGINT NULLABLEGo 侧 *int64

理由

  • 与项目所有金额字段统一单位bigint
  • Nullable 而非 NOT NULL DEFAULT 0:线下支付 actual_paid_amount 为 null区分"0元"和"无实付"语义不同
  • 存量 order_id = 0 的记录(如企业无订单直接分配套餐)回填为 null

备选方案NOT NULL DEFAULT 0 — 被否决0 与"无实付"语义歧义。

决策 3赋值来源

选择:直接读 order.ActualPaidAmount,不需要调用方额外传参

理由

  • 4 个写入点创建 PackageUsage 时均已持有完整 order 对象
  • 避免调用链增加参数,改动最小

决策 4存量数据回填策略

选择:迁移 SQL 中 UPDATE ... FROM tb_order 一次性回填

UPDATE tb_package_usage pu
SET paid_amount = o.actual_paid_amount
FROM tb_order o
WHERE pu.order_id = o.id
  AND pu.order_id != 0
  AND pu.paid_amount IS NULL;

理由

  • 历史数据量有限,一次性回填可接受
  • order_id = 0 的记录(无订单分配)保持 null语义正确

Risks / Trade-offs

风险 缓解措施
写入点遗漏:未来新增的 PackageUsage 创建路径忘记赋值 在 Model 层 PackageUsage 字段注释中标注"创建时必须从 order 赋值"
存量回填失败(迁移中途中断) 迁移 SQL 使用 IF NOT EXISTS + UPDATE 幂等写法,重跑安全
线下支付 null 值前端未处理 DTO 字段使用 *int64omitempty前端已有 null 处理惯例

Migration Plan

  1. 执行迁移 000129_add_paid_amount_to_package_usage.up.sql
    • ALTER TABLE tb_package_usage ADD COLUMN IF NOT EXISTS paid_amount BIGINT
    • UPDATE ... FROM tb_order 回填历史数据
  2. 部署代码Model / Service / DTO
  3. 新的套餐激活记录自动写入 paid_amount

回滚:执行 .down.sql DROP COLUMN代码回滚到上一版本。

Open Questions

无。时序、字段设计、回填策略均已确认。