Forráskód Böngészése

feat(task): 新人引导复用任务模板+落tasks表(source_type=onboarding,顺序链前置解锁)

E2E Test Bot 2 hete
szülő
commit
c503681314

+ 9 - 22
cfc-backend/src/main/java/com/etotem/cfc/service/OnboardingService.java

@@ -26,6 +26,9 @@ public class OnboardingService {
     @Resource
     private FamilyMemberMapper familyMemberMapper;
 
+    @Resource
+    private TaskService taskService;
+
     private static final Map<String, int[]> REWARDS = new LinkedHashMap<>();
     static {
         REWARDS.put("REGISTER", new int[]{100, 10});
@@ -39,36 +42,20 @@ public class OnboardingService {
 
     /**
      * Initialize 6 PENDING onboarding tasks for a new user
+     * 统一任务系统:委托 TaskService(生成 tasks 表 onboarding 任务)
      */
+    @Deprecated
     public void initForUser(Long userId) {
-        for (Map.Entry<String, int[]> entry : REWARDS.entrySet()) {
-            OnboardingTask task = new OnboardingTask();
-            task.setUserId(userId);
-            task.setTaskType(entry.getKey());
-            task.setRewardPoints(entry.getValue()[0]);
-            task.setRewardEnergy(entry.getValue()[1]);
-            task.setStatus("PENDING");
-            task.setCreatedAt(new Date());
-            onboardingTaskMapper.insert(task);
-        }
+        taskService.initOnboardingTasks(userId);
     }
 
     /**
      * Mark a task as COMPLETED if it is still PENDING
+     * 统一任务系统:委托 TaskService(按 title 匹配 onboarding 任务并完成)
      */
+    @Deprecated
     public void completeTask(Long userId, String taskType) {
-        OnboardingTask task = onboardingTaskMapper.selectOne(
-                new LambdaQueryWrapper<OnboardingTask>()
-                        .eq(OnboardingTask::getUserId, userId)
-                        .eq(OnboardingTask::getTaskType, taskType)
-                        .eq(OnboardingTask::getStatus, "PENDING")
-                        .last("LIMIT 1")
-        );
-        if (task != null) {
-            task.setStatus("COMPLETED");
-            task.setCompletedAt(new Date());
-            onboardingTaskMapper.updateById(task);
-        }
+        taskService.completeOnboardingTask(userId, taskType);
     }
 
     /**

+ 96 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java

@@ -54,9 +54,6 @@ public class TaskService implements TaskServiceInterface {
     @Resource
     private EnergyService energyService;
 
-    @Resource
-    private OnboardingService onboardingService;
-
     @Resource
     private FamilyRelationshipScoreMapper familyRelationshipScoreMapper;
 
@@ -75,6 +72,9 @@ public class TaskService implements TaskServiceInterface {
     @Resource
     private SurveyTemplateMapper surveyTemplateMapper;
 
+    @Resource
+    private TaskTemplateMapper taskTemplateMapper;
+
     private static final int EARLY_BONUS = 1; // 提前完成奖励
     private static final int PENALTY_MAX_DAILY = 5; // 每日最多扣分
     private static final int LATE_GRACE_MINUTES = 10; // 10分钟内不算迟到
@@ -772,7 +772,7 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
         Map<String, Object> result = new HashMap<>();
         result.put("pointsEarned", pointsEarned);
 
-        try { FamilyMember child = familyMemberMapper.selectById(task.getFamilyMemberId()); if (child != null) { onboardingService.completeTask(child.getUserId(), "FIRST_TASK"); } } catch (Exception e) { /* onboarding trigger failure should not block task completion */ }
+        try { FamilyMember child = familyMemberMapper.selectById(task.getFamilyMemberId()); if (child != null) { this.completeOnboardingTask(child.getUserId(), "FIRST_TASK"); } } catch (Exception e) { /* onboarding trigger failure should not block task completion */ }
     result.put("newBalance", user.getTotalPoints());
     result.put("needReview", task.getNeedReview() == 1);
 
@@ -1709,4 +1709,96 @@ return 0; // 达到每日扣分上限
         }
         return result;
     }
