Просмотр исходного кода

feat(task): GrowthTask DAILY/AI任务迁入TaskService(tasks表统一执行),旧Service委托+废弃

E2E Test Bot 2 недель назад
Родитель
Сommit
3eca870bdb

+ 13 - 16
cfc-backend/src/main/java/com/etotem/cfc/controller/GrowthTaskController.java

@@ -20,6 +20,9 @@ public class GrowthTaskController {
  @Resource
  @Resource
  private GrowthTaskMapper growthTaskMapper;
  private GrowthTaskMapper growthTaskMapper;
 
 
+ @Resource
+ private com.etotem.cfc.mapper.TaskMapper taskMapper;
+
     @PostMapping("/list")
     @PostMapping("/list")
     public Result<List<Map<String, Object>>> list(@RequestAttribute("userId") Long userId,
     public Result<List<Map<String, Object>>> list(@RequestAttribute("userId") Long userId,
                                                    @RequestBody Map<String, String> params) {
                                                    @RequestBody Map<String, String> params) {
@@ -29,15 +32,8 @@ return Result.success(growthTaskService.getTaskList(userId, params));
  @PostMapping("/claim")
  @PostMapping("/claim")
  public Result<String> claim(@RequestAttribute("userId") Long userId,
  public Result<String> claim(@RequestAttribute("userId") Long userId,
      @RequestBody Map<String, Long> params) {
      @RequestBody Map<String, Long> params) {
-   Long taskLogId = params.get("taskLogId");
-   if (taskLogId == null) {
-     return Result.error("taskLogId不能为空");
-   }
-   String msg = growthTaskService.claimReward(userId, taskLogId);
-   if ("领取成功".equals(msg)) {
-     return Result.success(msg);
-   }
-   return Result.error(msg);
+   // 统一任务系统:奖励自动发放,无 claimed 概念
+   return Result.success("奖励已自动发放");
  }
  }
 
 
  @PostMapping("/accept-dynamic")
  @PostMapping("/accept-dynamic")
@@ -48,16 +44,17 @@ return Result.success(growthTaskService.getTaskList(userId, params));
      return Result.error("taskId不能为空");
      return Result.error("taskId不能为空");
    }
    }
    Long taskId = ((Number) taskIdObj).longValue();
    Long taskId = ((Number) taskIdObj).longValue();
-   GrowthTask growthTask = growthTaskMapper.selectById(taskId);
-   if (growthTask == null) {
+   // 统一任务系统:AI 任务已迁入 tasks 表(source_type=ai)
+   com.etotem.cfc.entity.Task task = taskMapper.selectById(taskId);
+   if (task == null) {
      return Result.error("任务不存在");
      return Result.error("任务不存在");
    }
    }
    Map<String, Object> taskInfo = new java.util.HashMap<>();
    Map<String, Object> taskInfo = new java.util.HashMap<>();
-   taskInfo.put("id", growthTask.getId());
-   taskInfo.put("title", growthTask.getTitle());
-   taskInfo.put("description", growthTask.getDescription());
-   taskInfo.put("dimension", growthTask.getDimension());
-   taskInfo.put("rewardPoints", growthTask.getRewardPoints());
+   taskInfo.put("id", task.getId());
+   taskInfo.put("title", task.getTitle());
+   taskInfo.put("description", task.getDescription());
+   taskInfo.put("dimension", task.getDimensionCode());
+   taskInfo.put("rewardPoints", task.getPoints());
    return Result.success(taskInfo);
    return Result.success(taskInfo);
  }
  }
  }
  }

+ 26 - 132
cfc-backend/src/main/java/com/etotem/cfc/service/GrowthTaskService.java

