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

90 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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 数据,不回写为赠送语义。
- 赠送套餐历史识别优先以后台发放记录为准,补录清单只作为迁移输入,不改变判定标准。