+
+    // ==================== 统一任务系统:新人引导迁移(原 OnboardingService) ====================
+
+    /**
+     * 统一任务系统:新用户注册初始化新人引导任务
+     * 按 admin_task_templates 中 onboarding_sequence 有值的模板生成 tasks(source_type=onboarding)
+     * 顺序链:sequence 递增,前序未完成则后序 locked(复用 prerequisite_task_id + locked 状态)
+     */
+    @Transactional
+    public void initOnboardingTasks(Long userId) {
+        User user = userService.getUserInfo(userId);
+        if (user == null || user.getFamilyId() == null) {
+            return;
+        }
+
+        // 幂等:该用户已有 onboarding 任务则跳过
+        Long existing = taskMapper.selectCount(new LambdaQueryWrapper<Task>()
+                .eq(Task::getCreatorId, userId)
+                .eq(Task::getSourceType, "onboarding"));
+        if (existing != null && existing > 0) {
+            return;
+        }
+
+        List<TaskTemplate> templates = taskTemplateMapper.selectList(
+                new LambdaQueryWrapper<TaskTemplate>()
+                        .eq(TaskTemplate::getIsActive, 1)
+                        .isNotNull(TaskTemplate::getOnboardingSequence)
+                        .orderByAsc(TaskTemplate::getOnboardingSequence));
+        if (templates.isEmpty()) {
+            return;
+        }
+
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+
+        Date now = new Date();
+        Long prevTaskId = null;
+        for (TaskTemplate tpl : templates) {
+            Task task = new Task();
+            task.setFamilyId(user.getFamilyId());
+            task.setCreatorId(userId);
+            task.setExecutorType("child");
+            if (member != null) {
+                task.setExecutorId(member.getId());
+                task.setFamilyMemberId(member.getId());
+                task.setChildId(member.getId());
+            }
+            task.setTitle(tpl.getTitle());
+            task.setDescription(tpl.getDescription());
+            task.setPoints(tpl.getPoints() != null ? tpl.getPoints() : 2);
+            task.setCategory(tpl.getCategory());
+            task.setStatus(prevTaskId == null ? "pending" : "locked"); // 首任务可直接做,后续需解锁
+            task.setNeedReview(tpl.getNeedReview() != null ? tpl.getNeedReview() : 0);
+            task.setIsTemplate(0);
+            task.setSourceType("onboarding");
+            task.setTemplateId(tpl.getId());
+            task.setPrerequisiteTaskId(prevTaskId); // 顺序链
+            task.setMemberOnly(0);
+            task.setDeadline(new Date(now.getTime() + 30L * 24 * 3600 * 1000)); // 30天有效期
+            task.setCreatedAt(now);
+            task.setUpdatedAt(now);
+            taskMapper.insert(task);
+            prevTaskId = task.getId();
+        }
+        log.info("新用户{}初始化{}条新人引导任务", userId, templates.size());
+    }
+
+    /**
+     * 统一任务系统:完成指定新人引导任务(原 OnboardingService.completeTask)
+     * 按 source_type=onboarding + title 精确匹配(title 约定 = 原 taskType,如 FIRST_TASK)
+     */
+    public void completeOnboardingTask(Long userId, String templateTitle) {
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+        if (member == null) {
+            return;
+        }
+        Task task = taskMapper.selectOne(new LambdaQueryWrapper<Task>()
+                .eq(Task::getFamilyMemberId, member.getId())
+                .eq(Task::getSourceType, "onboarding")
+                .eq(Task::getTitle, templateTitle)
+                .eq(Task::getStatus, "pending")
+                .last("LIMIT 1"));
+        if (task != null) {
+            awardTaskCompletionRewards(task, member.getId(), new Date());
+            unlockPrerequisiteTasks(task.getId());
+        }
+    }
 }