# 能量行为配置与系统功能的关联机制 ## 当前实现:硬编码调用 ### 调用链路 ``` 用户完成动作 ↓ 业务 Service (TaskService / GameRecordService / ...) ↓ 调用 energyService.awardEnergy(childId, sourceType, sourceId, amount, desc, expireDays) ↓ awardEnergy() 内部: 1. 查 energy_source_config → sourceType + sourceId 精确匹配 2. 有配置 → 按配置维度比例分配 3. 无配置 → 全部归入 action(行) 维度 ↓ 4. 日上限检查 5. 按比例分配到各维度 6. 更新余额 + 写流水 ``` ### 具体例子:完成任务 ```java // TaskService.java 第414-424行 — 完成任务时触发 try { int energyAmount = Math.abs(pointsEarned); // 能量值 = 任务积分 if (energyAmount > 0) { energyService.awardEnergy( childId, // 孩子ID "task", // sourceType = "task" taskId, // sourceId = 具体任务ID energyAmount, // 能量值 = 积分 "完成任务: " + title, // 描述 null // 不过期 ); } } catch (Exception e) { log.warn("能量发放失败(不影响积分)"); } ``` ### 问题:维度分配全部走 Fallback `awardEnergy()` 内部查 `energy_source_config`: ```sql SELECT * FROM energy_source_config WHERE source_type = 'task' AND source_id = 123; ``` `energy_source_config` 表中只有 `source_type='product'` 的5条记录,没有 `task` 的配置。 所以 `resolveDimensionRatios()` 返回空 → 触发 Fallback: ```java // 默认全部归入 action(行) 维度,100% return Collections.singletonMap(action.getId(), 10000); ``` **结果:所有来源的能量都进入了 action 维度。** ## 改造方案:配置驱动 ### 新表:energy_behavior_config 每条记录定义一个"行为"的完整能量规则: ```sql energy_behavior_config ( behavior_code VARCHAR(32) -- 行为编码: task_complete / game_finish / ... behavior_name VARCHAR(50) -- 行为名称: 完成任务 / 完成游戏 / ... default_amount INT -- 默认能量值 amount_source VARCHAR(32) -- 能量值来源: fixed / points / order_amount / config dimension_assign JSON -- 维度分配: [{"dim":"action","ratio":100}] daily_limit INT -- 每日上限 cooldown_seconds INT -- 冷却时间 expire_days INT -- 过期天数 enabled TINYINT -- 是否启用 ) ``` ### 改造后的调用链路 ``` 用户完成动作 ↓ 业务 Service (TaskService) ↓ 调用 energyService.awardByBehavior(childId, "task_complete", context) ↓ awardByBehavior() 内部: 1. 查 energy_behavior_config → behavior_code = "task_complete" 2. 配置不存在或不启用 → 返回,不给能量 3. 配置存在 → 读取 amount_source 和 dimension_assign │ ├─ amount_source = "points" → 从 context 取积分值 ├─ amount_source = "fixed" → 用 default_amount ├─ amount_source = "order_amount" → 从 context 取订单金额 └─ amount_source = "config" → 从关联配置表读取 │ └─ dimension_assign = [{"dim":"action","ratio":50},{"dim":"wisdom","ratio":50}] → 按比例分配到各维度 ↓ 4. 日上限检查 5. 分配到各维度 → 更新余额 + 写流水 ``` ### 具体例子:改造后 ```java // TaskService.java — 改造后 // 不再直接调用 awardEnergy,而是调用 awardByBehavior // 构造上下文:包含所有可能需要的参数 Map context = new HashMap<>(); context.put("childId", childId); context.put("sourceId", taskId); context.put("points", pointsEarned); // 积分值 context.put("taskTitle", task.getTitle()); energyService.awardByBehavior(childId, "task_complete", context); ``` ```java // EnergyService.java — 新增方法 public void awardByBehavior(Long childId, String behaviorCode, Map context) { // 1. 查配置 EnergyBehaviorConfig config = behaviorConfigMapper.selectOne( new LambdaQueryWrapper() .eq(EnergyBehaviorConfig::getBehaviorCode, behaviorCode) .eq(EnergyBehaviorConfig::getEnabled, 1) ); if (config == null) return; // 无配置或未启用,不给能量 // 2. 计算能量值 int amount = 0; switch (config.getAmountSource()) { case "fixed": amount = config.getDefaultAmount(); break; case "points": Integer points = (Integer) context.get("points"); amount = points != null ? Math.abs(points) : config.getDefaultAmount(); break; case "order_amount": Integer orderAmount = (Integer) context.get("orderAmount"); amount = orderAmount != null ? orderAmount : config.getDefaultAmount(); break; } if (amount <= 0) return; // 3. 解析维度分配配置 // dimension_assign = [{"dim":"action","ratio":50},{"dim":"wisdom","ratio":50}] JSONArray dimAssign = JSON.parseArray(config.getDimensionAssign()); Map dimRatios = new LinkedHashMap<>(); for (int i = 0; i < dimAssign.size(); i++) { JSONObject item = dimAssign.getJSONObject(i); String dimCode = item.getString("dim"); int ratio = item.getInt("ratio"); EnergyDimension dim = getDimByCode(dimCode); if (dim != null) { dimRatios.put(dim.getId(), ratio * 100); // 转为10000制 } } if (dimRatios.isEmpty()) return; // 4. 日上限检查 if (isDailyLimitExceeded(childId, dimRatios.keySet(), amount)) { log.warn("日上限已达,跳过: behaviorCode={}, childId={}", behaviorCode, childId); return; } // 5. 按比例分配 + 更新余额 + 写流水 Map allocations = calculateAllocations(amount, dimRatios); // ... 更新各维度余额和流水 ... } ``` ## 所有行为如何关联到系统代码 ### 现有行为(19个) | behavior_code | 触发位置 | 当前代码 | |--------------|---------|---------| | `task_complete` | `TaskService.completeTask()` | 硬编码 `awardEnergy(childId, "task", ...)` | | `game_finish` | `GameRecordService.completeGame()` | 硬编码 `awardEnergy(childId, "game", ...)` | | `activity_checkin` | `ActivityService.checkin()` | 硬编码 `awardEnergy(childId, "activity", ...)` | | `article_read` | `ArticleService.completeArticleRead()` | 硬编码 `awardEnergy(childId, "article", ...)` | | `product_purchase` | `ProductOrderService.handlePayment()` | 硬编码 `awardEnergy(childId, "product", ...)` | | `health_checkin` | `HealthCheckinService.checkin()` | 硬编码 `awardEnergy(childId, "health_checkin", ...)` | | `finance_checkin` | `FinanceCheckinService.checkin()` | 硬编码 `awardEnergy(childId, "checkin", ...)` | | `emotion_checkin` | `EmotionCheckinService.checkin()` | 硬编码 `awardEnergy(childId, "emotion_checkin", ...)` | | `appointment_submit` | `AssessmentAppointmentService.create()` | 硬编码 `awardEnergy(childId, "appointment", ...)` | | `appointment_complete` | `AssessmentAppointmentService.complete()` | 硬编码 `awardEnergy(childId, "appointment", ...)` | | `streak_daily` | `StreakService.checkStreak()` | 硬编码 `awardEnergy(childId, "streak", ...)` | | `streak_milestone` | `StreakService.checkStreak()` | 硬编码同上 | | `growth_task` | `GrowthTaskService.completeTask()` | 硬编码 `awardEnergy(childId, "growth_day", ...)` | | `micro_action` | `MicroActionService.complete()` | 硬编码 `awardEnergy(childId, "micro_action", ...)` | | `onboarding` | `OnboardingService.claim()` | 硬编码 `awardEnergy(childId, "onboarding", ...)` | | `invite_milestone` | `InviteMilestoneService.check()` | 硬编码 `awardEnergy(childId, "invite_milestone", ...)` | | `dimension_sync` | `DimensionEnergySyncService.sync()` | 硬编码 `awardEnergy(childId, "dimension", ...)` | ### 新增行为(5个,尚未实现) | behavior_code | 触发位置 | 实现方式 | |--------------|---------|---------| | `invite_friend` | `CommissionService.bindReferral()` | 绑定推荐人时触发 | | `invite_family` | `FamilyInviteService.acceptByCode()` | 家庭成员接受邀请时触发 | | `report_upload` | `HealthReportController.uploadAndParse()` | 上传体检报告时触发 | | `plan_complete` | `GrowthPlanService.reviewPlan()` | 完成成长计划时触发 | | `wisdom_report` | `DanAssessmentController.uploadReport()` | 上传智测评报告时触发 | ## 改造步骤 ### 第一步:替换现有硬编码调用(最小改动) 不改业务逻辑,只在 `awardEnergy()` 方法内部增加配置读取逻辑: ```java // EnergyService.awardEnergy() — 改造 public Map awardEnergy(Long childId, String sourceType, Long sourceId, ...) { // 新增:先查 behavior_config EnergyBehaviorConfig config = matchBehaviorConfig(sourceType); if (config != null && config.getEnabled()) { // 使用配置的维度分配 return awardByConfig(childId, config, sourceType, sourceId, totalAmount, description, daysToExpire); } // 无配置 → 原有逻辑(查 energy_source_config + Fallback) ... } ``` 这样所有现有调用代码不用改,只需要在 `awardEnergy()` 内部增加一个配置查询点。 ### 第二步:添加配置数据 在 `energy_behavior_config` 表中插入预置数据,每个 `sourceType` 对应一条配置。 ### 第三步:管理后台 实现管理后台的 CRUD 页面,让管理员可以: 1. 启用/禁用某个行为 2. 修改能量值 3. 修改维度分配比例 4. 查看各行为每日发放统计