2026-08-06-diet-module-design.md 21 KB

饮食模块设计规格说明书

日期: 2026-08-06
状态: 待审查
作者: Sisyphus(Brainstorming 协作输出)


1. 项目背景

饮食模块是"身"维度的核心执行层,目标链路:菌群报告 → 饮食偏好调研 → 食材推荐 → 食谱生成 → 饮食记录 → 持续优化推荐


2. 现有底座(复用,不动)

模块 文件 复用方式
菌群报告上传/解析/存储 HealthReportController, PdfParseService, HealthReportService 直接调用 report/list, report/latest, report/detail
菌群→食材推荐引擎 BeijingNutritionService, bacteria_food_mapping.json 扩展 generateIngredientList() 方法
家庭/成员/辈分体系 family_members, family_relationships, GenerationLevel 扩展 is_living_together, meal_with_* 字段
营养档案服务 FamilyMemberNutritionProfileService 直接调用 getProfile(memberId)
家庭访问拦截 FamilyAccessInterceptor 新 Controller 自动纳入拦截
AI 网关 AiGateway(LangGraph 接入) 扩展 recognizeFood(imageUrl), generateMenu(...) 方法
食材数据底座 foods, report_food_suitability, food_recommend_idx 直接查询、写入

3. 新增数据模型

3.1 family_members 表扩展(迁移新增 7 字段)

-- 迁移: family_members 表添加饮食相关字段(饮食模块需求)
ALTER TABLE family_members
  ADD COLUMN is_living_together TINYINT(1) DEFAULT 1 COMMENT '是否同住(0=不同住,不出现在食谱推荐中)',
  ADD COLUMN meal_with_breakfast_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日早餐同餐',
  ADD COLUMN meal_with_lunch_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日午餐同餐',
  ADD COLUMN meal_with_dinner_weekday TINYINT(1) DEFAULT 0 COMMENT '工作日晚餐同餐',
  ADD COLUMN meal_with_breakfast_weekend TINYINT(1) DEFAULT 0 COMMENT '周末早餐同餐',
  ADD COLUMN meal_with_lunch_weekend TINYINT(1) DEFAULT 0 COMMENT '周末午餐同餐',
  ADD COLUMN meal_with_dinner_weekend TINYINT(1) DEFAULT 0 COMMENT '周末晚餐同餐';

3.2 diet_preferences 表(调研表,每成员一份)

