Files
junhong_cmp_fiber/openspec/changes/package-price-fallback-and-platform-gift-policy/design.md
huang 37b4483183
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m55s
赠送套餐逻辑
2026-04-28 17:40:06 +08:00

5.0 KiB
Raw Blame History

Context

当前套餐价格在多个链路里被直接读取后台配置、C 端展示、订单创建、自动购包和代理授权都在用同一批字段,但它们需要的其实不是同一种值。与此同时,赠送套餐已经被业务明确为独立语义,既不是普通可售套餐的别名,也不能靠 package_type 或数值 0 去推断。这个变更会同时影响 Handler、Service、Store、Model 和迁移脚本,必须先把语义拆开,再改各条链路。

Goals / Non-Goals

Goals:

  • 建立普通可售套餐的原始价与生效价分层。
  • 在建议零售价未配置时,统一回退到成本价。
  • 普通可售套餐不能显式配置 0 作为售卖价0 只保留给赠送语义。
  • 把赠送套餐做成独立语义,平台专属,禁止代理分配和 C 端自购。
  • 保留原始配置值,避免把回退后的值写回配置页。
  • 完成历史数据与存量记录的迁移回填,并输出人工复核清单。

Non-Goals:

  • 不重做套餐类型体系,也不把 gift 语义塞进 package_type
  • 不新增外部依赖,不改用新的队列或存储组件。
  • 不在本次变更里重构整个套餐管理域,只处理价格与赠送政策相关路径。

Decisions

1. 价格状态与价格数值分开存

采用独立的价格配置状态来区分“未配置”“赠送 0”“已配置为非 0”原始建议零售价继续保留生效售价由服务层计算。

原因:普通可售套餐不能显式卖 0但赠送套餐仍可能使用 0不能再用数值 0 当哨兵值。

备选方案

  • 继续用 0 代表未配置,最简单,但会把合法 0 价和未配置混在一起,不能接受。
  • 直接覆盖原始字段为生效价,查询简单,但配置页会丢失真实原值。

2. 赠送语义独立于价格和套餐类型

新增独立的赠送语义标记,平台专属限制和价格规则彼此独立。

原因:平台-only、不可分配给代理、不可自购这些都是业务权限问题不是价格问题。

备选方案

  • 仅靠 package_type=formal/addon 推断,已经被明确否决。
  • 仅靠价格为 0 推断,无法区分合法 0 价与未配置。

3. 生效价格在 Service 层统一计算

Handler 只接收和返回值不做价格推导。Store 只取原始数据Service 统一计算生效价并下发给下游。

原因:价格规则会同时影响 C 端列表、下单、自动购包和授权链路,放在 Service 层更容易复用,也更符合分层职责。

备选方案

  • 在 Handler 里临时计算,容易散落和重复。
  • 在 Store 里拼接业务规则,会把数据访问和业务逻辑混在一起。

4. 迁移采用“先加字段,再回填,再切流”的顺序

先把新语义字段和状态字段上线,再用迁移脚本回填历史数据,最后让读链路切到生效价。

原因:这样可以避免一次性改动过大导致历史数据无法解释,也方便回滚。

备选方案

  • 直接替换旧字段,风险太高,历史记录会失真。
  • 先切读后补数据,会出现短时间内的口径不一致。

5. 不引入价格缓存

价格计算只依赖单条套餐和分配记录,属于 O(1) 读取,不新增 Redis 缓存层。

原因:这次变更的核心是语义和一致性,不是性能瓶颈。现有分页与查询边界已经足够。

备选方案

  • 引入缓存可以做,但会增加失效和回填复杂度,不适合作为首版方案。

Risks / Trade-offs

  • [历史数据无法准确判断是否为赠送 0 价] → 迁移脚本优先读取历史发放记录和配置快照,只有能明确识别为赠送 0 的记录才保留为赠送 0其余旧 0 一律转为未配置并输出待人工核验清单。
  • [赠送语义改动会影响多条购买链路] → 先在 Service 层统一封装判定函数,再逐步替换各入口的直接读取。
  • [价格展示口径切换后前端可能误解返回值] → 接口文档明确区分原始值、生效值和赠送语义,前后端一起切换。
  • [存量分配记录字段回填出错] → 迁移前后保留回滚脚本,按批次执行并校验记录数。

Migration Plan

  1. 增加价格状态字段、赠送语义字段和平台专属标记,保持原始价格字段不变。
  2. 回填存量套餐的价格状态和生效价,优先使用历史配置与发放记录判断。只有能明确识别为赠送 0 的记录才保留为赠送 0其余旧 0 一律转为未配置,并输出人工复核清单。
  3. 回填分配记录、历史订单和自动购包相关快照,让展示链路能读取生效价。
  4. 切换 Service 层统一价格解析与赠送校验,再切换 Handler 输出。
  5. 验证完成后,清理旧的直接读取路径,保留原始值用于配置页和审计。

Implementation Notes

  • 人工复核清单只负责承接无法正向识别的旧 0 数据,不回写为赠送语义。
  • 赠送套餐历史识别优先以后台发放记录为准,补录清单只作为迁移输入,不改变判定标准。