用户完成动作
↓
业务 Service (TaskService / GameRecordService / ...)
↓
调用 energyService.awardEnergy(childId, sourceType, sourceId, amount, desc, expireDays)
↓
awardEnergy() 内部:
1. 查 energy_source_config → sourceType + sourceId 精确匹配
2. 有配置 → 按配置维度比例分配
3. 无配置 → 全部归入 action(行) 维度
↓
4. 日上限检查
5. 按比例分配到各维度
6. 更新余额 + 写流水
// 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("能量发放失败(不影响积分)");
}
awardEnergy() 内部查 energy_source_config:
SELECT * FROM energy_source_config
WHERE source_type = 'task' AND source_id = 123;
energy_source_config 表中只有 source_type='product' 的5条记录,没有 task 的配置。
所以 resolveDimensionRatios() 返回空 → 触发 Fallback:
// 默认全部归入 action(行) 维度,100%
return Collections.singletonMap(action.getId(), 10000);
结果:所有来源的能量都进入了 action 维度。
每条记录定义一个"行为"的完整能量规则:
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. 分配到各维度 → 更新余额 + 写流水
// TaskService.java — 改造后
// 不再直接调用 awardEnergy,而是调用 awardByBehavior
// 构造上下文:包含所有可能需要的参数
Map<String, Object> 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);
// EnergyService.java — 新增方法
public void awardByBehavior(Long childId, String behaviorCode, Map<String, Object> context) {
// 1. 查配置
EnergyBehaviorConfig config = behaviorConfigMapper.selectOne(
new LambdaQueryWrapper<EnergyBehaviorConfig>()
.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<Long, Integer> 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<Long, Integer> allocations = calculateAllocations(amount, dimRatios);
// ... 更新各维度余额和流水 ...
}
| 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", ...) |
| behavior_code | 触发位置 | 实现方式 |
|---|---|---|
invite_friend |
CommissionService.bindReferral() |
绑定推荐人时触发 |
invite_family |
FamilyInviteService.acceptByCode() |
家庭成员接受邀请时触发 |
report_upload |
HealthReportController.uploadAndParse() |
上传体检报告时触发 |
plan_complete |
GrowthPlanService.reviewPlan() |
完成成长计划时触发 |
wisdom_report |
DanAssessmentController.uploadReport() |
上传智测评报告时触发 |
不改业务逻辑,只在 awardEnergy() 方法内部增加配置读取逻辑:
// EnergyService.awardEnergy() — 改造
public Map<String, Integer> 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 页面,让管理员可以: