ソースを参照

feat: 方案保存后自动生成每日任务(P0-3后端)

Sisyphus 1 ヶ月 前
コミット
92ace7c730

+ 97 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/impl/HealthPlanServiceImpl.java

@@ -2,35 +2,55 @@ package com.etotem.cfc.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.etotem.cfc.entity.HealthPlan;
+import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.mapper.HealthPlanMapper;
+import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.service.HealthPlanService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.RestTemplate;
 import javax.annotation.Resource;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import com.etotem.cfc.util.SortUtil;
 
+@Slf4j
 @Service
 public class HealthPlanServiceImpl implements HealthPlanService {
-    private static final Logger log = LoggerFactory.getLogger(HealthPlanServiceImpl.class);
 
     @Resource
     private HealthPlanMapper healthPlanMapper;
 
+    @Resource
+    private TaskMapper taskMapper;
+
     @Value("${cfc.langgraph.base-url:}")
     private String langgraphBaseUrl;
 
+    // 匹配方案文本中的任务行(与前端 parseTasksFromPlan 保持一致)
+    private static final Pattern TASK_PATTERN = Pattern.compile(
+        "^(?:\\d+[\\.、\\)\\]]]|[-*•]|\\[.+\\]|【任务\\d+】)[\\s ]*([^\\n]+)"
+    );
+
     @Override
     public Long savePlan(HealthPlan plan) {
         plan.setCreatedAt(new Date());
         healthPlanMapper.insert(plan);
+        try {
+            generateDailyTasksFromPlan(plan);
+            log.info("方案{}自动生成任务完成,familyId={}, memberIds={}",
+                plan.getId(), plan.getFamilyId(), plan.getMemberIds());
+        } catch (Exception e) {
+            log.warn("方案{}自动生成任务失败,不影响方案保存: {}", plan.getId(), e.getMessage());
+        }
         return plan.getId();
     }
 
@@ -88,4 +108,77 @@ public class HealthPlanServiceImpl implements HealthPlanService {
         sb.append("坚持执行以上方案,预计 4 周内可见明显改善。\n");
         return sb.toString();
     }
-}
+
+    /**
+     * 解析方案文本,为每个任务项创建当天的 Task 记录
+     */
+    private void generateDailyTasksFromPlan(HealthPlan plan) {
+        String content = plan.getPlanContent();
+        if (content == null || content.trim().isEmpty()) return;
+
+        List<String> taskLines = parseTaskLines(content);
+        if (taskLines.isEmpty()) return;
+
+        Long familyId = plan.getFamilyId();
+        String[] memberIdArray = (plan.getMemberIds() != null && !plan.getMemberIds().isEmpty())
+            ? plan.getMemberIds().split(",") : new String[0];
+        Long childId = memberIdArray.length > 0 ? Long.valueOf(memberIdArray[0]) : null;
+        Long executorId = childId != null ? childId : null;
+
+        // 当天截止:23:59:59
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        Date deadline = cal.getTime();
+
+        Date now = new Date();
+
+        for (String line : taskLines) {
+            // 幂等:同一方案同一天已有同名任务则跳过
+            boolean exists = taskMapper.selectCount(new QueryWrapper<Task>()
+                .eq("family_id", familyId)
+                .eq("source_type", "plan")
+                .eq("source_id", plan.getId())
+                .eq("title", line.trim())) > 0;
+            if (exists) continue;
+
+            Task task = new Task();
+            task.setFamilyId(familyId);
+            task.setChildId(childId);
+            task.setExecutorType("child");
+            task.setExecutorId(executorId);
+            task.setCreatorId(executorId != null ? executorId : 0L);
+            task.setTitle(line.trim());
+            task.setDescription("");
+            task.setPoints(10);
+            task.setDeadline(deadline);
+            task.setStatus("pending");
+            task.setSourceType("plan");
+            task.setSourceId(plan.getId());
+            task.setFrequency("daily");
+            task.setCategory("健康方案");
+            task.setNeedReview(0);
+            task.setCreatedAt(now);
+            task.setUpdatedAt(now);
+            taskMapper.insert(task);
+        }
+    }
+
+    /**
+     * 从方案文本中提取任务行(与前端正则逻辑一致)
+     */
+    private List<String> parseTaskLines(String planText) {
+        String[] lines = planText.split("\n");
+        List<String> tasks = new java.util.ArrayList<>();
+        for (String line : lines) {
+            line = line.trim();
+            if (line.isEmpty()) continue;
+            Matcher m = TASK_PATTERN.matcher(line);
+            if (m.find()) {
+                tasks.add(m.group(1).trim());
+            }
+        }
+        return tasks;
+    }
+}