|
@@ -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
|
|
|
|
|
+- 每任务完成后独立核验(编译 + 单测 + 运行态)
|