Просмотр исходного кода

docs(cf): 打卡CF后台配置改造实施任务书(T1-T6,含廖总5项拍板)

Agnes Agent 1 неделя назад
Родитель
Сommit
f21232e98b
1 измененных файлов с 305 добавлено и 0 удалено
  1. 305 0
      docs/superpowers/plans/2026-10-31-checkin-cf-config-implementation.md

+ 305 - 0
docs/superpowers/plans/2026-10-31-checkin-cf-config-implementation.md

@@ -0,0 +1,305 @@
+# 打卡 CF 值后台配置化改造 · 实施任务书
+
+> 生成:2026-10-31 22:0x · 基于廖总 5 项拍板
+> 基座:origin/cfclub = 865017a2d(最新)
+
+---
+
+## 廖总拍板结论(单一事实源)
+
+| # | 决策点 | 拍板 |
+|---|---|---|
+| 1 | 等级金额通道形态 | ✅ `params_json.levels[levelCode]` 显式值(可审计) |
+| 2 | 等级档位 | ✅ 动态读 `membership_levels` 表 |
+| 3 | F11 里程碑(B1/B2) | ✅ 本次一并迁移到 DB |
+| 4 | points 写死(P1–P3) | ✅ 修(P3 bug 顺手) |
+| 5 | 数值矩阵 | ✅ FAMILY=非会员×2,PREMIUM=非会员×5(非原设计 0.5/1.0/1.5) |
+
+---
+
+## 一、现状盘点(核查结论)
+
+### 1.1 CF 规则引擎现状(F02–F24)
+
+| 规则 | 打卡内容 | 类型 | 当前 amount | params_json 现状 | 调用方 |
+|---|---|---|---|---|---|
+| F05 | 活动签到 | per_item | 活动配置 platform_points | `{"source":"activities.platform_points"}` | ActivityService:450 |
+| F07 | 文章答题 | formula | score+全对3 | `{"bonus_all_correct":3}` | CfPathwayEarnService.earnArticleQuizCf |
+| F08 | AI阅读答题 | fixed | 5 | `{"require_all_correct":true}` | CfPathwayEarnService.earnAiReadingQuizCf |
+| F14 | 喝水打卡 | fixed | 5 | `{"daily_limit_by_tier":"cf_earn_daily_limit"}` | **未接调用方** ❌ |
+| F15 | 饮食打卡 | fixed | 5 | `{"daily_limit_by_tier":"cf_earn_daily_limit"}` | **未接调用方** ❌ |
+| F16 | 每日健康打卡 | fixed | 10 | `{"daily_limit_by_tier":"cf_earn_daily_limit"}` | DailyCheckinService:80 ✅ |
+
+> ⚠️ F14/F15 规则已建(DatabaseInitializer 种子),但**无调用方**——打卡服务只发 points 积分,未接 CF。F05/F07/F08 已有调用。
+
+### 1.2 等级差异化现状
+
+- `cf_earn_daily_limit` 表:FREE=1 / BASIC=3 / FAMILY=10 / LIFETIME=10 / PREMIUM=20(仅 F14/F15/F16/F22 命中)
+- `CfBalanceServiceImpl.computeAmount()`:**不读等级**——无论 FREE 还是 PREMIUM,单次金额相同
+- 等级只影响「每日限次」,不影响「单次金额」
+
+### 1.3 F11 里程碑硬编码(B1+B2)
+
+- `InviteMilestoneService.MILESTONE_TIERS`(L40-43)静态硬编码:INVITE_3={3,200,20} / INVITE_5={5,500,50} / INVITE_10={10,1000,100}
+- `AdminMilestoneController.MILESTONE_CONFIGS` 同值静态副本
+- `config()` 返回静态值,`updateConfig()` 只改 status=PENDING 行(DB 无种子则无效)
+- **真配置源** = 静态字段,管理端改不生效
+
+### 1.4 points 积分写死(P1–P3)
+
+- HealthCheckinService:92 `int points=5`(读 sys_config health_checkin_points 覆盖,默认 5 写死)
+- FinanceCheckinService:70 `int points=5`(读 finance_checkin_points 覆盖,默认 5 写死)
+- MicroActionService:237 `int points=5` + **误读 `health_checkin_points` 键**(应为 micro 专用键)
+
+---
+
+## 二、改造任务清单(T1–T6)
+
+### T1: computeAmount 支持 levels 等级金额通道(P0)
+
+**文件**:`cfc-backend/src/main/java/com/etotem/cfc/service/impl/CfBalanceServiceImpl.java`
+
+**改动**:
+1. `computeAmount()` 末尾,在 `base = base * resolveMultiplier(params)` 之前,增加等级金额覆盖:
+   ```java
+   // 等级金额通道:params_json.levels[levelCode] 优先,缺省回落 rule.amount
+   double levelAmount = resolveLevelAmount(rule, levelCode, params);
+   if (levelAmount > 0) {
+       base = levelAmount;
+   }
+   base = base * resolveMultiplier(params);
+   ```
+2. 新增私有方法:
+   ```java
+   private double resolveLevelAmount(CfEarnRule rule, String levelCode, Map<String, Object> params) {
+       if (params == null || levelCode == null) return 0;
+       Object levels = params.get("levels");
+       if (!(levels instanceof Map)) return 0;
+       Map<String, Object> levelsMap = (Map<String, Object>) levels;
+       Object val = levelsMap.get(levelCode);
+       if (val == null) val = levelsMap.get("NONE"); // fallback to NONE
+       if (val == null) return 0;
+       try { return Double.parseDouble(String.valueOf(val)); } catch (Exception e) { return 0; }
+   }
+   ```
+3. `resolveLevelCode()` 不变(已实现,读 membership_levels 动态档位)
+
+**验收**:
+- 单元测试:新建 `CfBalanceServiceImplLevelAmountTest`,断言:
+  - params.levels.FAMILY=10 且 levelCode=FAMILY → amount=10
+  - params.levels 无 FAMILY → 回落 rule.amount
+  - params.levels.NONE=2 且 levelCode=FREE → amount=2
+- 编译 PASS,无回归
+
+---
+
+### T2: CfRuleConfig.vue 加等级矩阵编辑(P0)
+
+**文件**:`cfc-web/src/views/admin/CfRuleConfig.vue`
+
+**改动**:
+1. 规则编辑弹窗增加「按会员等级配置单次 CF 值」区块(当 `ruleType === 'fixed'` 时显示)
+2. 表格行 = 当前有效等级(读 membership_levels 表或硬编码枚举 FAMILY/PREMIUM/NONE)
+3. 每行:等级名称 + 输入框(数字,默认空=回落 rule.amount)
+4. 保存时序列化入 `paramsJson.levels`(JSON.stringify)
+5. 编辑时反序列化展示矩阵
+
+**等级列表来源**:
+- 优先读 `/api/admin/membership/levels`(若存在)
+- 兜底枚举:`[{code:"NONE",label:"非会员"},{code:"FAMILY",label:"FAMILY"},{code:"PREMIUM",label:"PREMIUM"}]`
+
+**验收**:
+- 新建规则 Fxx → 保存后 paramsJson 含 `{"levels":{"NONE":2,"FAMILY":4,"PREMIUM":10}}`
+- 编辑已有规则 → 矩阵回显
+- 保存时 JSON 合法(防注入)
+
+---
+
+### T3: 种子补 levels 默认值(P1)
+
+**文件**:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`
+
+**改动**:迁移419(新增):
+```sql
+-- 为打卡类规则补 levels 默认值(幂等 UPDATE,仅改 params_json 列)
+UPDATE cf_earn_rule SET params_json = JSON_MERGEREPLACE(
+    COALESCE(params_json, '{}'),
+    '{"levels":{"NONE":2,"FAMILY":4,"PREMIUM":10}}'
+) WHERE code IN ('F14','F15','F16');
+UPDATE cf_earn_rule SET params_json = JSON_MERGEREPLACE(
+    COALESCE(params_json, '{}'),
+    '{"levels":{"NONE":1,"FAMILY":2,"PREMIUM":5}}'
+) WHERE code IN ('F05','F07','F08');
+```
+
+> 矩阵数值(廖总拍板 FAMILY=非会员×2 / PREMIUM=非会员×5):
+> - F14/F15(喝水/饮食):NONE=2, FAMILY=4, PREMIUM=10
+> - F16(每日健康):NONE=5, FAMILY=10, PREMIUM=25
+> - F05/F07/F08(活动/文章/AI答题):NONE=1, FAMILY=2, PREMIUM=5
+
+**验收**:
+- 新库建表后 seed 即含 levels
+- 存量库执行迁移419 后,F14/F15/F16/F05/F07/F08 params_json 含 levels
+
+---
+
+### T4: F11 里程碑迁移到 DB(B1+B2)(P0)
+
+**现状**:
+- `invite_milestone` 表结构:id, user_id, milestone(ENUM 'INVITE_3/5/10'), reward_points, reward_energy, status(PENDING/CLAIMED), achieved_at, claimed_at, created_at
+- 表已建,但**无种子数据**(DatabaseInitializer 未 insert INVITE_3/5/10 基准行)
+- `InviteMilestoneService.checkMilestones()` 每达成时 INSERT 用户实例(正确)
+- 但初始 reward_points/reward_energy 来自静态 TIERS,不来自 DB
+
+**改动**:
+
+#### T4-A: DatabaseInitializer 迁移420(种子)
+```sql
+-- invite_milestone_template 表(模板,不是 invite_milestone 实例表)
+CREATE TABLE IF NOT EXISTS invite_milestone_template (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    milestone VARCHAR(20) NOT NULL UNIQUE,  -- INVITE_3/5/10
+    target_count INT NOT NULL,
+    reward_points INT NOT NULL,
+    reward_energy INT NOT NULL,
+    enabled TINYINT(1) NOT NULL DEFAULT 1,
+    sort_order INT NOT NULL DEFAULT 0,
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    INDEX idx_milestone (milestone)
+) COMMENT='邀请里程碑模板(管理端可配)';
+
+-- 种子
+INSERT IGNORE INTO invite_milestone_template (milestone, target_count, reward_points, reward_energy, sort_order) VALUES
+('INVITE_3', 3, 200, 20, 1),
+('INVITE_5', 5, 500, 50, 2),
+('INVITE_10', 10, 1000, 100, 3);
+```
+
+#### T4-B: InviteMilestoneService 改读 DB
+- 删除 `MILESTONE_TIERS` 静态字段
+- `checkMilestones()` 改用 `inviteMilestoneTemplateMapper.selectAll()` 遍历模板
+- 每模板:count=invite_count,if invite_count >= template.target_count → INSERT 实例(现有逻辑)
+
+#### T4-C: AdminMilestoneController 改读 DB
+- `config()` 改查 `invite_milestone_template` 表
+- `updateConfig()` 改 UPDATE `invite_milestone_template`(status 条件去掉,直接 by milestone)
+- 新增 `/api/admin/milestone/template` GET 列表,`/api/admin/milestone/template/save` POST 保存
+
+#### T4-D: cfc-web PromotionManagement.vue 对接新接口
+- 里程碑编辑弹窗提交改走 `/api/admin/milestone/template/save`
+
+**验收**:
+- 新库建表后 template 表有 3 行种子
+- 存量库执行迁移420 后补 template 表
+- 管理端修改 reward_points 落库,重新达成里程碑时按新值发放
+- 原有 invite_milestone 实例不受影响(存量数据保留)
+
+---
+
+### T5: points 写死修复(P1–P3)(P2)
+
+**T5-A: HealthCheckinService:92**
+- `int points = 5` 改 `int points = 5`(保留,但读 sys_config 后覆盖)
+- 无需大改,default 作为兜底合理
+
+**T5-B: FinanceCheckinService:70**
+- 同上,保留
+
+**T5-C: MicroActionService:237 误读键修正**
+- `sysConfigService.getValue("health_checkin_points")` 改为 `sysConfigService.getValue("micro_checkin_points")`
+- DatabaseInitializer 迁移421 补种子:`insertSysConfigSeed("micro_checkin_points", "5", "微小行动打卡积分")`
+
+**验收**:
+- MicroAction 打卡积分正确读 micro_checkin_points(不再是 health_checkin_points)
+
+---
+
+### T6: 打卡 CF 调用方补齐(P1)
+
+**现状**:F14/F15 规则已建,但无调用方。
+
+**改动**:
+
+#### T6-A: HealthCheckinService 接 F14(喝水打卡)
+- 位置:打卡成功后末尾追加 CF 发放(try/catch 只 warn)
+- 注意:需确认「喝水打卡」与「健康打卡」的区分——当前 HealthCheckinService 可能包含喝水记录
+- 查找 WaterRecordService / HealthWaterService,确认 F14 调用点
+
+#### T6-B: FinanceCheckinService 接 F15(饮食打卡)
+- 同理,打卡成功后末尾追加 CF 发放
+
+#### T6-C: MicroActionService 接 F25(新增打卡 CF 规则)
+- 本方案新增 F25 = 微小行动打卡(category=behavior, fixed, ref_type=checkin_micro)
+- params_json: `{"levels":{"NONE":1,"FAMILY":2,"PREMIUM":5}}`
+
+#### T6-D: EmotionCheckinService 接 F26(情绪/冥想打卡)
+- 新增 F26 = 情绪打卡(category=behavior, fixed, ref_type=checkin_emotion)
+- params_json: `{"levels":{"NONE":1,"FAMILY":2,"PREMIUM":5}}`
+
+#### T6-E: 新增睡眠打卡/专注打卡规则 F27/F28
+- 若代码中有 SleepCheckinService / FocusCheckinService,同样接入
+- 若无,暂不接(待需求明确)
+
+**验收**:
+- 各打卡服务打卡成功后调用 CfGrowthPathwayEarnService 对应方法
+- CF 发放失败不回滚打卡主流程(try/catch warn)
+- cf_balance_log 可查 CF 流水(ref_type=checkin_health/finance/micro/emotion)
+
+---
+
+## 三、实施路径
+
+### 阶段 1:核心通道(T1+T2+T3)
+- T1 computeAmount 等级金额通道(后端核心)
+- T2 管理端等级矩阵编辑(前端)
+- T3 种子补 levels 默认值(迁移)
+- 完成后:已配置 levels 的规则按等级差异化发放
+
+### 阶段 2:F11 里程碑迁移(T4)
+- T4-A/B/C/D 一并完成
+- 完成后:F11 奖励可后台配置,不再写死
+
+### 阶段 3:打卡调用方补齐(T5+T6)
+- T5 points 写死修复(低优先)
+- T6 打卡 CF 调用方补齐(含 F25/F26 新增规则)
+- 完成后:全打卡内容接入 CF,等级差异化生效
+
+---
+
+## 四、验收标准(汇总)
+
+| 编号 | 验收项 | 方式 |
+|---|---|---|
+| AC-01 | computeAmount 按 levels[levelCode] 覆盖 base | 单测 |
+| AC-02 | CfRuleConfig.vue 规则编辑含等级矩阵,保存合法 JSON | 手动 |
+| AC-03 | 存量库迁移419 后 F14/F15/F16/F05/F07/F08 含 levels | SQL 查询 |
+| AC-04 | invite_milestone_template 表种子 3 行 | SQL 查询 |
+| AC-05 | 管理端修改 F11 里程碑 reward_points 落库 | 手动 |
+| AC-06 | MicroAction 打卡积分读 micro_checkin_points | 日志/数据库 |
+| AC-07 | HealthCheckin/FinanceCheckin 打卡成功后 CF 发放 | 测试库实测 |
+| AC-08 | 等级差异化:同打卡,FAMILY 得 2×,PREMIUM 得 5× | 测试库实测 |
+| AC-09 | 编译三端 PASS | mvn compile + npm build |
+| AC-10 | 无 P0 回归(原有打卡/CF 功能正常) | 冒烟测试 |
+
+---
+
+## 五、风险与护栏
+
+1. **computeAmount 改动风险**:所有 cf_earn_rule 都走此方法,改错影响全局
+   - 护栏:单测全覆盖 + 灰度验证(新规则先试)
+2. **levels 缺失兜底**:旧规则 params_json 无 levels 字段 → resolveLevelAmount 返回 0 → 回落 rule.amount
+   - 护栏:T3 种子补全 + 迁移419 幂等 UPDATE
+3. **F11 迁移兼容性**:存量 invite_milestone 实例(用户已达成但未领取)不受影响
+   - 护栏:仅改模板表,实例表不变
+4. **打卡调用方插入**:打卡服务已稳定运行,追加 CF 发放需 try/catch 隔离
+   - 护栏:CF 失败只 warn,不回滚打卡主流程(与 FoodRecommend 同纪律)
+
+---
+
+## 六、派发建议
+
+- **T1+T2+T3**:合并发派(后端核心 + 前端管理端 + 种子),lan-204 或 lan-248
+- **T4**:独立派发(F11 迁移),lan-204
+- **T5+T6**:合并派发(打卡补齐),lan-189
+- 每任务完成后独立核验(编译 + 单测 + 运行态)