Explorar o código

feat(task): onboarding读取侧完整迁移——getProgress读tasks表+claim自动发放+模板种子数据

E2E Test Bot hai 1 mes
pai
achega
fe11118edd

+ 20 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -10473,5 +10473,25 @@ public class DatabaseInitializer implements CommandLineRunner {
             log.warn("迁移297: growth_task迁移失败或已执行: {}", e.getMessage());
         }
         // 说明:growth_task_log 历史进度、onboarding_task 存量数据仅保留在旧表(只读备份),不回填 tasks(历史数据无法1:1映射到新模型)
+
+        // 迁移299: 插入新人引导任务模板种子数据(统一任务系统:onboarding 复用 admin_task_templates)
+        // title = 原 taskType(REGISTER/COMPLETE_PROFILE/...),onboarding_sequence 定义顺序链
+        try {
+            Integer onboardCount = jdbcTemplate.queryForObject(
+                    "SELECT COUNT(*) FROM admin_task_templates WHERE onboarding_sequence IS NOT NULL", Integer.class);
+            if (onboardCount == null || onboardCount == 0) {
+                jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, is_active, task_type, frequency, onboarding_sequence, created_at, updated_at) VALUES " +
+                        "('REGISTER', '完成注册', 100, '成长', 0, 1, 'onetime', 'none', 1, NOW(), NOW()), " +
+                        "('COMPLETE_PROFILE', '完善个人资料', 50, '成长', 0, 1, 'onetime', 'none', 2, NOW(), NOW()), " +
+                        "('ADD_CHILD', '添加家庭成员', 100, '成长', 0, 1, 'onetime', 'none', 3, NOW(), NOW()), " +
+                        "('FIRST_CHECKIN', '完成首次健康打卡', 30, '成长', 0, 1, 'onetime', 'none', 4, NOW(), NOW()), " +
+                        "('FIRST_TASK', '完成首次任务', 50, '成长', 0, 1, 'onetime', 'none', 5, NOW(), NOW()), " +
+                        "('BIND_REFERRAL', '绑定推荐人', 50, '成长', 0, 1, 'onetime', 'none', 6, NOW(), NOW()), " +
+                        "('COMPLETE_SELF_CHECK', '完成一次五维自检', 60, '成长', 0, 1, 'onetime', 'none', 7, NOW(), NOW())");
+                log.info("迁移299: 已插入7条新人引导任务模板种子数据");
+            }
+        } catch (Exception e) {
+            log.warn("迁移299: 插入新人引导任务模板失败或已存在: {}", e.getMessage());
+        }
     }
 }

+ 1 - 2
cfc-backend/src/main/java/com/etotem/cfc/controller/OnboardingController.java

@@ -1,7 +1,6 @@
 package com.etotem.cfc.controller;
 
 import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.OnboardingTask;
 import com.etotem.cfc.service.OnboardingService;
 import org.springframework.web.bind.annotation.*;
 
