|
|
@@ -0,0 +1,101 @@
|
|
|
+# CFC-EN-001 独立核验报告
|
|
|
+
|
|
|
+> 核验对象:`/api/energy/grant` 返回 500「服务器内部错误」修复(后端未捕获异常)。
|
|
|
+> 被测提交:`fcc6aa876`(`fix(energy): CFC-EN-001 修复 /api/energy/grant 500 服务器内部错误`)
|
|
|
+> 核验基线:`origin/feature/cfc-en-001-energy-grant-500` = `fcc6aa876`
|
|
|
+> 核验分支:`verify/cfc-en-001`(基于被测提交 `checkout -b`,已 `rebase origin/cfclub` 无冲突,重放后提交 `129b1ab3f`)
|
|
|
+> 核验方式:独立 worktree `D:\tmp\en001-verify`,只读纪律下逐项验收,**未修改任何业务代码**。
|
|
|
+> 核验日期:2026-11-01
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 总体结论:**verdict = PASS**
|
|
|
+
|
|
|
+- 检出 + rebase(步骤1):成功,无冲突。
|
|
|
+- 编译(步骤2):`mvn -T 1C compile -DskipTests` → **BUILD SUCCESS**(2032 源文件,1:54min)。
|
|
|
+- 单测(步骤3):新增 2 类 **5/5 全绿**(Controller 3 + Service 2);顺带跑 commit message 声称的 energy 相关套件 **11/11 全绿**(GrantEnergy 5 + Family 2 + WealthIncome 4)。
|
|
|
+- 逻辑审查(步骤4):Q1–Q4、Q6 全部通过;Q5 为**既有**小瑕疵(非本次引入)。
|
|
|
+- 边界风险(步骤5):无阻断性风险;2 项为**既有**并发非原子性,非本次引入。
|
|
|
+
|
|
|
+**结论:建议合入。** 无需要修订的缺陷;2 项非阻断观察建议(缺参 code=500 语义、并发非原子)列入后续技术债,不阻塞本修复。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 步骤 1:检出 + Rebase
|
|
|
+
|
|
|
+| 项 | 结果 |
|
|
|
+|----|------|
|
|
|
+| `git checkout -b verify/cfc-en-001 origin/feature/cfc-en-001-energy-grant-500` | ✅ HEAD=fcc6aa876 |
|
|
|
+| `git rebase origin/cfclub`(origin/cfclub=ddf36696d) | ✅ 无冲突,1/1 重放成功 |
|
|
|
+| 重放后提交 | `129b1ab3f`(仅 commit message 不变) |
|
|
|
+| 与 cfclub 差异 | 4 文件,+265/-32(`EnergyService.java` 40 行改、2 个新测试类、e2e spec 20 行改) |
|
|
|
+
|
|
|
+- merge-base(origin/cfclub, feature) = `7baa09caa`,feature 分支仅领先 1 个提交,rebase 语义干净。
|
|
|
+- 无 `DatabaseInitializer`/schema.sql 改动 → **未新增 DB 迁移**(符合纪律)。
|
|
|
+
|
|
|
+## 步骤 2:编译
|
|
|
+
|
|
|
+命令:`mvn -T 1C compile -DskipTests`(Maven 3.9.6,Java 1.8.0_25)
|
|
|
+
|
|
|
+| 结果 | 说明 |
|
|
|
+|------|------|
|
|
|
+| **BUILD SUCCESS** | 2032 源文件编译通过,1:54min;仅有既有 deprecation/unchecked 警告(KnowledgeBaseController、ReportBlockAssembler、CommissionDistService410Test、UserServiceFamilyNameLinkTest),与本修复无关 |
|
|
|
+
|
|
|
+## 步骤 3:单测
|
|
|
+
|
|
|
+命令:`mvn -T 1C test -Dtest="..." -DfailIfNoTests=false -DskipTests=false`
|
|
|
+> 注:本仓库 pom 默认 skipTests=true(见提交 03e1eb810),必须显式 `-DskipTests=false` 才真正执行;首次按指令文件未带该参数时日志显示「Tests are skipped」假绿灯,已按仓库既定约定修正重跑。
|
|
|
+
|
|
|
+### 3.1 新增 2 类(指令文件要求范围)— 5/5 全绿
|
|
|
+
|
|
|
+| 测试类 | 用例 | 结果 |
|
|
|
+|--------|------|:----:|
|
|
|
+| `EnergyControllerGrantEnergyTest` | `grantEnergy_success_returns200WithData` | ✅ |
|
|
|
+| | `grantEnergy_missingParam_returnsError` | ✅(断言 code=500,锁定既有缺参行为) |
|
|
|
+| | `grantEnergy_noFamily_returnsNoFamilyError` | ✅ |
|
|
|
+| `EnergyServiceGrantEnergyTest` | `grantEnergy_bodyDimension_succeedsAndWritesLog` | ✅ |
|
|
|
+| | `grantEnergy_invalidDimension_returnsBadRequest` | ✅ |
|
|
|
+
|
|
|
+### 3.2 energy 相关套件(commit message 声称 11 例)— 11/11 全绿
|
|
|
+
|
|
|
+| 测试类 | 用例数 | 结果 |
|
|
|
+|--------|:----:|:----:|
|
|
|
+| `EnergyControllerGrantEnergyTest` | 3 | ✅ |
|
|
|
+| `EnergyServiceFamilyTest` | 2 | ✅ |
|
|
|
+| `EnergyServiceGrantEnergyTest` | 2 | ✅ |
|
|
|
+| `EnergyServiceWealthIncomeTest` | 4 | ✅ |
|
|
|
+
|
|
|
+## 步骤 4:逻辑审查(6 项)
|
|
|
+
|
|
|
+| # | 审查点 | 结论 | 理由 |
|
|
|
+|---|--------|:----:|------|
|
|
|
+| **Q1** | 根因是否彻底消除 | ✅ | `grantEnergy`(L1994) 不再调用 `awardEnergy("task")`,改为显式构造 `allocations={请求维度: amount}` 后直接 `executeAward`(L2018),完全绕过 `matchBehaviorConfig("task")→task_complete`(`dimension_assign=[{"dim":"action","ratio":100}]`,L8131 种子)的 100%-action 固定分配。请求 body 时 `executeAward` 返回 `{"body":amount}`,`balanceMap.get("body")` 不再为 null。单测 `grantEnergy_bodyDimension_succeedsAndWritesLog` 实证。 |
|
|
|
+| **Q2** | 日上限逻辑 | ✅ | `isDailyLimitExceeded(memberId, {bodyId}, amount)`(L2011) 走 4 参版本(L2211):全局查当日全部正向流水之和 + amount vs `GLOBAL_DAILY_LIMIT=200`(L1608);再逐维度查 `dimToday + amount` vs `DAILY_LIMIT_MAP[code]`(body=50, mind=40, wisdom=80, action=60, wealth=50,L1600-1606)。覆盖「本次请求 + 今日已有」。与既有 `awardByConfig` 共用同一校验(语义一致)。 |
|
|
|
+| **Q3** | executeAward 签名一致性 | ✅ | 调用 `executeAward(memberId, "task", taskId, amount.intValue(), "任务奖励", null, allocations)` 与定义 `(Long, String, Long, Integer, String, Integer, Map<Long,Integer>)`(L1723) 完全匹配。编译通过 + 单测全绿。 |
|
|
|
+| **Q4** | 异常安全网 | ✅ | `IllegalArgumentException`(L24-27)与 `IllegalStateException`(L30-33)在 `GlobalExceptionHandler` 均显式映射 `Result.error(400, msg)`;不再落入 catch-all `Exception→500`(L101-105)。非法维度/非正数→400「维度代码无效/能量数量必须为正数」;日上限/余额失败→400「今日能量发放已达上限/维度没有分配能量」。 |
|
|
|
+| **Q5** | Controller 缺参早返回 | ⚠ | `Result.error("memberId、taskId和amount不能为空")` 的默认 code=**500**(`Result.error(String)` L37-42)。消息友好但 code=500 对「缺参客户端错误」语义不当,**应为 400**。此行为为**既有**(Controller 与 `Result.error` 均未改动,新增单测 L72 甚至锁定该 500),**非本次修复引入**,不阻塞合入;建议后续单独改 400。另 `dimensionCode` 未在 Controller 判空,但 `getDimByCode(null)`(HashMap 容忍 null key,`initDimMaps` L2302-2308)返回 null → `IllegalArgumentException`→400,不 500。 |
|
|
|
+| **Q6** | e2e 用例修复 | ✅ | `energy-api.spec.js`:移除 场景2(L53)/场景6(L131) 两处 `test.fixme`;删除 场景2c(已知缺陷复现、断言 code=500)。场景2 断言 `grant.code=200` + `grant.data` + 流水 `sourceId==taskId`;场景6 断言 200 + 流水可查。与修复后契约一致。注:e2e 为远程 `cfc.iwintrue.com` 运行,需修复部署后才转绿;本核验不执行远程。 |
|
|
|
+
|
|
|
+## 步骤 5:边界风险检查
|
|
|
+
|
|
|
+| # | 检查点 | 结论 | 说明 |
|
|
|
+|---|--------|:----:|------|
|
|
|
+| B1 | `getOrCreateBalance` 缺失记录时 insert vs update | ✅ | `EnergyBalance` 用 `@TableId(IdType.AUTO)`(L17-18),`getOrCreateBalance`(L2182) 缺失时先 `insert`(回填 id),再 `executeAward` L1738 `updateById(balance)` 可正确定位。无「只插不更/只更不插」错配。 |
|
|
|
+| B2 | `GLOBAL_DAILY_LIMIT` 常量 | ✅ | `= 200`(L1608),`DAILY_LIMIT_MAP` 五维上限已确认(body 50 / mind 40 / wisdom 80 / action 60 / wealth 50)。 |
|
|
|
+| B3 | 并发非原子性 | ⚠ 既有 | `isDailyLimitExceeded`(L2211) 读-判-写非原子,`executeAward`(L1734-1738) 余额读-改-写非原子;多线程同 member 同维度同日并发可超发/丢更新。**该模式为既有**(`awardByConfig` 同用),非本次引入;建议后续对 `(member_id, dimension_id)` 加唯一约束 + 行锁或原子 `UPDATE ... SET balance=balance+?` 加固。 |
|
|
|
+
|
|
|
+## 其他非阻断观察(供主理人知悉)
|
|
|
+
|
|
|
+1. **语义变更(有意为之)**:`grantEnergy` 维度由调用方显式指定并直接发放到该维度,不再受行为配置固定比例约束。对非 `action` 维度(body/mind/wisdom/wealth),此修复将**必现 500 转为 200(额度内)/400(超该维度日上限)**,属净改善;对 `action` 维度行为不变。`task_complete` 的 `daily_limit=200`(种子 L8131)不再被本 API 查询,改由全局默认 200 + 请求维度自身上限约束,结果更严格但语义自洽。
|
|
|
+2. **过期语义无变化**:`task_complete` 种子 `expire_days=0`(L8131)→ 旧 `awardByConfig` 中 `getExpireDays()>0` 为假 → `daysToExpire=null`;新路径 `executeAward(..., expireDays=null, ...)` 亦不过期。等效。
|
|
|
+3. **被绕过配置字段无实际影响**:`task_complete` 的 `enabled=1`、`cooldown_seconds=0`、`amount_source=points`;绕过 `enabled`/`cooldown` 均无副作用(enabled 恒真、cooldown 恒 0),`amount_source` 因 amount 由调用方显式传入而无影响。
|
|
|
+4. **`grantEnergy` 调用面**:生产侧仅 `EnergyController.grantEnergy`(L223) 一处调用;`awardEnergy` 仍供 TaskService 等其它来源使用,未受影响。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 核验人签名
|
|
|
+
|
|
|
+- 核验分支:`verify/cfc-en-001`(基于 `fcc6aa876` checkout -b,rebase origin/cfclub 无冲突)
|
|
|
+- 核验范围:检出/rebase ✅ / 编译 ✅ / 单测 ✅(5/5 新增、11/11 energy 套件)/ 逻辑审查 ✅(Q5 既有小瑕疵)/ 边界风险 ✅(2 项既有并发债)
|
|
|
+- 最终 verdict:**PASS(建议合入)**
|
|
|
+- 只读纪律:本报告仅含核验发现,未对业务代码/数据库/迁移做任何写操作;唯一新增文件为本报告,提交至独立分支 `verify/cfc-en-001`,**未回写 cfclub、未合并任何分支到 cfclub**。
|