# 五维家庭自检 AI 结合(P0 + P1)实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 将五维自检结果从静态五行寻源表(`WuxingSourcingService`)替换为 LangGraph AI 生成建议,并叠加健康计划生成、趋势分析、Chat 上下文注入三个能力。 **架构:** LangGraph 新增 2 个 graph(`self_check_analysis_graph`、`self_check_trend_graph`)+ 1 个 api 模块;Java 新增 `SelfCheckAnalysisService` + 2 个 AiGateway 方法 + 3 个 Controller 接口;前端新增 API 方法 + 改造结果页/中间页/Chat。 **技术栈:** Java 8 / Spring Boot 2.7.18 / MyBatis-Plus / Python 3.11 + FastAPI + LangGraph + LangChain / uni-app Vue 2 **规格文档:** `docs/superpowers/specs/2026-08-31-self-check-ai-integration-design.md` --- ## 文件结构 | 文件 | 职责 | 变更 | |------|------|------| | `cfc-langgraph/app/graphs/self_check_analysis_graph.py` | P0-1:自检建议 AI 生成 graph | 新建 | | `cfc-langgraph/app/graphs/self_check_trend_graph.py` | P1-1:趋势分析 AI 生成 graph | 新建 | | `cfc-langgraph/app/api/self_check.py` | 注册 P0-1 + P1-1 路由 | 新建 | | `cfc-langgraph/app/main.py` | 注册 self_check router | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/dto/WuxingSourcingAdviceVO.java` | VO 改造(去 upstream/restrainer/action,加 AI 字段) | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/service/SelfCheckAnalysisService.java` | P0-1:调 AiGateway 生成建议 | 新建 | | `cfc-backend/src/main/java/com/etotem/cfc/service/WuxingSourcingService.java` | 删除 SOURCING_TABLE/getAdvice/getAdvicesForLowScores | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionSelfCheckService.java` | submitSelfCheck 调新 Service;新增 trend 方法 | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/service/AiGateway.java` | 新增 generateSelfCheckAdvice/generateSelfCheckTrend | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/controller/family/FiveDimensionSelfCheckController.java` | 新增 generatePlan、trendAnalysis 接口;submit 改造 | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/service/HealthPlanService.java` | 新增 generateFromSelfCheck 接口 | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/service/impl/HealthPlanServiceImpl.java` | 实现 generateFromSelfCheck | 修改 | | `cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java` | sendMessage 支持 selfCheckId 参数 | 修改 | | `cfc-frontend/utils/api.js` | 新增 3 个 API 方法 | 修改 | | `cfc-frontend/pages/family/self-check-result.vue` | P0-1/P0-2/P1-2 展示改造 | 修改 | | `cfc-frontend/pages/family/self-check-entry.vue` | P1-1 趋势展示 | 修改 | --- ## 任务 1:LangGraph — self_check_analysis_graph(P0-1 核心) **文件:** - 创建:`cfc-langgraph/app/graphs/self_check_analysis_graph.py` - 创建:`cfc-langgraph/app/api/self_check.py` - [ ] **步骤 1:创建 self_check_analysis_graph.py** 创建文件 `cfc-langgraph/app/graphs/self_check_analysis_graph.py`: ```python import json import logging from typing import TypedDict, Optional from langgraph.graph import StateGraph, START, END from langchain_core.messages import SystemMessage, HumanMessage from app.llm.client import get_llm from app.monitoring import monitor_agent logger = logging.getLogger(__name__) SYSTEM_PROMPT = """你是一位家庭健康顾问,基于五维自检结果(身·智·富·行·心,每维0-9分,满分45)给出个性化建议。 要求: 1. 对每个低分维度(≤6分)给出1-2句解读和2-3个具体可执行的微行动 2. 如有历史数据,简要对比趋势(改善/下滑) 3. 语气温暖口语化,每条解读不超过80字 4. 最后给出1句家庭整体洞察(30字以内) 返回 JSON(严格格式,不要额外文字): { "advice": [ { "dimension": "mind", "dimensionName": "心", "interpretation": "你的情绪能量偏低,可能最近压力较大,建议...", "microActions": ["今晚睡前做10分钟深呼吸", "和伴侣约定每周一次夜谈"], "fallbackUsed": false } ], "familyInsight": "建议从行动维度入手,关系顺畅了内心才能安定" } """ class GraphState(TypedDict): scores: dict question_ids: list user_id: int recent_history: list advice: Optional[dict] error: Optional[str] class SelfCheckAnalysisAgent: def __init__(self): self.llm = get_llm() @monitor_agent("self_check_analysis") async def run(self, scores: dict, question_ids: list, user_id: int, recent_history: list) -> dict: try: score_summary = "\n".join( f"{v.get('dimensionName', k)}({k}): {v.get('score', 0)}分" for k, v in scores.items() ) history_summary = "" if recent_history: history_summary = "历史趋势:\n" + "\n".join( f"- {h.get('createdAt', '')}: 总分{h.get('totalScore', 0)}分" for h in recent_history[:3] ) messages = [ SystemMessage(content=SYSTEM_PROMPT), HumanMessage(content=f"用户ID: {user_id}\n当前自检得分:\n{score_summary}\n{history_summary}"), ] response = await self.llm.ainvoke(messages) text = response.content.strip() if "```json" in text: text = text.split("```json")[1].split("```")[0].strip() elif "```" in text: text = text.split("```")[1].split("```")[0].strip() data = json.loads(text) advice_list = data.get("advice", []) for item in advice_list: item.setdefault("fallbackUsed", False) if advice_list: advice_list[0]["familyInsight"] = data.get("familyInsight", "") return {"advice": {"advice_json": json.dumps(advice_list, ensure_ascii=False), "fallback_used": False}} except Exception as e: logger.warning("自检建议生成失败: %s", e) return {"advice": {"advice_json": None, "fallback_used": True}} def build_graph(): agent = SelfCheckAnalysisAgent() def parse_input(state: GraphState) -> GraphState: return state async def call_llm(state: GraphState) -> dict: return await agent.run(state["scores"], state["question_ids"], state["user_id"], state["recent_history"]) def validate(state: GraphState) -> GraphState: adv = state.get("advice") if adv is None or adv.get("advice_json") is None: return {**state, "error": "AI 建议生成失败"} return state graph = StateGraph(GraphState) graph.add_node("parse", parse_input) graph.add_node("llm", call_llm) graph.add_node("validate", validate) graph.add_edge(START, "parse") graph.add_edge("parse", "llm") graph.add_edge("llm", "validate") graph.add_edge("validate", END) return graph.compile() _graph = None def get_graph(): global _graph if _graph is None: _graph = build_graph() return _graph ``` - [ ] **步骤 2:创建 self_check.py API 模块** 创建文件 `cfc-langgraph/app/api/self_check.py`: ```python import logging from fastapi import APIRouter from pydantic import BaseModel from typing import Any, Dict, Optional from app.graphs.self_check_analysis_graph import get_graph from app.graphs.self_check_trend_graph import get_trend_graph logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1", tags=["self-check"]) class SelfCheckAnalysisRequest(BaseModel): scores: Dict[str, Any] question_ids: list user_id: int recent_history: Optional[list] = None class SelfCheckTrendRequest(BaseModel): history: list user_id: int @router.post("/self-check/analysis") async def self_check_analysis(req: SelfCheckAnalysisRequest): graph = get_graph() state = { "scores": req.scores, "question_ids": req.question_ids, "user_id": req.user_id, "recent_history": req.recent_history or [], "advice": None, "error": None, } result = await graph.ainvoke(state) advice = result.get("advice") or {} return { "advice_json": advice.get("advice_json"), "fallback_used": advice.get("fallback_used", True), "error": result.get("error"), } @router.post("/self-check/trend") async def self_check_trend(req: SelfCheckTrendRequest): graph = get_trend_graph() state = {"history": req.history, "user_id": req.user_id, "insight": None, "error": None} result = await graph.ainvoke(state) insight = result.get("insight") or {} return { "aiInsight": insight.get("aiInsight", ""), "trendSummary": insight.get("trendSummary", ""), "error": result.get("error"), } ``` - [ ] **步骤 3:注册路由** 在 `cfc-langgraph/app/main.py` 中,于 `app.include_router(innate_portrait.router)` 之后追加: ```python from app.api import self_check app.include_router(self_check.router) ``` - [ ] **步骤 4:语法校验** ```bash cd cfc-langgraph && python -m py_compile app/graphs/self_check_analysis_graph.py app/graphs/self_check_trend_graph.py app/api/self_check.py && echo "OK" ``` - [ ] **步骤 5:Commit** ```bash git add cfc-langgraph/app/graphs/self_check_analysis_graph.py \ cfc-langgraph/app/graphs/self_check_trend_graph.py \ cfc-langgraph/app/api/self_check.py \ cfc-langgraph/app/main.py git commit -m "feat(langgraph): 新增自检建议+trend graph + self_check 路由" ``` --- ## 任务 2:LangGraph — self_check_trend_graph(P1-1 核心) **文件:** - 创建:`cfc-langgraph/app/graphs/self_check_trend_graph.py` - [ ] **步骤 1:创建 self_check_trend_graph.py** 创建文件 `cfc-langgraph/app/graphs/self_check_trend_graph.py`: ```python import json import logging from typing import TypedDict, Optional from langgraph.graph import StateGraph, START, END from langchain_core.messages import SystemMessage, HumanMessage from app.llm.client import get_llm from app.monitoring import monitor_agent logger = logging.getLogger(__name__) SYSTEM_PROMPT = """分析用户近3次五维自检趋势,输出: - aiInsight: 趋势解读(2-3句,指出最大变化维度和可能原因,口语化) - trendSummary: 各维度 delta 简写(如"身-2 智+1 富0 行-1 心+2") 返回 JSON:{"aiInsight": "...", "trendSummary": "..."} """ class TrendState(TypedDict): history: list user_id: int insight: Optional[dict] error: Optional[str] class SelfCheckTrendAgent: def __init__(self): self.llm = get_llm() @monitor_agent("self_check_trend") async def run(self, history: list, user_id: int) -> dict: try: if len(history) < 2: return {"insight": {"aiInsight": "自检次数不足,建议完成至少2次自检后查看趋势", "trendSummary": ""}, "error": None} history_text = "\n".join( f"{h.get('createdAt', '')}: 总分{h.get('totalScore', 0)}," + " ".join(f"{d.get('name','')}{d.get('score',0)}分" for d in h.get('dimensions', [])) for h in history[-3:] ) messages = [ SystemMessage(content=SYSTEM_PROMPT), HumanMessage(content=f"用户{user_id}的自检历史:\n{history_text}"), ] response = await self.llm.ainvoke(messages) text = response.content.strip() if "```json" in text: text = text.split("```json")[1].split("```")[0].strip() elif "```" in text: text = text.split("```")[1].split("```")[0].strip() data = json.loads(text) return {"insight": data, "error": None} except Exception as e: logger.warning("趋势分析失败: %s", e) return {"insight": {"aiInsight": "", "trendSummary": ""}, "error": str(e)} def build_trend_graph(): agent = SelfCheckTrendAgent() async def call_llm(state: TrendState) -> dict: return await agent.run(state["history"], state["user_id"]) graph = StateGraph(TrendState) graph.add_node("llm", call_llm) graph.add_edge(START, "llm") graph.add_edge("llm", END) return graph.compile() _trend_graph = None def get_trend_graph(): global _trend_graph if _trend_graph is None: _trend_graph = build_trend_graph() return _trend_graph ``` - [ ] **步骤 2:语法校验** ```bash cd cfc-langgraph && python -m py_compile app/graphs/self_check_trend_graph.py && echo "OK" ``` - [ ] **步骤 3:Commit** ```bash git add cfc-langgraph/app/graphs/self_check_trend_graph.py git commit -m "feat(langgraph): 新增 self_check_trend_graph 趋势分析实现" ``` --- ## 任务 3:Java — WuxingSourcingAdviceVO 改造 + SelfCheckAnalysisService **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/dto/WuxingSourcingAdviceVO.java` - 新建:`cfc-backend/src/main/java/com/etotem/cfc/service/SelfCheckAnalysisService.java` - [ ] **步骤 1:改造 WuxingSourcingAdviceVO** 将 `cfc-backend/src/main/java/com/etotem/cfc/dto/WuxingSourcingAdviceVO.java` 的字段替换为: ```java package com.etotem.cfc.dto; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @Data public class WuxingSourcingAdviceVO { private String dimension; private String dimensionName; private String element; private String color; private Integer score; private String level; private String levelName; /** AI 解读(替换原 upstreamReason) */ private String interpretation; /** AI 微行动列表 */ private List microActions; /** AI 补充洞察 */ private String aiInsight; /** 家庭整体洞察(仅第一条携带) */ private String familyInsight; /** 是否使用降级静态建议 */ @JsonProperty("fallbackUsed") private Boolean fallbackUsed; } ``` - [ ] **步骤 2:创建 SelfCheckAnalysisService** 创建文件 `cfc-backend/src/main/java/com/etotem/cfc/service/SelfCheckAnalysisService.java`: ```java package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.etotem.cfc.entity.FiveDimensionSelfCheck; import com.etotem.cfc.mapper.FiveDimensionSelfCheckMapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; @Service public class SelfCheckAnalysisService { private static final Logger log = LoggerFactory.getLogger(SelfCheckAnalysisService.class); @Resource private AiGateway aiGateway; @Resource private FiveDimensionSelfCheckMapper selfCheckMapper; private final ObjectMapper objectMapper = new ObjectMapper(); /** * 生成自检建议(AI 优先,失败返回 adviceJson=null + fallbackUsed=true) */ public Map generateAdvice(Long userId, Map scoreMap, List questionIds) { try { List> recentHistory = getRecentHistory(userId, 3); Map scoresWithMeta = new LinkedHashMap<>(); for (Map.Entry entry : scoreMap.entrySet()) { Map m = new LinkedHashMap<>(); m.put("dimension", entry.getKey()); m.put("dimensionName", getDimensionName(entry.getKey())); m.put("score", entry.getValue()); scoresWithMeta.put(entry.getKey(), m); } Map inputs = new LinkedHashMap<>(); inputs.put("scores", scoresWithMeta); inputs.put("questionIds", questionIds); inputs.put("userId", userId); inputs.put("recentHistory", recentHistory); Map result = aiGateway.generateSelfCheckAdvice(inputs); if (result == null) { log.info("AI 自检建议生成失败,fallback"); return Map.of("adviceJson", null, "fallbackUsed", true); } String adviceJson = (String) result.get("advice_json"); Boolean fallbackUsed = (Boolean) result.getOrDefault("fallback_used", false); return Map.of("adviceJson", adviceJson, "fallbackUsed", fallbackUsed); } catch (Exception e) { log.warn("自检建议生成异常: {}", e.getMessage()); return Map.of("adviceJson", null, "fallbackUsed", true); } } /** * 生成趋势分析(AI 优先,失败返回空 insight) */ public Map generateTrend(Long userId) { try { List> history = getRecentHistory(userId, 3); Map inputs = Map.of("history", history, "userId", userId); Map result = aiGateway.generateSelfCheckTrend(inputs); if (result == null) { return Map.of("aiInsight", "", "trendSummary", ""); } return Map.of( "aiInsight", result.getOrDefault("aiInsight", ""), "trendSummary", result.getOrDefault("trendSummary", "") ); } catch (Exception e) { log.warn("趋势分析异常: {}", e.getMessage()); return Map.of("aiInsight", "", "trendSummary", ""); } } /** 获取最近 N 次自检历史(供 AI 和前端使用) */ public List> getRecentHistory(Long userId, int limit) { try { List records = selfCheckMapper.selectList( new LambdaQueryWrapper() .eq(FiveDimensionSelfCheck::getUserId, userId) .orderByDesc(FiveDimensionSelfCheck::getCreatedAt) .last("LIMIT " + limit) ); List> result = new ArrayList<>(); for (FiveDimensionSelfCheck r : records) { Map m = new LinkedHashMap<>(); m.put("createdAt", r.getCreatedAt() != null ? r.getCreatedAt().toString() : ""); m.put("totalScore", r.getTotalScore()); if (r.getScoresJson() != null) { try { Map scores = objectMapper.readValue(r.getScoresJson(), new TypeReference>() {}); List> dims = new ArrayList<>(); for (Map.Entry e : scores.entrySet()) { Map d = new LinkedHashMap<>(); d.put("dimension", e.getKey()); d.put("name", getDimensionName(e.getKey())); d.put("score", e.getValue()); dims.add(d); } m.put("dimensions", dims); } catch (Exception ignored) {} } result.add(m); } return result; } catch (Exception e) { log.warn("获取自检历史失败: {}", e.getMessage()); return Collections.emptyList(); } } private String getDimensionName(String dim) { switch (dim) { case "body": return "身"; case "wisdom": return "智"; case "wealth": return "富"; case "action": return "行"; case "mind": return "心"; default: return dim; } } } ``` - [ ] **步骤 3:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 4:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/dto/WuxingSourcingAdviceVO.java \ cfc-backend/src/main/java/com/etotem/cfc/service/SelfCheckAnalysisService.java git commit -m "feat(self-check): WuxingSourcingAdviceVO 改造为 AI 建议 VO + SelfCheckAnalysisService" ``` --- ## 任务 4:Java — AiGateway 新增方法 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/AiGateway.java` - [ ] **步骤 1:新增 generateSelfCheckAdvice 方法** 在 `AiGateway.java` 中,于 `generateInnateReading` 方法之后追加: ```java /** * 生成自检 AI 建议(调用 LangGraph self_check_analysis_graph) * @return 含 "advice_json" 和 "fallback_used" 的 Map;失败返回 null */ public Map generateSelfCheckAdvice(Map inputs) { if (!enabled || isCircuitOpen()) return null; try { ObjectNode body = objectMapper.valueToTree(inputs); HttpEntity entity = new HttpEntity<>(body.toString(), createJsonHeaders()); String url = baseUrl + "/api/v1/self-check/analysis"; ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { JsonNode root = objectMapper.readTree(response.getBody()); Map result = new LinkedHashMap<>(); result.put("advice_json", root.has("advice_json") ? root.get("advice_json").asText() : null); result.put("fallback_used", root.has("fallback_used") ? root.get("fallback_used").asBoolean() : true); consecutiveFailures.set(0); return result; } return null; } catch (Exception e) { log.warn("AiGateway generateSelfCheckAdvice 调用失败: {}", e.getMessage()); recordFailure(); return null; } } /** * 生成自检趋势分析(调用 LangGraph self_check_trend_graph) * @return 含 "aiInsight" 和 "trendSummary" 的 Map;失败返回 null */ public Map generateSelfCheckTrend(Map inputs) { if (!enabled || isCircuitOpen()) return null; try { ObjectNode body = objectMapper.valueToTree(inputs); HttpEntity entity = new HttpEntity<>(body.toString(), createJsonHeaders()); String url = baseUrl + "/api/v1/self-check/trend"; ResponseEntity response = restTemplate.postForEntity(url, entity, String.class); if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { JsonNode root = objectMapper.readTree(response.getBody()); Map result = new LinkedHashMap<>(); result.put("aiInsight", root.has("aiInsight") ? root.get("aiInsight").asText() : ""); result.put("trendSummary", root.has("trendSummary") ? root.get("trendSummary").asText() : ""); consecutiveFailures.set(0); return result; } return null; } catch (Exception e) { log.warn("AiGateway generateSelfCheckTrend 调用失败: {}", e.getMessage()); recordFailure(); return null; } } ``` - [ ] **步骤 2:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/AiGateway.java git commit -m "feat(self-check): AiGateway 新增 generateSelfCheckAdvice/generateSelfCheckTrend" ``` --- ## 任务 5:Java — WuxingSourcingService 清理 + FiveDimensionSelfCheckService 改造 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/WuxingSourcingService.java` - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionSelfCheckService.java` - [ ] **步骤 1:清理 WuxingSourcingService** 删除 `WuxingSourcingService.java` 中的: - `SOURCING_TABLE` 静态字段(第 41-68 行) - `getAdvice()` 方法(第 78-109 行) - `getAdvicesForLowScores()` 方法(第 117-132 行) 保留:`DIMENSION_META`、`levelOf()`、`levelName()`、`dimensionName()` - [ ] **步骤 2:FiveDimensionSelfCheckService 改造 submitSelfCheck** 在 `submitSelfCheck` 方法中,将第 472-477 行(`wuxingSourcingService.getAdvicesForLowScores` + 落库 adviceJson)替换为: ```java // 生成 AI 建议(AI 优先,失败降级为空建议) String adviceJson = null; try { Map adviceResult = selfCheckAnalysisService.generateAdvice(userId, scoreMap, submittedQuestionIds); adviceJson = (String) adviceResult.get("adviceJson"); } catch (Exception e) { log.warn("AI 建议生成异常,使用空建议: {}", e.getMessage()); } record.setAdviceJson(adviceJson != null ? adviceJson : "[]"); ``` 同时新增注入: ```java @Resource private SelfCheckAnalysisService selfCheckAnalysisService; ``` 并将 `vo.setAdvices(...)` 改为根据 adviceJson 解析(若无 AI 结果则空列表): ```java vo.setAdvices(parseAdvices(adviceJson)); ``` - [ ] **步骤 3:新增 parseAdvices + trend 方法** 在 Service 中新增: ```java private List parseAdvices(String adviceJson) { if (adviceJson == null || adviceJson.isEmpty() || "[]".equals(adviceJson)) { return new ArrayList<>(); } try { return objectMapper.readValue(adviceJson, objectMapper.getTypeFactory().constructCollectionType(List.class, WuxingSourcingAdviceVO.class)); } catch (Exception e) { log.warn("解析 AI 建议 JSON 失败: {}", e.getMessage()); return new ArrayList<>(); } } /** 获取自检趋势分析结果(含历史列表) */ public Map getTrendAnalysis(Long userId) { Map trend = selfCheckAnalysisService.generateTrend(userId); Map resp = new HashMap<>(); resp.put("history", selfCheckAnalysisService.getRecentHistory(userId, 3)); resp.put("aiInsight", trend.getOrDefault("aiInsight", "")); resp.put("trendSummary", trend.getOrDefault("trendSummary", "")); return resp; } ``` - [ ] **步骤 4:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 5:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/WuxingSourcingService.java \ cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionSelfCheckService.java git commit -m "feat(self-check): 清理 WuxingSourcingService 静态表 + submitSelfCheck 接 AI" ``` --- ## 任务 6:Java — Controller 新增接口 + HealthPlanService **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/family/FiveDimensionSelfCheckController.java` - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/HealthPlanService.java` - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/impl/HealthPlanServiceImpl.java` - [ ] **步骤 1:Controller 新增 generatePlan 接口** 在 `FiveDimensionSelfCheckController.java` 中追加: ```java @Resource private com.etotem.cfc.service.HealthPlanService healthPlanService; @Operation(summary = "用户点击生成健康计划(基于自检低分维度)") @PostMapping("/generate-plan") public Result> generatePlan( @RequestBody(required = false) Map body, @RequestAttribute("userId") Long userId) { if (userId == null) return Result.error("请先登录"); Long checkId = body != null ? com.etotem.cfc.util.ParamUtils.getLong(body.get("checkId")) : null; try { Long planId = healthPlanService.generateFromSelfCheck(userId, checkId); if (planId == null) return Result.error("计划生成失败,请稍后重试"); Map resp = new HashMap<>(); resp.put("planId", planId); resp.put("status", "draft"); return Result.success(resp); } catch (Exception e) { return Result.error("计划生成失败"); } } @Operation(summary = "获取自检历史趋势分析") @PostMapping("/trend-analysis") public Result> getTrendAnalysis(@RequestAttribute("userId") Long userId) { if (userId == null) return Result.error("请先登录"); try { return Result.success(selfCheckService.getTrendAnalysis(userId)); } catch (Exception e) { return Result.error("趋势分析失败"); } } ``` - [ ] **步骤 2:HealthPlanService 接口新增 generateFromSelfCheck** 在 `HealthPlanService.java` 中追加: ```java /** 基于自检低分维度自动生成 draft 计划 */ Long generateFromSelfCheck(Long userId, Long checkId); ``` - [ ] **步骤 3:HealthPlanServiceImpl 实现 generateFromSelfCheck** 在 `HealthPlanServiceImpl.java` 中追加(需新增 `@Resource private com.etotem.cfc.mapper.FiveDimensionSelfCheckMapper selfCheckMapper;`): ```java @Override public Long generateFromSelfCheck(Long userId, Long checkId) { try { com.etotem.cfc.entity.FiveDimensionSelfCheck check; if (checkId != null) { check = selfCheckMapper.selectById(checkId); } else { check = selfCheckMapper.selectOne( new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() .eq(com.etotem.cfc.entity.FiveDimensionSelfCheck::getUserId, userId) .orderByDesc(com.etotem.cfc.entity.FiveDimensionSelfCheck::getCreatedAt) .last("LIMIT 1")); } if (check == null) return null; Map scores = objectMapper.readValue(check.getScoresJson(), new com.fasterxml.jackson.core.type.TypeReference>() {}); List lowDims = new ArrayList<>(); for (Map.Entry e : scores.entrySet()) { if (e.getValue() != null && e.getValue() <= 6) lowDims.add(e.getKey()); } if (lowDims.isEmpty()) return null; com.etotem.cfc.entity.User user = userMapper.selectById(userId); if (user == null || user.getFamilyId() == null) return null; Map inputs = new LinkedHashMap<>(); inputs.put("familyId", user.getFamilyId()); inputs.put("dimensions", String.join(",", lowDims)); inputs.put("goal", "基于五维自检结果,针对低分维度制定改善计划"); String planContent = aiGateway.generateHealthPlan(inputs); if (planContent == null) return null; com.etotem.cfc.entity.HealthPlan plan = new com.etotem.cfc.entity.HealthPlan(); plan.setFamilyId(user.getFamilyId()); plan.setDimensions(String.join(",", lowDims)); plan.setGoal("五维自检自动生成"); plan.setPlanContent(planContent); plan.setStatus("draft"); plan.setCreatedAt(new Date()); healthPlanMapper.insert(plan); return plan.getId(); } catch (Exception e) { log.warn("generateFromSelfCheck 失败: {}", e.getMessage()); return null; } } ``` - [ ] **步骤 4:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 5:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/family/FiveDimensionSelfCheckController.java \ cfc-backend/src/main/java/com/etotem/cfc/service/HealthPlanService.java \ cfc-backend/src/main/java/com/etotem/cfc/service/impl/HealthPlanServiceImpl.java git commit -m "feat(self-check): 新增 /generate-plan + /trend-analysis 接口 + HealthPlanService.generateFromSelfCheck" ``` --- ## 任务 7:Java — AIChatController P1-2 context 注入 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java` - [ ] **步骤 1:sendMessage 支持 selfCheckId** 在 `AIChatController.java` 的 `sendMessage` 方法中,于第 72 行(`String surveyIdStr = params.get("surveyId");`)之后追加: ```java String selfCheckIdStr = params.get("selfCheckId"); ``` 在第 122 行(`inputs.put("portrait_prompt", chatPortrait);`)之后追加: ```java // 注入自检上下文(P1-2) if (selfCheckIdStr != null && !selfCheckIdStr.trim().isEmpty()) { try { Long selfCheckId = Long.valueOf(selfCheckIdStr); com.etotem.cfc.entity.FiveDimensionSelfCheck selfCheck = selfCheckMapper.selectById(selfCheckId); if (selfCheck != null) { inputs.put("self_check_result", objectMapper.writeValueAsString(selfCheck)); } } catch (Exception e) { log.warn("注入自检上下文失败: {}", e.getMessage()); } } ``` 需要新增注入(如未已有): ```java @Resource private com.etotem.cfc.mapper.FiveDimensionSelfCheckMapper selfCheckMapper; @Resource private com.fasterxml.jackson.databind.ObjectMapper objectMapper; ``` - [ ] **步骤 2:编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:BUILD SUCCESS - [ ] **步骤 3:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/controller/ai/AIChatController.java git commit -m "feat(self-check): AIChat sendMessage 支持 selfCheckId 注入上下文(P1-2)" ``` --- ## 任务 8:前端 — utils/api.js 新增方法 **文件:** - 修改:`cfc-frontend/utils/api.js` - [ ] **步骤 1:追加 2 个 API 方法** 在 `cfc-frontend/utils/api.js` 中,于 `ignoreSelfCheck` 方法之后追加: ```js export const generateSelfCheckPlan = (data) => { return request('/api/family/self-check/generate-plan', 'POST', data || {}) } export const getSelfCheckTrendAnalysis = () => { return request('/api/family/self-check/trend-analysis', 'POST', {}) } ``` - [ ] **步骤 2:语法校验** ```bash node --check cfc-frontend/utils/api.js && echo "api.js OK" ``` - [ ] **步骤 3:Commit** ```bash git add cfc-frontend/utils/api.js git commit -m "feat(self-check): 新增 generateSelfCheckPlan/getSelfCheckTrendAnalysis API" ``` --- ## 任务 9:前端 — self-check-result.vue 改造(P0-1/P0-2/P1-2) **文件:** - 修改:`cfc-frontend/pages/family/self-check-result.vue` - [ ] **步骤 1:替换寻源建议区块(P0-1)** 将第 70-131 行的 `` 区块(五行相生寻源建议 + 全部健康提示)整体替换为: ```html AI 健康解读 基于你的五维自检结果 {{ adv.dimensionName }} {{ adv.score }}分 {{ adv.interpretation }} 本周行动: {{ i+1 }}. {{ action }} 💡 家庭整体洞察 {{ result.advices[0].familyInsight }} AI 分析暂时不可用,请稍后再试 🌿 五维状态均健康,请继续保持这份平衡! ``` - [ ] **步骤 2:底部按钮区改造(P0-2 + P1-2)** 将第 134-137 行的底部按钮区替换为: ```html ``` - [ ] **步骤 3:script 改造** 在 `data()` 中追加 `planGenerating: false`;在 `computed` 中追加 `hasLowScore`;在 `methods` 中追加 `generatePlan` / `askAI`: ```javascript planGenerating: false, // data() 内 // computed 内 hasLowScore: function() { return this.result && this.result.advices && this.result.advices.some(function(a) { return a.score != null && a.score <= 6 }) }, // methods 内 generatePlan: function() { var self = this if (this.planGenerating) return this.planGenerating = true generateSelfCheckPlan({ checkId: this.result && this.result.id }).then(function(res) { self.planGenerating = false if (res.code === 200 && res.data && res.data.planId) { uni.showToast({ title: '计划已生成,可在健康计划页查看', icon: 'success' }) setTimeout(function() { uni.navigateTo({ url: '/pages/health-main/index?planId=' + res.data.planId }) }, 1000) } else { uni.showToast({ title: (res && res.message) || '计划生成失败', icon: 'none' }) } }).catch(function() { self.planGenerating = false uni.showToast({ title: '计划生成失败', icon: 'none' }) }) }, askAI: function() { var selfCheckId = this.result && this.result.id if (!selfCheckId) { uni.showToast({ title: '暂无自检记录', icon: 'none' }) return } uni.navigateTo({ url: '/pages/ai/chat?selfCheckId=' + selfCheckId }) } ``` - [ ] **步骤 4:import 追加** 在文件头部 import 区追加: ```javascript import { generateSelfCheckPlan } from '@/utils/api' ``` - [ ] **步骤 5:样式追加** 在 `