@@ -17,7 +16,7 @@ public class OnboardingController {
     private OnboardingService onboardingService;
 
     @PostMapping("/progress")
-    public Result<List<OnboardingTask>> progress(@RequestAttribute("userId") Long userId) {
+    public Result<List<Map<String, Object>>> progress(@RequestAttribute("userId") Long userId) {
         return Result.success(onboardingService.getProgress(userId));
     }
 

+ 12 - 73
cfc-backend/src/main/java/com/etotem/cfc/service/OnboardingService.java

@@ -1,47 +1,22 @@
 package com.etotem.cfc.service;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.FamilyMember;
-import com.etotem.cfc.entity.OnboardingTask;
-import com.etotem.cfc.mapper.FamilyMemberMapper;
-import com.etotem.cfc.mapper.OnboardingTaskMapper;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 import org.springframework.context.annotation.Lazy;
 
 import javax.annotation.Resource;
-import java.util.*;
+import java.util.List;
+import java.util.Map;
 
+/**
+ * 新人引导服务(统一任务系统:已迁移至 TaskService,本类保留为兼容委托层)
+ */
 @Service
 public class OnboardingService {
 
-    @Resource
-    private OnboardingTaskMapper onboardingTaskMapper;
-
-    @Resource
-    private PointsService pointsService;
-
-    @Resource
-    private EnergyService energyService;
-
-    @Resource
-    private FamilyMemberMapper familyMemberMapper;
-
     @Resource
     @Lazy
     private TaskService taskService;
 
-    private static final Map<String, int[]> REWARDS = new LinkedHashMap<>();
-    static {
-        REWARDS.put("REGISTER", new int[]{100, 10});
-        REWARDS.put("COMPLETE_PROFILE", new int[]{50, 5});
-        REWARDS.put("ADD_CHILD", new int[]{100, 10});
-        REWARDS.put("FIRST_CHECKIN", new int[]{30, 5});
-        REWARDS.put("FIRST_TASK", new int[]{50, 5});
-        REWARDS.put("BIND_REFERRAL", new int[]{50, 10});
-        REWARDS.put("COMPLETE_SELF_CHECK", new int[]{60, 10});
-    }
-
     /**
      * Initialize 6 PENDING onboarding tasks for a new user
      * 统一任务系统:委托 TaskService(生成 tasks 表 onboarding 任务)
@@ -62,55 +37,19 @@ public class OnboardingService {
 
     /**
      * Claim reward for a COMPLETED task: set CLAIMED, award points + energy
+     * 统一任务系统:奖励已自动发放,无 claimed 概念
      */
-    @Transactional
+    @Deprecated
     public String claimReward(Long userId, String taskType) {
-        OnboardingTask task = onboardingTaskMapper.selectOne(
-                new LambdaQueryWrapper<OnboardingTask>()
-                        .eq(OnboardingTask::getUserId, userId)
-                        .eq(OnboardingTask::getTaskType, taskType)
-                        .eq(OnboardingTask::getStatus, "COMPLETED")
-                        .last("LIMIT 1")
-        );
-        if (task == null) {
-            return "任务未完成或已领取";
-        }
-
-        task.setStatus("CLAIMED");
-        onboardingTaskMapper.updateById(task);
-
-        // Find child by userId to award points and energy
-        FamilyMember child = familyMemberMapper.selectOne(
-                new LambdaQueryWrapper<FamilyMember>()
-                        .eq(FamilyMember::getUserId, userId)
-                        .last("LIMIT 1")
-        );
-        if (child != null) {
-            try {
-                pointsService.awardCfPoints(child.getId(), task.getRewardPoints(),
-                        "新手引导奖励: " + taskType);
-            } catch (Exception e) {
-                // Points award failure should not block claim
-            }
-            try {
-                energyService.awardEnergy(child.getId(), "onboarding", 0L,
-                        task.getRewardEnergy(), "新手引导奖励: " + taskType, 30);
-            } catch (Exception e) {
-                // Energy award failure should not block claim
-            }
-        }
-
-        return "奖励已发放";
+        return "奖励已自动发放";
     }
 
     /**
      * Get all onboarding tasks for a user
+     * 统一任务系统:委托 TaskService(读 tasks 表 source_type=onboarding)
      */
-    public List<OnboardingTask> getProgress(Long userId) {
-        return onboardingTaskMapper.selectList(
-                new LambdaQueryWrapper<OnboardingTask>()
-                        .eq(OnboardingTask::getUserId, userId)
-                        .orderByAsc(OnboardingTask::getTaskType)
-        );
+    @Deprecated
+    public List<Map<String, Object>> getProgress(Long userId) {
+        return taskService.getOnboardingTasks(userId);
     }
 }

+ 29 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java

@@ -1837,4 +1837,33 @@ return 0; // 达到每日扣分上限
             unlockPrerequisiteTasks(task.getId());
         }
     }
+
+    /**
+     * 统一任务系统:查询用户的新人引导任务列表(原 OnboardingService.getProgress)
+     * 返回前端兼容结构:taskType=title、status(completed→CLAIMED,其余→PENDING)、rewardPoints、rewardEnergy
+     */
+    public List<Map<String, Object>> getOnboardingTasks(Long userId) {
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+        List<Map<String, Object>> result = new ArrayList<>();
+        if (member == null) {
+            return result;
+        }
+        List<Task> tasks = taskMapper.selectList(new LambdaQueryWrapper<Task>()
+                .eq(Task::getFamilyMemberId, member.getId())
+                .eq(Task::getSourceType, "onboarding")
+                .orderByAsc(Task::getId));
+        for (Task task : tasks) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("taskType", task.getTitle());
+            item.put("rewardPoints", task.getPoints() != null ? task.getPoints() : 0);
+            item.put("rewardEnergy", task.getPoints() != null ? task.getPoints() : 0); // 能量≈积分(与 awardTaskCompletionRewards 一致)
+            // 状态映射:completed→CLAIMED(奖励已自动发放),其余→PENDING
+            item.put("status", "completed".equals(task.getStatus()) ? "CLAIMED" : "PENDING");
+            result.add(item);
+        }
+        return result;
+    }
 }

+ 1 - 8
cfc-frontend/pages/profile-extra/onboarding.vue

@@ -23,15 +23,8 @@
           </view>
         </view>
         <view class="task-right">
-          <text class="task-status" v-if="item.status === 'CLAIMED'" :class="'status-claimed'">已领取</text>
-          <text class="task-status" v-else-if="item.status === 'COMPLETED'" :class="'status-completed'">已完成</text>
+          <text class="task-status" v-if="item.status === 'CLAIMED' || item.status === 'COMPLETED'" :class="'status-claimed'">已领取</text>
           <text class="task-status" v-else :class="'status-pending'">待完成</text>
-          <button
-            v-if="item.status === 'COMPLETED'"
-            class="claim-btn"
-            @click="claimReward(item)"
-            :disabled="item.claiming"
-          >领取</button>
         </view>
       </view>
     </view>