2026-08-09-health-data-center-plan.md 15 KB

健康数据中心 — 实现计划

源文档: docs/superpowers/specs/2026-08-08-health-data-center-design.md 日期: 2026-08-09 分支: cfclub


一、Codebase 现状核查(与 spec 差异)

项 spec 描述 实际现状 影响
身维度首页路径 pages/body/index.vue 不存在;实际为 pages/body-detail/index.vue(pages.json:883-890) 改造目标文件需修正
迁移编号 spec §8.2 写"迁移194" 当前最高迁移为 迁移203(notices表) 新迁移编号应为 迁移204
dimension-questionnaire 维度 spec §6.1 要新增 cognitive/mental/relationship 现有仅 7 个体质维度(growth/sleep/vision/immunity/nutrition/gut/exercise) 需新增题目+后端 DIMENSIONS 列表扩展
LangGraph 端点 spec §7.2 调用 analysis/run Python 侧 /api/v1/analysis/run 存在(cfc-langgraph/app/api/adapter.py:124);Java 侧需走 AiGateway 需补充 AiGateway 方法
health_plans 表 spec §7.3 建表 不存在 需新建
RadarChart 复用 spec 未说明 components/RadarChart.vue:34-43 已支持 props {dimensions, scores, avgScores} 无需扩展,直接复用

二、任务分解

Batch A — 数据库 + 实体(串行,依赖无)

任务 1: 迁移204 — 创建 health_plans 表

文件: cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java(在迁移203后追加)

DDL(与 spec §7.3 一致,补充 deleted 字段支持软删除):