@@ -37,138 +37,32 @@ public class GrowthTaskService {
     @Resource
     @Resource
     private EnergyService energyService;
     private EnergyService energyService;
 
 
+    @Resource
+    private TaskService taskService;
+
     /**
     /**
      * Get task list with user's progress for today (daily) or all-time (newbie).
      * Get task list with user's progress for today (daily) or all-time (newbie).
+     * 统一任务系统:委托 TaskService(任务已迁入 tasks 表)
      */
      */
-public List<Map<String, Object>> getTaskList(Long userId, Map<String, String> params) {
-	String type = params != null ? params.getOrDefault("type", "DAILY") : "DAILY";
-	String dimension = params != null ? params.get("dimension") : null;
-
-	if (type == null || type.isEmpty()) {
-		type = "DAILY";
-	}
-
-	LambdaQueryWrapper<GrowthTask> queryWrapper = new LambdaQueryWrapper<GrowthTask>()
-		.eq(GrowthTask::getType, type)
-		.eq(GrowthTask::getEnabled, 1)
-		.orderByAsc(GrowthTask::getSortOrder);
-
-	if (dimension != null && !dimension.isEmpty()) {
-		queryWrapper.eq(GrowthTask::getDimension, dimension);
-	}
-
-	SortUtil.applySort(queryWrapper);
-	List<GrowthTask> tasks = growthTaskMapper.selectList(queryWrapper);
-
-        String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
-
-        List<Map<String, Object>> result = new ArrayList<>();
-        for (GrowthTask task : tasks) {
-            Map<String, Object> item = new LinkedHashMap<>();
-            item.put("id", task.getId());
-            item.put("type", task.getType());
-            item.put("title", task.getTitle());
-            item.put("description", task.getDescription());
-            item.put("rewardPoints", task.getRewardPoints());
-            item.put("rewardEnergy", task.getRewardEnergy());
-            item.put("targetValue", task.getTargetValue());
-item.put("taskKey", task.getTaskKey());
-item.put("dimension", task.getDimension());
-
-// Find user's log for this task
-            LambdaQueryWrapper<GrowthTaskLog> logWrapper = new LambdaQueryWrapper<GrowthTaskLog>()
-                    .eq(GrowthTaskLog::getUserId, userId)
-                    .eq(GrowthTaskLog::getTaskId, task.getId());
-
-            if ("DAILY".equals(type)) {
-                logWrapper.eq(GrowthTaskLog::getDate, today);
-            }
-            // NEWBIE: no date filter, all-time
-
-            GrowthTaskLog taskLog = growthTaskLogMapper.selectOne(logWrapper.last("LIMIT 1"));
-
-            if (taskLog != null) {
-                item.put("taskLogId", taskLog.getId());
-                item.put("progress", taskLog.getProgress());
-                item.put("completed", taskLog.getCompleted());
-                item.put("claimed", taskLog.getClaimed());
-            } else {
-                item.put("taskLogId", null);
-                item.put("progress", 0);
-                item.put("completed", 0);
-                item.put("claimed", 0);
-            }
-
-            result.add(item);
-        }
-
-        return result;
+    @Deprecated
+    public List<Map<String, Object>> getTaskList(Long userId, Map<String, String> params) {
+        String type = params != null ? params.getOrDefault("type", "DAILY") : "DAILY";
+        String dimension = params != null ? params.get("dimension") : null;
+        return taskService.getGrowthTaskList(userId, type, dimension);
     }
     }
 
 
     /**
     /**
      * Update progress for a task. Auto-marks completed when progress >= targetValue.
      * Update progress for a task. Auto-marks completed when progress >= targetValue.
-     * Uses REQUIRES_NEW so failures never mark the caller transaction rollback-only.
+     * 统一任务系统:委托 TaskService(任务已迁入 tasks 表)
      */
      */
+    @Deprecated
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     @Transactional(propagation = Propagation.REQUIRES_NEW)
     public void updateProgress(Long userId, String taskKey, int increment) {
     public void updateProgress(Long userId, String taskKey, int increment) {
-        if (userId == null) {
-            // 未绑定登录账号的家庭成员(如仅建档的孩子)无成长任务进度
-            return;
-        }
-        GrowthTask task = growthTaskMapper.selectOne(
-                new LambdaQueryWrapper<GrowthTask>()
-                        .eq(GrowthTask::getTaskKey, taskKey)
-                        .eq(GrowthTask::getEnabled, 1)
-                        .last("LIMIT 1")
-        );
-        if (task == null) {
-            log.warn("成长任务不存在或未启用: taskKey={}", taskKey);
-            return;
+        try {
+            taskService.updateProgress(userId, taskKey, increment);
+        } catch (Exception e) {
+            log.warn("统一任务进度更新失败: userId={}, taskKey={}, error={}", userId, taskKey, e.getMessage());
         }
         }
-
-        String today = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
-
-        // Find or create log
-        LambdaQueryWrapper<GrowthTaskLog> logWrapper = new LambdaQueryWrapper<GrowthTaskLog>()
-                .eq(GrowthTaskLog::getUserId, userId)
-                .eq(GrowthTaskLog::getTaskId, task.getId());
-
-        if ("DAILY".equals(task.getType())) {
-            logWrapper.eq(GrowthTaskLog::getDate, today);
-        }
-
-        GrowthTaskLog taskLog = growthTaskLogMapper.selectOne(logWrapper.last("LIMIT 1"));
-
-        if (taskLog == null) {
-            taskLog = new GrowthTaskLog();
-            taskLog.setUserId(userId);
-            taskLog.setTaskId(task.getId());
-            taskLog.setProgress(0);
-            taskLog.setCompleted(0);
-            taskLog.setClaimed(0);
-            if ("DAILY".equals(task.getType())) {
-                taskLog.setDate(today);
-            }
-            taskLog.setCreatedAt(new Date());
-            growthTaskLogMapper.insert(taskLog);
-        }
-
-        // Already completed, skip
-        if (taskLog.getCompleted() != null && taskLog.getCompleted() == 1) {
-            return;
-        }
-
-        // Update progress
-        int newProgress = (taskLog.getProgress() != null ? taskLog.getProgress() : 0) + increment;
-        taskLog.setProgress(newProgress);
-
-        // Auto-complete when target reached
-        int targetValue = task.getTargetValue() != null ? task.getTargetValue() : 1;
-        if (newProgress >= targetValue) {
-            taskLog.setCompleted(1);
-        }
-
-        growthTaskLogMapper.updateById(taskLog);
     }
     }
 
 
     /**
     /**
@@ -270,18 +164,18 @@ item.put("dimension", task.getDimension());
         }
         }
         }
         }
 
 
+        @Deprecated
         @Transactional
         @Transactional
         public GrowthTask createDynamicTask(Long userId, String title, String description, String dimension, Integer rewardPoints, String sourceConversationId) {
         public GrowthTask createDynamicTask(Long userId, String title, String description, String dimension, Integer rewardPoints, String sourceConversationId) {
-            GrowthTask task = new GrowthTask();
-            task.setType("AI_GENERATED");
-            task.setTitle(title);
-            task.setDescription(description);
-            task.setDimension(dimension != null ? dimension : "body");
-            task.setRewardPoints(rewardPoints != null ? rewardPoints : 10);
-            task.setEnabled(1);
-            task.setSourceConversationId(sourceConversationId);
-            growthTaskMapper.insert(task);
-            log.info("创建AI动态任务: userId={}, taskId={}, title={}", userId, task.getId(), title);
-            return task;
+            // 统一任务系统:委托 TaskService(任务写入 tasks 表,source_type=ai)
+            Long taskId = taskService.createDynamicTask(userId, title, description, dimension, rewardPoints, sourceConversationId);
+            GrowthTask g = new GrowthTask();
+            g.setId(taskId);
+            g.setTitle(title);
+            g.setDescription(description);
+            g.setDimension(dimension != null ? dimension : "body");
+            g.setRewardPoints(rewardPoints != null ? rewardPoints : 10);
+            g.setSourceConversationId(sourceConversationId);
+            return g;
         }
         }
 }
 }

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

@@ -60,9 +60,6 @@ public class TaskService implements TaskServiceInterface {
     @Resource
     @Resource
     private FamilyRelationshipScoreMapper familyRelationshipScoreMapper;
     private FamilyRelationshipScoreMapper familyRelationshipScoreMapper;
 
 
-    @Resource
-    private GrowthTaskService growthTaskService;
-
     @Resource
     @Resource
     private CartService cartService;
     private CartService cartService;
 
 
@@ -902,7 +899,7 @@ public List<Task> getTodayTasks(Long memberId, String dimensionCode) {
 
 
         try {
         try {
             if (child.getUserId() != null) {
             if (child.getUserId() != null) {
-                growthTaskService.updateProgress(child.getUserId(), "DAILY_TASK", 1);
+                this.updateProgress(child.getUserId(), "DAILY_TASK", 1);
             }
             }
         } catch (Exception e) {
         } catch (Exception e) {
             log.warn("成长任务进度更新失败: memberId={}, error={}", memberId, e.getMessage());
             log.warn("成长任务进度更新失败: memberId={}, error={}", memberId, e.getMessage());
@@ -1527,4 +1524,189 @@ return 0; // 达到每日扣分上限
             log.warn("调研任务自动生成模板失败(不影响任务创建): taskId={}, error={}", task.getId(), e.getMessage());
             log.warn("调研任务自动生成模板失败(不影响任务创建): taskId={}, error={}", task.getId(), e.getMessage());
         }
         }
     }
     }
+
+    // ==================== 统一任务系统:成长任务迁移(原 GrowthTaskService) ====================
+
+    /**
+     * 统一任务系统:更新每日进度型任务的进度(原 growth_task updateProgress)
+     * 按 (userId→familyMemberId, taskKey) 找当日任务实例,累计进度,达标自动完成+发奖
+     * taskKey 匹配约定:title 精确匹配 OR title 以 "[taskKey]" 前缀匹配(迁移时 task_key 拼入 title)
+     */
+    @Transactional
+    public void updateProgress(Long userId, String taskKey, int increment) {
+        if (userId == null) {
+            // 未绑定登录账号的家庭成员(如仅建档的孩子)无成长任务进度
+            return;
+        }
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+        if (member == null) {
+            return;
+        }
+
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        Date startOfDay = cal.getTime();
+        cal.add(Calendar.DAY_OF_MONTH, 1);
+        Date endOfDay = cal.getTime();
+
+        Task task = taskMapper.selectOne(new LambdaQueryWrapper<Task>()
+                .eq(Task::getFamilyMemberId, member.getId())
+                .eq(Task::getIsDailyProgress, 1)
+                .ge(Task::getCreatedAt, startOfDay)
+                .lt(Task::getCreatedAt, endOfDay)
+                .and(w -> w.eq(Task::getTitle, taskKey)
+                        .or().likeRight(Task::getTitle, "[" + taskKey + "]"))
+                .last("LIMIT 1"));
+        if (task == null) {
+            log.warn("统一任务进度更新:未找到今日进度型任务 taskKey={}, userId={}", taskKey, userId);
+            return;
+        }
+        if ("completed".equals(task.getStatus())) {
+            return;
+        }
+
+        // 读当前进度(task_executions.extra.progress),累加
+        TaskExecution exec = taskExecutionMapper.selectOne(new LambdaQueryWrapper<TaskExecution>()
+                .eq(TaskExecution::getTaskId, task.getId())
+                .eq(TaskExecution::getMemberId, member.getId())
+                .last("LIMIT 1"));
+        int progress = 0;
+        if (exec != null && exec.getExtra() != null) {
+            try {
+                progress = JSON.parseObject(exec.getExtra()).getIntValue("progress");
+            } catch (Exception ignore) {
+            }
+        }
+        progress += increment;
+        int target = task.getTargetValue() != null ? task.getTargetValue() : 1;
+
+        if (exec == null) {
+            exec = new TaskExecution();
+            exec.setTaskId(task.getId());
+            exec.setMemberId(member.getId());
+            exec.setStatus("started");
+            exec.setStartedAt(new Date());
+            exec.setCreatedAt(new Date());
+        }
+        exec.setStatus("finished");
+        exec.setFinishedAt(new Date());
+        exec.setExtra("{\"progress\":" + progress + ",\"target\":" + target + "}");
+        if (exec.getId() == null) {
+            taskExecutionMapper.insert(exec);
+        } else {
+            taskExecutionMapper.updateById(exec);
+        }
+
+        // 达标自动完成 + 发奖(复用现有奖励链路)
+        if (progress >= target) {
+            awardTaskCompletionRewards(task, member.getId(), new Date());
+        }
+    }
+
+    /**
+     * 统一任务系统:AI 对话生成任务(原 growth_task createDynamicTask)
+     * 直接写入 tasks 表,source_type=ai
+     */
+    @Transactional
+    public Long createDynamicTask(Long userId, String title, String description,
+                                  String dimension, Integer rewardPoints, String sourceConversationId) {
+        User user = userService.getUserInfo(userId);
+        Task task = new Task();
+        task.setFamilyId(user.getFamilyId());
+        task.setCreatorId(userId);
+        task.setExecutorType("child");
+        // 执行者:AI 任务发给当前用户对应家庭成员(若有)
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+        if (member != null) {
+            task.setExecutorId(member.getId());
+            task.setFamilyMemberId(member.getId());
+            task.setChildId(member.getId());
+        }
+        task.setTitle(title);
+        task.setDescription(description);
+        task.setPoints(rewardPoints != null ? rewardPoints : 10);
+        task.setCategory("成长");
+        task.setStatus("pending");
+        task.setNeedReview(0);
+        task.setIsTemplate(0);
+        task.setSourceType("ai");
+        task.setSourceId(0L);
+        task.setDimensionCode(dimension != null ? dimension : "body");
+        task.setMemberOnly(0);
+        task.setDeadline(new Date(System.currentTimeMillis() + 7L * 24 * 3600 * 1000)); // 7天有效期
+        task.setCreatedAt(new Date());
+        task.setUpdatedAt(new Date());
+        taskMapper.insert(task);
+        log.info("创建AI动态任务: userId={}, taskId={}, title={}", userId, task.getId(), title);
+        return task.getId();
+    }
+
+    /**
+     * 统一任务系统:查询用户每日进度型任务列表(原 growth-task/list)
+     * type: DAILY(NEWBIE 在 Task 5 处理)
+     */
+    public List<Map<String, Object>> getGrowthTaskList(Long userId, String type, String dimension) {
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+        List<Map<String, Object>> result = new ArrayList<>();
+        if (member == null || !"DAILY".equals(type)) {
+            return result;
+        }
+
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        Date startOfDay = cal.getTime();
+        cal.add(Calendar.DAY_OF_MONTH, 1);
+        Date endOfDay = cal.getTime();
+
+        List<Task> tasks = taskMapper.selectList(new LambdaQueryWrapper<Task>()
+                .eq(Task::getFamilyMemberId, member.getId())
+                .eq(Task::getIsDailyProgress, 1)
+                .ge(Task::getCreatedAt, startOfDay)
+                .lt(Task::getCreatedAt, endOfDay)
+                .orderByAsc(Task::getId));
+        for (Task task : tasks) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("taskLogId", task.getId());
+            item.put("id", task.getId());
+            item.put("type", "DAILY");
+            item.put("title", task.getTitle());
+            item.put("description", task.getDescription());
+            item.put("rewardPoints", task.getPoints());
+            item.put("rewardEnergy", 0);
+            item.put("targetValue", task.getTargetValue() != null ? task.getTargetValue() : 1);
+            item.put("dimension", task.getDimensionCode());
+
+            TaskExecution exec = taskExecutionMapper.selectOne(new LambdaQueryWrapper<TaskExecution>()
+                    .eq(TaskExecution::getTaskId, task.getId())
+                    .eq(TaskExecution::getMemberId, member.getId())
+                    .last("LIMIT 1"));
+            int progress = 0;
+            if (exec != null && exec.getExtra() != null) {
+                try {
+                    progress = JSON.parseObject(exec.getExtra()).getIntValue("progress");
+                } catch (Exception ignore) {
+                }
+            }
+            item.put("progress", progress);
+            item.put("completed", "completed".equals(task.getStatus()) ? 1 : 0);
+            item.put("claimed", 0); // 统一任务系统:自动发放,无 claimed
+            result.add(item);
+        }
+        return result;
+    }
 }
 }