CREATE TABLE IF NOT EXISTS diet_preferences (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  family_member_id BIGINT NOT NULL COMMENT '绑定家庭成员',
  -- 硬性(必填)
  allergies JSON COMMENT '过敏原列表(如 ["花生","海鲜","牛奶"])',
  absolute_avoid JSON COMMENT '绝对忌口列表(如 ["猪肉","酒精"])',
  religious_diet VARCHAR(50) DEFAULT 'none' COMMENT '宗教饮食:none/清真/素食/纯素/印度素',
  -- 软性(可后填)
  spice_level TINYINT DEFAULT NULL COMMENT '辣度 0-5',
  flavor_pref JSON COMMENT '口味偏好(酸/甜/咸/鲜/苦)',
  cuisine_pref JSON COMMENT '菜系偏好(川/粤/淮扬/日料/西餐)',
  cooking_methods JSON COMMENT '偏好烹饪方式(炒/蒸/煮/烤/凉拌)',
  -- 目标(系统推荐+用户确认)
  health_goals JSON COMMENT '健康目标(减脂/增肌/控糖/肠道调理/护肝)',
  goal_source VARCHAR(20) DEFAULT NULL COMMENT 'manual/system_recommend',
  goal_confirmed TINYINT(1) DEFAULT 0 COMMENT '用户已确认(0=未确认/待确认)',
  -- 元数据
  filled_at TIMESTAMP NULL COMMENT '首次填写时间',
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_member (family_member_id),
  INDEX idx_updated (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食偏好调研表(每成员一份)';

3.3 meal_configs 表(共餐配置,按家庭)

CREATE TABLE IF NOT EXISTS meal_configs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  family_id BIGINT NOT NULL COMMENT '家庭ID',
  config_date_type VARCHAR(20) NOT NULL COMMENT '工作日/周末/节假日',
  meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner',
  participant_member_ids JSON NOT NULL COMMENT '参与成员ID列表',
  notes VARCHAR(255) DEFAULT NULL COMMENT '备注',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_config (family_id, config_date_type, meal_type),
  INDEX idx_family (family_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='共餐配置表(按家庭+日期类型+餐次)';

3.4 diet_recommendations 表(食谱方案)

CREATE TABLE IF NOT EXISTS diet_recommendations (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  family_id BIGINT NOT NULL COMMENT '家庭ID',
  recommendation_date DATE NOT NULL COMMENT '推荐日期',
  meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner',
  participant_member_ids JSON COMMENT '参与成员ID列表',
  menu_json MEDIUMTEXT NOT NULL COMMENT '菜品列表JSON(结构见3.5)',
  nutrition_summary JSON COMMENT '营养汇总(calories/protein/carbs/fat/prebiotic/probiotic)',
  status VARCHAR(20) DEFAULT 'pending' COMMENT 'pending/accepted/adjusted/completed/skipped',
  version INT DEFAULT 1 COMMENT '版本号(重新生成+1)',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_family_date (family_id, recommendation_date),
  INDEX idx_meal (meal_type, recommendation_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食谱推荐方案表';

3.5 diet_recommendations.menu_json 结构

{
  "meals": [
    {
      "name": "番茄炒蛋",
      "ingredients": [
        {"name": "西红柿", "food_id": 123, "grams": 300},
        {"name": "鸡蛋", "food_id": 45, "grams": 2}
      ],
      "cooking_method": "快炒",
      "nutrition": {"calories": 360, "protein": 24, "carbs": 16, "fat": 20, "prebiotic": 4, "probiotic": 0},
      "notes": "低油少盐,适合肠道调理"
    }
  ]
}

3.6 diet_records 表(饮食记录)

CREATE TABLE IF NOT EXISTS diet_records (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  family_id BIGINT NOT NULL COMMENT '家庭ID',
  member_id BIGINT NOT NULL COMMENT '吃的成员ID',
  meal_type VARCHAR(20) NOT NULL COMMENT 'breakfast/lunch/dinner/snack',
  record_date DATE NOT NULL COMMENT '记录日期',
  record_method VARCHAR(20) DEFAULT NULL COMMENT 'photo/manual/from_recommendation',
  image_url VARCHAR(500) DEFAULT NULL COMMENT '原图OSS路径(photo方法时填写)',
  ai_recognized_foods JSON DEFAULT NULL COMMENT 'AI识别结果(photo方法时填写)',
  user_confirmed_foods JSON NOT NULL COMMENT '用户最终确认食材',
  notes VARCHAR(500) DEFAULT NULL COMMENT '备注',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_member_date (member_id, record_date),
  INDEX idx_family_date (family_id, record_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食记录表';

3.7 diet_record_items 表(饮食记录食材明细)

CREATE TABLE IF NOT EXISTS diet_record_items (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  record_id BIGINT NOT NULL COMMENT 'diet_records.id',
  food_name VARCHAR(100) NOT NULL COMMENT '食材名称',
  food_id BIGINT DEFAULT NULL COMMENT '关联foods表ID(可选)',
  confidence DECIMAL(3,2) DEFAULT NULL COMMENT 'AI置信度0-1(手动录入为空)',
  source VARCHAR(20) DEFAULT NULL COMMENT 'ai_recognized/manual/from_recommendation',
  INDEX idx_record (record_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='饮食记录食材明细表';

4. 核心流程

4.1 调研引导流程

用户首次进入 /pages/diet/index
  ↓
GET /api/diet/preferences/current-member
  → 无记录 → 返回 404
  ↓
前端弹"调研引导弹窗"
  → 必填:过敏原、绝对忌口、宗教饮食
  → 可选:辣度、口味、菜系、烹饪方式
  → 系统推荐目标(基于 disease_risks)→ 用户确认
  ↓
POST /api/diet/preferences/save
  → 写入 diet_preferences,filled_at = now()
  → goal_confirmed 字段记录是否确认
  ↓
首页刷新,显示推荐食材

4.2 食材推荐流程

用户进入饮食首页
  ↓
GET /api/diet/ingredients/suggest
  → 查 meal_configs → participant_member_ids
  → 查每个 member 的 diet_preferences + health_gut_flora
  → 过滤禁忌食材(过敏+忌口+宗教)
  → 计算推荐分:
    - food_recommend_idx 评分(0-100)
    - bacteria_food_mapping 映射加分/减分
    - 目标匹配加权
  → 返回 Top 15 食材列表(含推荐理由)
  ↓
前端展示"已选食材"列表(会话内)
  → 用户可:换一批 / 添加 / 删除
  ↓
POST /api/diet/ingredients/recommend(换一批)
  → 同 get suggest,但随机种子变化
  ↓
POST /api/diet/ingredients/add(添加食材)
  Body: { food_id, name }
  → 前端会话内追加,不持久化
  ↓
POST /api/diet/ingredients/remove(删除食材)
  Body: { food_id }
  → 前端会话内移除
  ↓
POST /api/diet/ingredients/confirm(确认并生成食谱)
  Body: { selected_foods: [{food_id, name}], date: "2026-08-06" }
  → 调 AiGateway.generateMenu(selected_foods, participants, date)
    → LangGraph 文本节点生成菜品 + 克数(按人数自动算)
  → 写入 diet_recommendations(version=1)
  → 返回推荐方案

4.3 饮食记录流程

用户点击"今日饮食" → 拍早餐
  ↓
uni.chooseImage → 上传 OSS → 拿到 image_url
  ↓
POST /api/diet/record/recognize
  Body: { image_url }
  → AiGateway.recognizeFood(image_url)
    → LangGraph 视觉节点识别食材 + 置信度
    → 输出:[{name, confidence, category}]
  → 返回识别结果
  ↓
前端展示"AI 识别结果"列表,用户可勾选/修改/增删
  ↓
POST /api/diet/record/save
  Body: {
    member_id, meal_type, record_method: "photo",
    image_url, ai_recognized_foods, user_confirmed_foods
  }
  → 写入 diet_records + diet_record_items
  → 返回记录 ID
  ↓
前端跳转到"今日饮食记录"页

4.4 目标自动推断流程

用户填写调研表时,系统推荐健康目标:
  ↓
查 family_member 对应 user 的 health_reports.disease_risks
  ↓
规则映射:
  - 糖尿病高风险 → 推荐 "控糖"
  - 肥胖 → 推荐 "减脂"
  - 肠道菌群失衡(gut_balance_score < 60) → 推荐 "肠道调理"
  - 高血压 → 推荐 "低钠"
  ↓
输出推荐目标列表 → 前端展示"系统推荐目标:减脂 + 肠道调理"
  → 用户确认 → 写入 diet_preferences.health_goals,goal_confirmed=1
  → 用户不确认 → 留空,后续手动填

5. AI 网关扩展

5.1 AiGateway.recognizeFood(imageUrl)

POST /api/ai/food/recognize
  Body: { image_url: "https://..." }

AiGatewayService.recognizeFood(imageUrl)
  → prompt: "识别图片中的主要食材,返回 JSON 格式:[{name, confidence, category}]"
  → 调 LangGraph /api/v1/chat/process(视觉节点)
  → fallback:失败时返回空数组
  → 返回:{ foods: [{name, confidence, category}], raw_response }

约束

  • 不引入新模型,复用现有 LangGraph 工作流
  • 图片先传到 OSS(复用健康报告上传流程)
  • 单次调用超时 3s,超时降级为纯手动

5.2 AiGateway.generateMenu(selectedFoods, participants, date)

POST /api/ai/menu/generate
  Body: {
    selected_foods: [{food_id, name}],
    participants: [member_ids],
    date: "2026-08-06"
  }

AiGatewayService.generateMenu(selectedFoods, participants, date)
  → prompt: "根据以下食材和用餐人数,生成一日三餐菜单..."
    输入:
    - 食材列表
    - 用餐人数(participants.length)
    - 日期
    - 营养目标(从 diet_preferences.health_goals 取)
    - 禁忌(从 allergies + absolute_avoid + religious_diet 取)
  → LangGraph 文本节点生成菜单
  → 输出:{ meals: [{name, ingredients: [{name, grams}], cooking_method, nutrition}] }
  → 返回 menu_json

克数自动计算:AI 根据 participants.length 自动推算克数,无需前端传入。


6. 后端 API 设计

6.1 新增 Controller

DietPreferencesController
  POST /api/diet/preferences/current-member  → 查当前成员调研表
  POST /api/diet/preferences/save            → 保存/更新调研表
  POST /api/diet/preferences/system-recommend → 系统推荐目标(基于 disease_risks)

DietIngredientController
  POST /api/diet/ingredients/suggest         → 系统推荐今日食材
  POST /api/diet/ingredients/recommend       → 换一批
  POST /api/diet/ingredients/add             → 手动添加食材(会话内)
  POST /api/diet/ingredients/remove          → 删除食材
  POST /api/diet/ingredients/confirm         → 确认食材,生成食谱

DietRecommendationController
  POST /api/diet/recommendation/today        → 查今日推荐
  POST /api/diet/recommendation/generate     → 生成推荐
  POST /api/diet/recommendation/update       → 手动调整菜品
  POST /api/diet/recommendation/regenerate   → 重新生成(version+1)
  POST /api/diet/recommendation/complete     → 标记完成

DietRecordController
  POST /api/diet/record/recognize            → AI 识别图片食材
  POST /api/diet/record/save                 → 保存饮食记录
  POST /api/diet/record/daily                → 查今日饮食汇总
  POST /api/diet/record/weekly               → 查本周趋势

DietMealConfigController
  POST /api/diet/meals/config                → 查共餐配置
  POST /api/diet/meals/config/save           → 保存共餐配置

6.2 响应结构(统一)

{
  "code": 200,
  "message": "success",
  "data": { ... }
}

7. 前端页面规划

7.1 分包结构

pages.jsonsubPackages 中注册 pages/diet/

{
  "subPackages": [
    {
      "root": "pages/diet",
      "pages": [
        { "path": "index", "style": { "navigationBarTitleText": "饮食" } },
        { "path": "preferences", "style": { "navigationBarTitleText": "饮食偏好调研" } },
        { "path": "food-query", "style": { "navigationBarTitleText": "食材查询" } },
        { "path": "meal-config", "style": { "navigationBarTitleText": "共餐配置" } },
        { "path": "recommendation", "style": { "navigationBarTitleText": "食谱推荐" } },
        { "path": "records", "style": { "navigationBarTitleText": "饮食记录" } },
        { "path": "record-detail", "style": { "navigationBarTitleText": "记录详情" } }
      ]
    }
  ]
}

7.2 页面清单

页面 路径 主要功能
饮食首页 pages/diet/index.vue 食材推荐器 + 今日共餐摘要 + 5 个入口卡片
调研表 pages/diet/preferences.vue 填写/编辑饮食偏好(硬性必填 + 软性可选)
食材查询 pages/diet/food-query.vue 搜索食材 + 适合/不适合筛选
共餐配置 pages/diet/meal-config.vue 工作日/周末早午晚 + 参与者勾选
食谱推荐 pages/diet/recommendation.vue 展示生成好的食谱(菜品 + 克数 + 操作)
饮食记录 pages/diet/records.vue 饮食记录列表(按日期分组)
记录详情 pages/diet/record-detail.vue 单条记录详情(含照片、识别结果、确认食材)

7.3 首页布局

┌─────────────────────────────────────────────┐
│ 今日食材推荐(2026-08-06)                   │
│ 基于你的菌群报告 + 饮食偏好                   │
├─────────────────────────────────────────────┤
│ [换一批] [添加食材] [生成食谱]               │
├─────────────────────────────────────────────┤
│ 已选食材(可删除)                          │
│ 🥚 鸡蛋                       [🗑]          │
│ 🍅 西红柿                       [🗑]          │
│ 🥬 菠菜                       [🗑]          │
│ 🍚 小米                       [🗑]          │
│ [已选 4 种,继续添加或生成食谱]             │
├─────────────────────────────────────────────┤
│ 今日共餐摘要                                │
│ [早餐: 张三 | 午餐: 全员 | 晚餐: 张三、李四] │
├─────────────────────────────────────────────┤
│ 本周饮食趋势                                │
│ 平均摄入 1800kcal / 推荐 1600kcal           │
└─────────────────────────────────────────────┘

7.4 组件规划

组件 位置 用途
MealSummaryCard.vue pages/diet/components/ 今日共餐摘要卡片
IngredientCard.vue pages/diet/components/ 单条食材卡片
WeeklyTrendCard.vue pages/diet/components/ 本周饮食趋势
FoodPickerDialog.vue pages/diet/components/ 添加食材弹窗
RecipeMenuItem.vue pages/diet/components/ 单道菜品卡片

8. 后端 Service 新增

8.1 BeijingNutritionService 扩展

新增方法:

/**
 * 生成食材推荐列表(供饮食首页使用)
 *
 * @param familyId 家庭ID
 * @param date     日期
 * @return 推荐食材列表(Top 15,含推荐理由)
 */
public List<IngredientRecommendation> generateIngredientList(Long familyId, LocalDate date) {
  // 1. 查 meal_configs → participant_member_ids
  // 2. 查每个 member 的 diet_preferences + health_gut_flora
  // 3. 过滤禁忌食材(过敏+忌口+宗教)
  // 4. 计算推荐分:food_recommend_idx + bacteria_food_mapping + 目标匹配
  // 5. 返回 Top 15
}

/**
 * 生成一日三餐菜单
 *
 * @param selectedFoods 用户选定的食材
 * @param participants  参与用餐的成员ID列表
 * @param date          日期
 * @return 菜单 JSON(含克数,由 AI 根据人数自动计算)
 */
public String generateMenu(List<IngredientRecommendation> selectedFoods, 
                           List<Long> participants, LocalDate date) {
  // 调 AiGateway.generateMenu()
}

8.2 新增 DietRecordService

@Service
public class DietRecordService {
  
  /**
   * AI 识别图片食材
   */
  public RecognizeFoodResult recognizeFood(String imageUrl) {
    return aiGateway.recognizeFood(imageUrl);
  }
  
  /**
   * 保存饮食记录
   */
  public Long saveRecord(DietRecordSaveRequest request) {
    // 写入 diet_records + diet_record_items
  }
  
  /**
   * 查今日饮食汇总
   */
  public DailyDietSummary getDailySummary(Long memberId, LocalDate date) {
    // 聚合 diet_records + 营养计算
  }
  
  /**
   * 查本周趋势
   */
  public WeeklyDietTrend getWeeklyTrend(Long memberId, LocalDate startDate) {
    // 聚合 + 趋势计算
  }
}

8.3 新增 DietRecommendationService

@Service
public class DietRecommendationService {
  
  /**
   * 查今日推荐
   */
  public TodayRecommendation getTodayRecommendation(Long familyId, LocalDate date) {
    // 查 diet_recommendations WHERE recommendation_date=? AND status IN ('pending','accepted','completed')
  }
  
  /**
   * 生成推荐
   */
  public DietRecommendation generateRecommendation(Long familyId, LocalDate date, String mealType) {
    // 1. 查 meal_configs → participant_member_ids
    // 2. 查每个 member 的 diet_preferences + health_gut_flora
    // 3. 调 BeijingNutritionService.generateIngredientList()
    // 4. 调 AiGateway.generateMenu()
    // 5. 写入 diet_recommendations
  }
}

9. 实施计划(待细化)

Phase 1:数据模型 + 后端基础

  • 执行迁移(7 张表/字段变更)
  • 实现 DietPreferencesController + DietPreferencesService
  • 实现 DietIngredientController + BeijingNutritionService.generateIngredientList()
  • 实现 DietRecommendationController + DietRecommendationService
  • mvn clean compile 验证

Phase 2:AI 扩展

  • 实现 AiGateway.recognizeFood()(视觉节点)
  • 实现 AiGateway.generateMenu()(文本节点)
  • 实现 DietRecordController + DietRecordService
  • 测试识别链路(失败降级)

Phase 3:前端

  • 注册 pages/diet/ 分包
  • 实现 pages/diet/index.vue(食材推荐器)
  • 实现 pages/diet/preferences.vue(调研表)
  • 实现 pages/diet/recommendation.vue(食谱展示)
  • 实现 pages/diet/records.vue(饮食记录)
  • 实现其他 3 个页面

Phase 4:集成测试

  • 端到端流程测试(调研 → 推荐 → 生成 → 记录)
  • 边界测试(无调研表、无菌群报告、无共餐配置)
  • 性能测试(AI 识别延迟、批量推荐)

10. 规格自检

检查项 结果
占位符扫描 ✅ 无"待定"/"TODO"
内部一致性 ✅ 数据流、接口、页面逻辑自洽
范围检查 ✅ 聚焦单一模块,不混入无关功能
模糊性检查 ✅ 每个需求有唯一解释
迁移工作流 ✅ 符合 AGENTS.md 规范(ensureColumn + schema.sql 同步)
接口规范 ✅ 全部用 @PostMapping
Bean 命名 ✅ 新 Controller/Service 无重名风险
小程序限制 ✅ 未使用可选链/CSS Grid/:key 表达式

文档 commit 后请审查。如有修改意见请告知,批准后进入 writing-plans 创建实现计划。