CREATE TABLE IF NOT EXISTS health_plans (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  family_id BIGINT NOT NULL COMMENT '家庭ID',
  member_ids VARCHAR(200) NOT NULL COMMENT '目标成员ID列表(逗号分隔)',
  dimensions VARCHAR(100) NOT NULL COMMENT '维度列表(逗号分隔)',
  goal TEXT NOT NULL COMMENT '用户输入的目标',
  plan_content TEXT COMMENT 'LangGraph生成的方案内容(JSON/HTML)',
  member_name VARCHAR(50) COMMENT '成员名称摘要',
  deleted TINYINT DEFAULT 0 COMMENT '软删标记(0=正常 1=已删)',
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_family (family_id),
  INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康方案汇总记录';

同步修改 cfc-backend/src/main/resources/schema.sql(文件末尾追加 CREATE TABLE)。

QA 场景:

  • mvn clean compile 通过
  • 启动后查日志 "已创建health_plans表"
  • 连接数据库执行 DESCRIBE health_plans 确认字段存在

任务 2: HealthPlan 实体 + Mapper

新建:

  • cfc-backend/src/main/java/com/etotem/cfc/entity/HealthPlan.java(字段与 DDL 一一对应)
  • cfc-backend/src/main/java/com/etotem/cfc/mapper/HealthPlanMapper.java(extends BaseMapper)
  • QA 场景:

    • mvn clean compile 通过,无编译错误

    Batch B — 服务层 + API 契约(可并行)

    任务 3: HealthPlanService + ServiceImpl

    新建:

    • cfc-backend/src/main/java/com/etotem/cfc/service/HealthPlanService.java
    • cfc-backend/src/main/java/com/etotem/cfc/service/impl/HealthPlanServiceImpl.java

    接口方法:

    Long savePlan(Long familyId, Long adminId, String memberIds, String dimensions, String goal, String planContent, String memberName);
    List<HealthPlan> listPlans(Long familyId, Integer page, Integer size);
    HealthPlan getPlan(Long id, Long userId);   // 校验 familyId 归属
    void deletePlan(Long id, Long userId);      // 软删除:deleted=1,校验归属
    

    QA 场景:

    • mvn clean compile 通过
    • 单元测试(如有)或手动调接口验证 CRUD

    任务 4: AiGateway — 新增 generateHealthPlan 方法

    文件: cfc-backend/src/main/java/com/etotem/cfc/service/AiGateway.java

    新增方法(与现有 generateQuestionnaire 行185-216 模式一致):

    public String generateHealthPlan(Map<String, Object> inputs) {
        if (!enabled || baseUrl == null) return null;
        try {
            String url = baseUrl + "/api/v1/analysis/run";
            HttpEntity<String> entity = new HttpEntity<>(JSON.toJSONString(inputs), headers());
            ResponseEntity<String> resp = restTemplate.postForEntity(url, entity, String.class);
            // 解析返回的 content/result 字段
            return extractContent(resp.getBody());
        } catch (Exception e) {
            log.warn("AiGateway generateHealthPlan 失败: {}", e.getMessage());
            return null;
        }
    }
    

    QA 场景:

    • mvn clean compile 通过
    • 单元测试或手动调 AiGateway(Python 服务未运行时返回 null,不抛异常)

    任务 5: DimensionScoreService DIMENSIONS 列表扩展

    文件: cfc-backend/src/main/java/com/etotem/cfc/service/impl/DimensionScoreServiceImpl.java

    在 DIMENSIONS 列表(行39-47)末尾追加:

    new String[]{"cognitive",    "认知能力"},
    new String[]{"mental",       "心理健康"},
    new String[]{"relationship", "关系质量"},
    

    注意: submitQuestionnaire 方法(行208-237)无维度白名单校验,传入新维度码可直接写入 health_dimension_scores 表。但 getDimensionOverview 和 getDimensionHistory 依赖 DIMENSIONS 列表来展示,扩展后前端可正常获取数据。

    QA 场景:

    • mvn clean compile 通过
    • 调 POST /api/dimension/overview?dimension=cognitive 返回数据结构与现有维度一致

    任务 6: 前端 API 函数声明(无需等后端编译)

    文件: cfc-frontend/utils/api.js

    新增:

    export const generateHealthPlan = (data) => {
      return request('/api/health/plan/generate', 'POST', data)
    }
    export const getHealthPlanList = (familyId) => {
      return request('/api/health/plan/list', 'POST', { familyId })
    }
    export const saveHealthPlan = (data) => {
      return request('/api/health/plan/save', 'POST', data)
    }
    export const deleteHealthPlan = (id) => {
      return request('/api/health/plan/delete', 'POST', { id })
    }
    

    QA 场景:

    • 语法检查:node -e "require('./utils/api.js')" 无 Unexpected token

    任务 7: pages.json 注册 health-plan-summary 页面

    文件: cfc-frontend/pages.json,在 pages/health 分包末尾追加:

    {
      "path": "health-plan-summary",
      "style": {
        "navigationBarTitleText": "方案汇总"
      }
    }
    

    QA 场景:

    • HBuilderX 或命令行检查 pages.json JSON 语法:node -e "JSON.parse(require('fs').readFileSync('pages.json','utf8'))" 无异常

    Batch C — Controller 层(依赖 Batch A+B 的实体/服务)

    任务 8: HealthPlanController(小程序前端接口)

    新建: cfc-backend/src/main/java/com/etotem/cfc/controller/health/HealthPlanController.java

    端点: | 方法 | 路径 | 说明 | |---|---|---| | POST | /api/health/plan/generate | 调 LangGraph 生成方案并返回结果 | | POST | /api/health/plan/save | 保存方案到 health_plans 表 | | POST | /api/health/plan/list | 查询当前用户家庭的历史方案列表 | | POST | /api/health/plan/detail | 查询方案详情 | | POST | /api/health/plan/delete | 软删除方案 |

    QA 场景:

    • mvn clean compile 通过
    • 用 curl 手动调 /api/health/plan/list 返回 JSON(未登录返回 401)

    任务 9: AdminHealthPlanController(管理后台接口,可选 MVP)

    新建: cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminHealthPlanController.java

    端点:

    • POST /api/admin/health-plans/list — 分页查询全平台方案列表
    • POST /api/admin/health-plans/delete — 管理员强制删除

    QA 场景:

    • mvn clean compile 通过

    Batch D — 小程序前端页面(依赖 Batch C 的 API 端点)

    任务 10: dimension-questionnaire.vue 扩展

    文件: cfc-frontend/pages/body-detail/dimension-questionnaire.vue(546 行)

    改动:

    1. dimensions 数组(行95-103)新增 3 项(spec §6.1)
    2. questions 对象(行105-221)新增 cognitive/mental/relationship 三组题库(spec §6.2-6.4,每组 8 题)
    3. onLoad 增加 dim 参数支持(spec §6.5):

      onLoad(options) {
       if (options.dim) {
         this.selectedDimension = options.dim
         this.step = 2  // 跳过选维度,直接进入答题
       }
       // 原有逻辑保留
      }
      
    4. 答题完成结果页新增"返回数据中心"按钮(跳回 body-detail/index.vue)

    QA 场景:

    • HBuilderX 打包无语法错误
    • 微信开发者工具打开 /pages/body-detail/dimension-questionnaire?dim=cognitive&memberId=1,直接进入答题页

    任务 11: body-detail/index.vue 改造为健康数据中心

    文件: cfc-frontend/pages/body-detail/index.vue(676 行)

    改造要点:

    1. 新增 Tab 栏(PageBanner 下方):身(橙)/智(靛)/心(粉)/行(绿),切换时仅刷新雷达图区域
    2. 雷达图随 Tab 切换:
      • 身 → 现有七维雷达图(growth/sleep/vision/immunity/nutrition/gut/exercise)
      • 智 → 五维蛛网图(记忆力/注意力/逻辑推理/空间想象/语言表达),color=#6366F1
      • 心 → 五维图(情绪稳定性/抗压能力/自我认知/社交意愿/幸福感),color=#FF6B9D
      • 行 → 五维图(亲子关系/夫妻关系/亲友关系/同事关系/社区参与),color=#10B981
    3. 移除 funcList 功能网格(运动/饮食/作息/冥想/舌诊/健康维度)
    4. 新增"看见·健康数据中心"区块:
      • 数据采集卡片1:上传健康报告 → /pages/health/report-upload
      • 数据采集卡片2:在线测评(认知/心理/关系 三个入口,各带开始/已完成分数)
      • 数据查看卡片:最近报告/测评摘要(无数据时显示空状态提示)
      • 方案汇总卡片:上次方案摘要 + [生成新方案] 按钮 → /pages/health/health-plan-summary
    5. 弱化活动/商品/文章区(收窄宽度至 80%,opacity 0.7)
    6. 保留:RadarChart、FamilyMemberStrip、FamilyEnergyBar、AIFloatingAvatar

    QA 场景:

    • HBuilderX 打包无语法错误
    • 微信开发者工具:切换身/智/心/行 Tab,雷达图标签和颜色随之变化;无数据时显示"上传报告后显示个人数据"

    任务 12: 新建 health-plan-summary.vue

    新文件: cfc-frontend/pages/health/health-plan-summary.vue

    页面结构(spec §7.1):

    • 顶部导航栏(← 返回)
    • 生成新方案表单:
      • 维度多选(健康/认知/心理/社会性)
      • 成员多选(从家庭列表选取,调用 getVisibleFamilyMembers)
      • 目标输入框(textarea,placeholder:"例如:改善孩子睡眠质量")
      • [生成方案] 按钮 → 调 generateHealthPlan API
    • 方案结果展示区(HTML 富文本渲染):
      • [保存方案] → 调 saveHealthPlan API
      • [重新生成] → 再次调 generateHealthPlan
    • 历史方案列表(按时间倒序,调 getHealthPlanList)

    QA 场景:

    • HBuilderX 打包无语法错误
    • 微信开发者工具:填写表单 → 生成方案 → 保存 → 返回列表出现新条目

    三、依赖关系图

    任务1 (迁移204) ──→ 任务2 (实体+Mapper) ──→ 任务3 (Service)
                                                           │
    任务6 (API函数) ←──────────────────────────────────────┘
    任务7 (pages.json) ←───────────────────────────────────┘
                                                           │
                                  ┌────────────────────────┼────────────────────────┐
                                  ▼                        ▼                        ▼
                             任务4 (AiGateway)       任务5 (DIMENSIONS)      任务8 (Controller)
                                  │                        │                        │
                                  └────────────────────────┴────────────────────────┘
                                                           │
                                                  任务9 (AdminController,可选)
                                                           │
                                  ┌────────────────────────┴────────────────────────┐
                                  ▼                                                 ▼
                             任务10 (问卷扩展)                              任务11 (body-detail改造)
                                  │                                                 │
                                  └────────────────────────┬────────────────────────┘
                                                           ▼
                                                     任务12 (方案汇总页)
    

    四、执行顺序(3个批次)

    批次 1(串行,无并行依赖)

    • 任务 1 → 任务 2(实体依赖 DDL)

    批次 2(4项可并行)

    • 任务 3(HealthPlanService,依赖任务2)
    • 任务 4(AiGateway,无依赖)
    • 任务 5(DIMENSIONS扩展,无依赖)
    • 任务 6(API函数声明,无依赖)
    • 任务 7(pages.json注册,无依赖)

    批次 3(串行,依赖批次2)

    • 任务 8(Controller,依赖任务3+4)
    • 任务 10(问卷扩展,依赖任务5)
    • 任务 11(body-detail改造,依赖任务8 API端点)
    • 任务 12(方案汇总页,依赖任务8+10+11)

    五、风险点

    风险 影响 缓解
    LangGraph Python 服务 /analysis/run 未部署或返回异常 方案生成功能不可用 AiGateway 已有熔断器,返回 null;Controller 层做 mock fallback(返回预置方案文本)
    body-detail/index.vue 现有样式复杂(676行,含大量残留样式) 改造时易引入样式冲突 保留 RadarChart/FamilyMemberStrip/FamilyEnergyBar 组件引用,仅替换 funcList 下方区块;新样式加 .health-center- 前缀避免冲突
    dimension-questionnaire 跳转 URL 参数变化 现有链接失效 dim 参数为可选,不传时走原有选维度流程
    五维雷达图标签数据从哪来 智/心/行 Tab 的 radarDimensions 如何填充 复用 getDimensionOverview,传 dimension=cognitive/mental/relationship;后端 DIMENSIONS 列表扩展后,返回对应五维分值
    迁移204 幂等性 重复部署不报错 使用 CREATE TABLE IF NOT EXISTS(与现有迁移203一致)

    六、验收标准

    1. 任务1+2: mvn clean compile 通过;数据库有 health_plans 表,含 deleted 字段
    2. 任务3+8: 调 POST /api/health/plan/save 写入一条记录;调 POST /api/health/plan/list 返回该记录
    3. 任务4: AiGateway.generateHealthPlan 在无 Python 服务时返回 null(不抛异常)
    4. 任务5: 调 POST /api/dimension/overview?dimension=cognitive 返回正常数据结构(即使分值为0)
    5. 任务10: 微信开发者工具打开 ?dim=cognitive&memberId=1 直接进入答题页,8题完整可作答
    6. 任务11: 切换身/智/心/行 Tab,雷达图标签和颜色正确变化;数据中心区块可见
    7. 任务12: 生成方案 → 保存 → 返回列表出现新条目;历史方案可点击查看
    8. 编译: 无 Java 编译错误(mvn clean compile);无 Vue/JS 语法错误(HBuilderX 打包无 Unexpected token)