|
|
@@ -0,0 +1,361 @@
|
|
|
+# SC-X-22 · CF 值获取预估 —— 后端设计契约
|
|
|
+
|
|
|
+> 版本:v1.0 | 日期:2026-11-05 | 分支:`feat/sc-x22-contract`(基于 `origin/cfclub` @ `10d6c9c9b`)
|
|
|
+> 上游场景:`tests/user-scenarios/应用场景总览.md` § 八 **SC-X-22 · CF 值获取预估(全局 UI 规范)**;入口叙事见 `tests/user-scenarios/02-parent.md`(P-77 活动打卡/助力、P-10 创建任务下发、P-13 心愿兑换、P-87 任务审批、P-89 手动 grant、P-93 文章互动)与 `07-five-dimension.md`(C-05/C-25/C-04 等)。
|
|
|
+> 测试用例:`tests/test-cases/cross-role-e2e.md`「CF 值获取预估专项用例(本轮新增 SC-X-22)」(`TC-X-22` / `TC-X-22-E` / `TC-X-22-E2` / `TC-X-22-P`)。
|
|
|
+> 底层事实源:`docs/superpowers/specs/2026-10-25-cf-value-single-source-of-truth.md`(CF 唯一账本 `cf_balance` / 流水 `cf_balance_log` / 规则 `cf_earn_rule` / 日限 `cf_earn_daily_limit` / 幂等键 / 三道闸)。**本契约是 SC-X-22 的「预估展示层」契约,不改变也不覆盖单一事实源的账本/规则/幂等底层约定,二者冲突时以单一事实源为准(仅涉及预估展示口径)。**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 0. 一句话结论
|
|
|
+
|
|
|
+**凡是平台向用户发放 CF 值的操作,进入操作前必须在页面入口展示「本操作预计获得 N CF」,由后端 `RewardRuleService` 按当日有效规则服务端计算 `expectedCf` 并随产出点接口一并返回;客户端一律禁止传入 `expectedCf`(伪造值服务端忽略),预估与实际回执分离(预估 ≠ 实际时按实际入账、不回溯历史)。**
|
|
|
+
|
|
|
+- **预估责任在服务端**:`expectedCf` 只由 `RewardRuleService` 计算,客户端不传、传了也忽略。
|
|
|
+- **CF 与五维能量不同源**:CF(`RewardRuleService`)与能量(`EnergyRuleEngine`)**独立记账、互不换算**;能量入口(审批/方案打卡/手动 grant/文章互动)**不得**返回 `expectedCf`、**不得**出现「+N CF」文案。
|
|
|
+- **三类产出点文案**:获得型「本操作预计获得 N CF」;消耗型「本操作将消耗 N CF」;零值「本操作无 CF 奖励」或省略,**禁止「+0 CF」**。
|
|
|
+- **幂等与上限**:同 `idempotency-key` 重放只入账一次;当日累计触顶按剩余额度显示与入账,不超上限。
|
|
|
+- **错误降级**:规则未配置 / 接口超时 3s → 入口降级「CF 奖励以实际为准」,接口不 500、页面不白屏。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 概览
|
|
|
+
|
|
|
+### 1.1 目的
|
|
|
+
|
|
|
+SC-X-22 是**跨角色全局 UI 规范**:让用户在做出产生 CF 值变动的操作前,先知情「本操作将获得/消耗多少 CF」再决策,避免事后才发现奖励低于预期引发争议。本契约把该规范落到**后端接口契约**,定义:
|
|
|
+
|
|
|
+1. `expectedCf` 的计算责任、来源与返回载体;
|
|
|
+2. 产出点(获得型 / 消耗型 / 零值)的分类与文案规范;
|
|
|
+3. 预估接口的端点、请求体、响应体;
|
|
|
+4. 幂等键与日上限的行为口径;
|
|
|
+5. 与五维能量的反向隔离;
|
|
|
+6. 权限(越权 / 防伪造);
|
|
|
+7. 错误降级策略。
|
|
|
+
|
|
|
+### 1.2 范围
|
|
|
+
|
|
|
+**In(覆盖)**:
|
|
|
+- 所有 CF 值产出点(获得型)与消耗点(消耗型)的**操作前预估**;
|
|
|
+- `expectedCf` 的返回载体(产出点列表/详情/入口接口)、预估专用接口;
|
|
|
+- 预估 ≠ 实际的回执(完成操作时展示实际值、原因、不回溯);
|
|
|
+- 幂等重放、日上限触顶、规则未配置 / 超时降级;
|
|
|
+- 能量侧反向隔离断言。
|
|
|
+
|
|
|
+**Out(不覆盖,仍属单一事实源范畴)**:
|
|
|
+- CF 账本结构、规则配置表结构、幂等键底层唯一键、三道闸(日限/累计上限/冷却)—— 以 `2026-10-25-cf-value-single-source-of-truth.md` 为准;
|
|
|
+- 五维能量(`EnergyRuleEngine`)自身的计算与展示 —— 属 ED-1~17 范畴,本契约**只**约束「能量入口不得混入 CF」的反向隔离;
|
|
|
+- 优惠券 / 积分 / 普通成长积分等**非 CF** 口径。
|
|
|
+
|
|
|
+### 1.3 引用(测试用例表)
|
|
|
+
|
|
|
+| 用例 | 类型 | 优先级 | 一句话断言 | 关联场景 |
|
|
|
+|---|---|---|---|---|
|
|
|
+| `TC-X-22` | 正常流(功能) | P0 | 各 CF 产出点入口展示预估,抓包 `expectedCf` 与页面一致,单位/图标/文案全局统一;**能量页不出现 CF 字样**(反向断言) | X-22 / P-10、C-05、X-07、T-6 |
|
|
|
+| `TC-X-22-E` | 异常流(边界+反向隔离) | P1 | 活动倍率/日上限/幂等重放/规则未配置/超时的降级;回执按实际入账不回溯;**四个能量入口不返回 `expectedCf`**;CF 与能量**分别独立展示** | X-22 / P-87、C-25、P-89、P-93 |
|
|
|
+| `TC-X-22-E2` | 异常流(零值与负向) | P2 | 规则为 0 →「本操作无 CF 奖励」或省略(**禁止「+0 CF」**);消耗型 →「本操作将消耗 N CF」;无新增发放场景不展示获得型预估 | X-22 / P-13、C-04、X-12 |
|
|
|
+| `TC-X-22-P` | 权限流 | P1 | 游客/未登录不泄露规则数值;孩子 token 调家长侧预估 403;跨家庭 403;非 admin 读/改规则 403;客户端伪造 `expectedCf` 被忽略、按服务端真实规则入账 | X-22 / P-77、X-07 |
|
|
|
+
|
|
|
+> 用例全文本见 `tests/test-cases/cross-role-e2e.md` 第 145–164 行;「口径前提」确认 CF 与五维能量不同源、独立记账、互不换算。
|
|
|
+
|
|
|
+### 1.4 关键术语
|
|
|
+
|
|
|
+| 术语 | 定义 |
|
|
|
+|---|---|
|
|
|
+| `expectedCf` | **预估 CF 值**。服务端 `RewardRuleService` 按「当日有效规则」在操作发生前计算并返回的 int;正=获得、负=消耗、0=无奖励。**只读、只由服务端产生。** |
|
|
|
+| 实际入账(`actualCf`) | 完成操作时**真实**记入 `cf_balance_log` 的 CF 值。预估 ≠ 实际(活动结束/日限/风控)时以实际为准,回执展示实际值,**不回溯历史**。 |
|
|
|
+| 幂等键(`idempotency-key`) | 客户端在请求体传入的请求级幂等键(建议 UUID)。**同键重放只入账一次**;底层账本唯一键 `(member_id, type, ref_type, ref_id)` 为最终防线(单一事实源 §4)。 |
|
|
|
+| 日上限 | 会员等级当日可获得的 CF 上限(`cf_earn_daily_limit`)。当日累计 + 本次预估 > 上限时,**显示与入账均按剩余额度**,不超上限。 |
|
|
|
+| 产出点 | 触发 CF 值变动的操作(获得型 / 消耗型 / 零值三类,见 §3)。 |
|
|
|
+| 降级 | 规则未配置 / 预估接口超时(3s)时,入口退化为「CF 奖励以实际为准」或省略预估,不 500、不白屏。 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 服务端计算
|
|
|
+
|
|
|
+### 2.1 `expectedCf` 计算责任
|
|
|
+
|
|
|
+- **唯一计算方**:`RewardRuleService`。所有产出点接口(列表/详情/入口)及预估专用接口的 `expectedCf` 一律由它按**当日有效规则**计算。
|
|
|
+- **当日有效规则**:计算时点 `cf_earn_rule` 中该产出点对应规则的生效配置(数量 / 活动倍率 / 限时加成 / 日限 / 冷却),与「展示时」而非「操作发生时」对齐。**规则动态变化(活动倍率、限时加成)取展示时生效规则,不回溯历史。**
|
|
|
+- **数量口径**:与单一事实源 §2.2 一致(如 1 元 = 100 CF 走 `params_json` 的 `cf_per_yuan`),**禁止写死业务常量**。
|
|
|
+- **不得**依赖客户端任何输入来计算(客户端传入仅作资源定位:操作类型、资源 ID)。
|
|
|
+
|
|
|
+```java
|
|
|
+// 概念签名(实现阶段落地)
|
|
|
+int expectedCf = rewardRuleService.estimateCf(
|
|
|
+ MemberId memberId,
|
|
|
+ String operationType, // 产出点分类(见 §3)
|
|
|
+ String resourceType, // 资源类型(GROWTH_TASK / PRODUCT_ORDER / ACTIVITY_CHECKIN ...)
|
|
|
+ String resourceId // 稳定业务单据 ID
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+### 2.2 禁止客户端传值(防伪造)
|
|
|
+
|
|
|
+- **客户端一律禁止在请求体传入 `expectedCf`**;即使传入(如伪造 `expectedCf=999999`),服务端**忽略该字段**,以 `RewardRuleService` 服务端计算为准,入账金额等于真实规则值。
|
|
|
+- 防伪造落点(`TC-X-22-P` 第 5 步):预估接口、下单接口、打卡接口的入账路径统一以服务端计算为准;**不信任**任何客户端提供的金额/倍率/上限。
|
|
|
+- 实现建议:请求体 DTO 不含 `expectedCf` 字段(或标记 `@JsonIgnoreProperties(ignoreUnknown = true)` + 注释「该字段由服务端计算,客户端传入将被忽略」),避免歧义。
|
|
|
+
|
|
|
+### 2.3 预估与实际回执分离
|
|
|
+
|
|
|
+- **预估接口**(操作前):返回 `expectedCf`(预估),`actualCf = null`。
|
|
|
+- **回执接口**(操作后 / 完成时):返回 `actualCf`(实际入账值)+ `reason`(当实际 ≠ 预估或触顶时的原因)。
|
|
|
+- **预估 ≠ 实际**:一律**按实际入账**,回执展示「实际 +M CF(预估 N,原因:…)」;**不回溯**已入账历史(已入账金额不变)。
|
|
|
+- 典型分支(`TC-X-22-E` 第 1、2 步):
|
|
|
+ - 活动提前结束(预估 20、实际 10)→ 回执「实际 +10 CF(预估 20,原因:活动已结束)」,账目按 10 入账;
|
|
|
+ - 触顶(预估 20、剩余 5)→ 回执「已达今日上限,本次实际 +5 CF」,余额精确 +5,不超上限;
|
|
|
+ - 实际 > 预估(额外加成)→ 同样以实际为准,回执展示实际值。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 产出点分类
|
|
|
+
|
|
|
+> 分类依据:操作对 CF 值的**净方向**。文案模板以「CF 值 + 方向语义」表达,禁止歧义。
|
|
|
+
|
|
|
+| 类型 | 净方向 | 标准文案(模板) | 适用 | 禁止 |
|
|
|
+|---|---|---|---|---|
|
|
|
+| **获得型** | 正向(+N CF) | 「本操作预计获得 N CF」(`N>0`);审核发分场景可用等效文案「本操作将发放 N CF」 | 打卡、下单、任务完成、审核发分、分润、活动、勋章、测评等所有发放 CF 的产出点 | 禁止负向/「消耗」字眼误用 |
|
|
|
+| **消耗型** | 负向(−N CF) | 「本操作将消耗 N CF」(`N>0`) | 心愿兑换(P-13)、商城兑换(C-04)、会员续费抵扣、提现冻结等扣减场景 | 禁止「获得」/「+N」字眼误用;不误用获得型预估 |
|
|
|
+| **零值** | 0 | 「本操作无 CF 奖励」或**省略**预估 | 规则值为 0、无新增发放的场景 | **禁止「+0 CF」** |
|
|
|
+
|
|
|
+### 3.1 获得型(正向 +N CF)
|
|
|
+
|
|
|
+- 展示「本操作预计获得 N CF」(或「本操作将发放 N CF」,审核发分场景)。
|
|
|
+- 数值一律为**正整数**(`N>0`),前缀「+」仅作为符号强调可选,**不以「获得 N CF」代替「+N CF」造成口径分裂**——文案与 `expectedCf` 数值严格对应(`expectedCf = +N`)。
|
|
|
+- 典型产出点(初版清单,见场景 §八):打卡类(P-77 活动打卡/助力、P-86 成长打卡榜、C-05 方案任务打卡、C-08 方案打卡进度、健康打卡)、购买/消费类(X-01 购套餐、X-07 商城履约/下单、X-12 会员权益、C-22 孩子商城兑换(发放侧))、分润类(规划师分润、服务商结算)、任务/游戏类(C-02 执行任务、P-10 创建任务下发-孩子完成、T-6 规划师审核发分、T-18 批量任务下发)、测评类(X-01 测评完成、T-10 测评预约完成)、勋章类(C-09 勋章墙、C-10 勋章解锁、X-06 方案激活→勋章)、活动营销类(P-77 助力裂变)。
|
|
|
+
|
|
|
+### 3.2 消耗型(负向「本操作将消耗 N CF」)
|
|
|
+
|
|
|
+- 展示「本操作将消耗 N CF」,`expectedCf = −N`(负数)。
|
|
|
+- **不误用获得型预估**:消耗型入口不得出现「预计获得」「+N」。
|
|
|
+- 典型:心愿兑换(P-13)、商城兑换(C-04)、会员续费抵扣、提现冻结(冻结部分)等扣减场景。
|
|
|
+- 与获得型**不同源但同一契约**:二者都由 `RewardRuleService` 按规则符号返回,前端按 `expectedCf` 符号选择文案模板。
|
|
|
+
|
|
|
+### 3.3 零值(展示「本操作无 CF 奖励」或省略,禁止 +0 CF)
|
|
|
+
|
|
|
+- 规则存在但值为 0(`expectedCf = 0`)→ 展示「本操作无 CF 奖励」**或省略预估**。
|
|
|
+- **禁止展示「+0 CF」**(空头奖励文案)。
|
|
|
+- 无新增发放的场景(如纯续费无首购奖励、非产出动作)→ 不展示获得型预估,可省略。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 接口契约
|
|
|
+
|
|
|
+> 通用约定(项目级,见根 AGENTS.md):统一 `@PostMapping`(禁止 `@GetMapping/@PutMapping/@DeleteMapping`);统一 `Result<T>`(code/message/data);JWT Bearer Token 经 `Authorization` Header,`JwtInterceptor` 拦截 `/api/**`(8 个公开路径除外)。
|
|
|
+
|
|
|
+### 4.1 端点
|
|
|
+
|
|
|
+| 端点(建议) | 用途 | 时机 | 返回 |
|
|
|
+|---|---|---|---|
|
|
|
+| `POST /api/reward/estimate` | 预估专用接口:按操作类型/资源计算 `expectedCf` | 操作前(入口/详情加载) | `data.expectedCf`、`data.display` |
|
|
|
+| 各产出点列表/详情/入口接口(既有端点,如 `POST /api/cf/balance` 等) | 在**既有响应 `data` 内**追加 `expectedCf` 字段 | 列表/详情/入口加载 | 追加 `data.expectedCf`、`data.display` |
|
|
|
+| 完成操作接口(下单/打卡/审核等既有端点) | 返回**实际入账** `actualCf` + `reason` | 操作后(回执) | `data.actualCf`、`data.reason` |
|
|
|
+
|
|
|
+> 预估专用端点**建议**为 `POST /api/reward/estimate`;若实现阶段选择在各产出点既有接口内嵌 `expectedCf` 而非新增专用端点,亦符合本契约(两种载体等效,但**不得同时缺省**:任一 CF 产出点入口必须能取到 `expectedCf`)。
|
|
|
+
|
|
|
+### 4.2 请求体(`POST /api/reward/estimate`)
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "idempotencyKey": "9f8e7d6c-5b4a-4321-9876-abcdef012345",
|
|
|
+ "operationType": "CHECKIN",
|
|
|
+ "resourceType": "GROWTH_TASK",
|
|
|
+ "resourceId": "102345"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|---|---|---|---|
|
|
|
+| `idempotencyKey` | string | 是 | 请求级幂等键(建议 UUID)。同操作重放复用同键 → 只入账一次。 |
|
|
|
+| `operationType` | string | 是 | 操作类型 / 产出点分类枚举(`CHECKIN` / `PURCHASE` / `AUDIT` / `EXCHANGE` / `WISH` / `TASK_COMPLETE` ...,见 §3)。 |
|
|
|
+| `resourceType` | string | 是 | 资源类型(`GROWTH_TASK` / `PRODUCT_ORDER` / `ACTIVITY_CHECKIN` / `ASSESSMENT_REPORT` ...)。 |
|
|
|
+| `resourceId` | string | 是 | 稳定业务单据 ID(订单号、任务 ID、活动 ID、规则记录 ID 等)。 |
|
|
|
+
|
|
|
+> **注意**:请求体**不含** `expectedCf`(见 §2.2)。`memberId` 不传——由服务端从 JWT 解析当前登录用户 → `family_members` 换算(防跨家庭伪造,见 §7)。
|
|
|
+
|
|
|
+### 4.3 响应体
|
|
|
+
|
|
|
+**预估接口(操作前)**:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "message": "ok",
|
|
|
+ "data": {
|
|
|
+ "expectedCf": 20,
|
|
|
+ "actualCf": null,
|
|
|
+ "reason": null,
|
|
|
+ "currency": "CF",
|
|
|
+ "operationType": "CHECKIN",
|
|
|
+ "resourceType": "GROWTH_TASK",
|
|
|
+ "resourceId": "102345",
|
|
|
+ "idempotencyKey": "9f8e7d6c-5b4a-4321-9876-abcdef012345",
|
|
|
+ "display": {
|
|
|
+ "type": "EARN",
|
|
|
+ "template": "本操作预计获得 20 CF",
|
|
|
+ "cfValue": 20
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**回执接口(操作后)**:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 200,
|
|
|
+ "message": "ok",
|
|
|
+ "data": {
|
|
|
+ "expectedCf": 20,
|
|
|
+ "actualCf": 10,
|
|
|
+ "reason": "活动已结束",
|
|
|
+ "currency": "CF",
|
|
|
+ "display": {
|
|
|
+ "type": "EARN",
|
|
|
+ "template": "实际 +10 CF(预估 20,原因:活动已结束)",
|
|
|
+ "cfValue": 10
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|---|---|---|
|
|
|
+| `expectedCf` | int | 服务端预估(正=获得、负=消耗、0=无奖励)。**客户端不可写。** |
|
|
|
+| `actualCf` | int \| null | 实际入账值;预估接口为 `null`,回执接口为真实入账。 |
|
|
|
+| `reason` | string \| null | 实际 ≠ 预估或触顶时的原因(如「活动已结束」「已达今日上限」)。 |
|
|
|
+| `currency` | string | 单位,固定 `"CF"`。 |
|
|
|
+| `display.type` | string | `EARN`(获得)/ `SPEND`(消耗)/ `ZERO`(零值)/ `LIMIT_HIT`(触顶)/ `DEGRADED`(降级)。 |
|
|
|
+| `display.template` | string | 前端可直接渲染的统一文案(见 §4.4)。 |
|
|
|
+| `display.cfValue` | int | 带方向的展示数值(同 `expectedCf` 或 `actualCf` 符号)。 |
|
|
|
+
|
|
|
+### 4.4 单位 / 图标 / 文案统一规范
|
|
|
+
|
|
|
+| 维度 | 规范 |
|
|
|
+|---|---|
|
|
|
+| **单位** | 统一 `CF`(大写),**禁止**「点」「积分」「c币」「金币」等异名;数值为整数。 |
|
|
|
+| **图标** | 全局统一使用**同一** CF 奖励图标(建议金币/奖励徽章,由前端 UI Token 定义),禁止各入口自选图标;能量(五维)图标不得用于 CF 预估。 |
|
|
|
+| **文案模板(获得型)** | 「本操作预计获得 N CF」;审核发分「本操作将发放 N CF」;紧凑徽标可「+N CF」(三者等价,`N=expectedCf`)。 |
|
|
|
+| **文案模板(消耗型)** | 「本操作将消耗 N CF」。 |
|
|
|
+| **文案模板(零值)** | 「本操作无 CF 奖励」或省略(**禁止「+0 CF」**)。 |
|
|
|
+| **文案模板(触顶)** | 「已达今日上限,本次实际 +M CF」。 |
|
|
|
+| **文案模板(实际≠预估)** | 「实际 +M CF(预估 N,原因:…)」。 |
|
|
|
+| **文案模板(降级)** | 「CF 奖励以实际为准」或省略预估。 |
|
|
|
+| **样式** | 同一产出点、同一端、同一文案风格(字号/颜色/图标由前端 UI Token 统一,非本契约范围)。 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. 幂等与上限
|
|
|
+
|
|
|
+### 5.1 幂等(同 `idempotency-key` 重放只入账一次)
|
|
|
+
|
|
|
+- **请求级幂等**:同 `idempotency-key` 重复提交(如 `TC-X-22-E` 第 3 步重放 5 次)→ **仅第 1 次入账并提示「获得」**,后 4 次**不重复入账、不再提示「获得」**,流水仅 1 条。
|
|
|
+- 底层最终防线:`cf_balance_log (member_id, type, ref_type, ref_id)` 唯一键(单一事实源 §4),二者**都命中才幂等**。
|
|
|
+- **预估仅对首次有效操作成立**:重复/重试不二次提示发放;预估接口对同一 `idempotency-key` 返回同一 `expectedCf`(服务端按规则计算,不随重放变化)。
|
|
|
+
|
|
|
+### 5.2 日上限(触顶按剩余额度,不超上限)
|
|
|
+
|
|
|
+- 当日累计 CF + 本次预估 > 日上限(`cf_earn_daily_limit`,按会员等级)→ **显示与入账均按剩余额度**,`actualCf = 上限 − 当日累计`,**不超上限**。
|
|
|
+- 例:日上限 20,已累计 15,入口预估 20 → 回执「已达今日上限,本次实际 +5 CF」,余额精确 +5。
|
|
|
+- 三道闸(会员等级日限 → 累计上限 → 冷却期)底层逻辑见单一事实源 §4;预估接口在**返回前**先按当日累计推算剩余额度,保证「展示的预估 ≤ 可入账额度」。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. 反向隔离(能量 vs CF)
|
|
|
+
|
|
|
+> **核心原则**:CF 与五维能量**不同源、独立记账、互不换算**。本契约只覆盖 CF 侧;能量侧**不得**混入 CF 预估。
|
|
|
+
|
|
|
+### 6.1 能量入口不得返回 `expectedCf`、不得出现「+N CF」
|
|
|
+
|
|
|
+以下五维能量变动入口(`TC-X-22-E` 第 5 步)的**页面与接口均不得返回 `expectedCf`、不得展示任何「+N CF」文案**,能量变动仅按五维口径展示(如「智 +5」):
|
|
|
+
|
|
|
+| 能量入口 | 场景 |
|
|
|
+|---|---|
|
|
|
+| 任务审批 | SC-P-87 |
|
|
|
+| 方案打卡 | SC-C-25 |
|
|
|
+| 手动 grant | SC-P-89 |
|
|
|
+| 文章互动 | SC-P-93 |
|
|
|
+
|
|
|
+> 这些入口属于 `EnergyRuleEngine`(ED-1~17)范畴,**不适用** SC-X-22 的「预计 +N CF」提示。实现上:能量接口响应体**不定义** `expectedCf` 字段;前端能量组件**不渲染** CF 徽标/文案。
|
|
|
+
|
|
|
+### 6.2 同一操作同时产 CF 与能量:分别独立展示,不合并
|
|
|
+
|
|
|
+- 同一操作同时产生 CF(+20)与能量(智 +5)→ 回执与页面**分别展示为两个独立数字**,不合并为单一数字。
|
|
|
+- **禁止**出现「+20 CF 能量」「+25(CF+能量)」类混写。
|
|
|
+- CF 侧展示预估(`expectedCf`),能量侧按既有五维提示口径独立展示,二者互不折算。
|
|
|
+
|
|
|
+### 6.3 与五维能量的边界结论
|
|
|
+
|
|
|
+| 维度 | 归口 | 展示 |
|
|
|
+|---|---|---|
|
|
|
+| CF 值 | `RewardRuleService`(SC-X-22 契约) | 「本操作预计获得 N CF」/「将消耗 N CF」 |
|
|
|
+| 五维能量 | `EnergyRuleEngine`(ED-1~17,`07-five-dimension.md`) | 「智 +5」等五维口径,**无 CF 字样** |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. 权限
|
|
|
+
|
|
|
+| 场景 | 预期 | 依据 |
|
|
|
+|---|---|---|
|
|
|
+| 游客 / 未登录打开任一 CF 产出点入口 | **不展示预估或展示「登录后可见」**,不泄露规则数值(`expectedCf` 不返回),接口返回 401 | `TC-X-22-P` 第 1 步 |
|
|
|
+| 孩子 token 调家长侧产出点预估接口(活动打卡预估、套餐购买预估) | **角色不符 403**,不返回预估 | `TC-X-22-P` 第 2 步 |
|
|
|
+| 家长 A 请求无关家庭 F9 成员的产出点预估 | **403**,不返回他人 `expectedCf` | `TC-X-22-P` 第 3 步 |
|
|
|
+| 非 admin 调用奖励规则**读**接口 | **403** | `TC-X-22-P` 第 4 步 |
|
|
|
+| 非 admin 尝试**改**奖励规则 | **403 且规则未被改动** | `TC-X-22-P` 第 4 步 |
|
|
|
+| 客户端伪造 `expectedCf=999999` 提交下单/打卡 | 服务端**忽略**,以 `RewardRuleService` 计算为准,入账 = 真实规则值 | `TC-X-22-P` 第 5 步 |
|
|
|
+
|
|
|
+> 角色/家庭归属判断:`memberId` 由服务端从 JWT 解析当前登录用户 → 所属家庭 → `family_members` 换算;**不得**信任客户端传入的成员/家庭 ID(防横向越权)。规则读/改接口沿用既有 admin 权限控制(根 AGENTS.md「角色控制:控制器内手动检查 `@RequestAttribute("role")`」)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. 错误降级
|
|
|
+
|
|
|
+| 场景 | 行为 | 禁止 |
|
|
|
+|---|---|---|
|
|
|
+| 该产出点奖励**规则未配置**(`cf_earn_rule` 无对应行) | 入口降级为「CF 奖励以实际为准」**或省略预估**(不展示 `expectedCf`) | 不返回 500、不白屏 |
|
|
|
+| 预估接口**超时 3s** | 入口降级为「CF 奖励以实际为准」**或省略预估** | 不返回 500、不白屏 |
|
|
|
+| 预估接口**内部异常** | 捕获并降级(同超时),**不阻塞**下单/打卡主流程 | 不抛 500、不阻塞业务 |
|
|
|
+| 规则值为 0 | 展示「本操作无 CF 奖励」或省略(见 §3.3) | **禁止「+0 CF」** |
|
|
|
+
|
|
|
+> 实现建议:预估接口为**旁路读**(non-blocking),主流程(下单/打卡/审核)**不依赖**预估接口成功;预估失败仅降级展示,主操作照常执行。前端对预估接口调用设置 3s 超时,超时即降级为「CF 奖励以实际为准」,**不**因预估接口挂起而白屏或阻塞用户操作。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. 验收对照(用例 → 契约条款)
|
|
|
+
|
|
|
+| 用例步骤 | 契约落点 |
|
|
|
+|---|---|
|
|
|
+| `TC-X-22` 1–4:各入口展示预估 | §3(三类产出点)、§4.4(文案/单位/图标) |
|
|
|
+| `TC-X-22` 5:抓包 `expectedCf` 与页面一致 | §2.1(服务端计算)、§4.3(响应体) |
|
|
|
+| `TC-X-22` 6 / `TC-X-22-E` 5:能量页无 CF 字样 | §6(反向隔离) |
|
|
|
+| `TC-X-22-E` 1:活动结束实际≠预估不回溯 | §2.3(预估≠实际分离) |
|
|
|
+| `TC-X-22-E` 2:触顶按剩余额度 | §5.2(日上限) |
|
|
|
+| `TC-X-22-E` 3:幂等重放只入账一次 | §5.1(幂等键) |
|
|
|
+| `TC-X-22-E` 4:规则未配置/超时降级 | §8(错误降级) |
|
|
|
+| `TC-X-22-E` 6:CF 与能量独立展示不合并 | §6.2 |
|
|
|
+| `TC-X-22-E2` 1:零值禁「+0 CF」 | §3.3 |
|
|
|
+| `TC-X-22-E2` 2:消耗型负向文案 | §3.2 |
|
|
|
+| `TC-X-22-E2` 3:无新增发放不展示获得型 | §3.3 |
|
|
|
+| `TC-X-22-P` 1–4:越权 | §7(权限) |
|
|
|
+| `TC-X-22-P` 5:伪造被忽略 | §2.2(禁止客户端传值) |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 10. 后端实现缺口(现状 → 目标)
|
|
|
+
|
|
|
+> 供实现阶段对照;本契约**只定义契约,不实现业务代码**。
|
|
|
+
|
|
|
+| 现状(`origin/cfclub` @ 10d6c9c9b) | 目标 |
|
|
|
+|---|---|
|
|
|
+| `RewardRuleService` **不存在**(`cfc-backend` 无该类) | 新建 `RewardRuleService`:按当日有效规则计算 `expectedCf`(§2.1) |
|
|
|
+| `expectedCf` 字段**不存在**于任何响应 | 产出点接口/预估接口返回 `expectedCf`(§4.3) |
|
|
|
+| 前端 `cfRule.js` / `CfRuleConfig.vue`(管理端)仅引用,无预估契约 | 管理端规则配置不变;预估契约仅约束 C 端/家长端展示(§4.4) |
|
|
|
+| 无 `POST /api/reward/estimate` | 新增预估端点(§4.1)或各产出点接口内嵌 `expectedCf` |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 11. 变更记录
|
|
|
+
|
|
|
+| 版本 | 日期 | 说明 |
|
|
|
+|---|---|---|
|
|
|
+| v1.0 | 2026-11-05 | 初版契约:SC-X-22 CF 值获取预估后端设计契约(基于 TC-X-22 / -E / -E2 / -P 反推)。 |
|