# 用户使用流程图 — 营养与饮食推荐 > **端口说明**: 📱 小程序 | 🖥 管理后台 | 📋 规划师端 | ⚙️ 系统自动 > > **分层说明**: 🏠 页面 → 🔗 API → ⚙️ Service → 📦 数据实体 ```mermaid flowchart TD %% ============ 颜色定义 ============ classDef page fill:#e3f2fd,stroke:#1565c0,stroke-width:2px classDef api fill:#fff3e0,stroke:#f57c00,stroke-width:1px classDef service fill:#e8f5e9,stroke:#388e3c,stroke-width:1px classDef data fill:#f3e5f5,stroke:#7b1fa2,stroke-width:1px classDef external fill:#fce4ec,stroke:#d32f2f,stroke-width:1px,stroke-dasharray:3 2 classDef actor fill:#e1f5fe,stroke:#0288d1,stroke-width:2px,stroke-dasharray:5 3 %% ============ 角色 ============ A0(("👤 用户/家长")):::actor %% ================================================================ %% 阶段一:食谱推荐 %% ================================================================ subgraph 阶段一[阶段一:食谱推荐] direction TB P1["🏠 小程序:饮食推荐页"]:::page P1 -->|"请求推荐"| A1["🔗 POST /api/meal/recommend"]:::api A1 -->|"userId, mealType"| S1["⚙️ MealRecommendService.aggregateContext()"]:::service S1 -->|"健康报告"| S1a["⚙️ HealthReportService.getLatestReport()"]:::service S1a -->|"最新报告"| D_HR["📦 health_reports 表"]:::data S1 -->|"异常指标"| S1b["⚙️ HealthReportService.getReportIndicators()"]:::service S1b -->|"指标列表"| D_HRI["📦 health_report_indicators 表"]:::data S1 -->|"营养偏好"| S1c["⚙️ UserNutritionProfileService.getByUserId()"]:::service S1c -->|"用户偏好"| D_NP["📦 user_nutrition_profile 表"]:::data S1 -->|"近期摄入"| S1d["⚙️ MealLogService.getNutritionSummary()"]:::service S1d -->|"7日营养汇总"| D_ML["📦 meal_logs 表"]:::data S1 -->|"候选食材"| S1e["⚙️ FoodService.listAll() + SeasonalFoodService"]:::service S1e -->|"当季活跃食材"| D_FOOD["📦 foods 表"]:::data S1 -->|"聚合上下文"| S1_final["⚙️ MealRecommendService.buildDifyInputs()"]:::service S1_final -->|"用户画像, 健康摘要, 候选食材"| S_AI["⚙️ AIService.sendNutritionMessage() — Dify AI"]:::service S_AI -->|"AI返回JSON食谱"| P1 P1 -->|"一换一菜"| A2["🔗 POST /api/meal/replace"]:::api A2 -->|"foodId, userId"| S2["⚙️ MealRecommendService.findReplaceCandidate()"]:::service S2 -->|"同品类同价位替换食材"| D_FOOD end %% ================================================================ %% 阶段二:饮食记录 %% ================================================================ subgraph 阶段二[阶段二:饮食记录] P1 -->|"满意, 记录饮食"| P2["🏠 记录饮食页"]:::page P2 -->|"提交饮食日志"| A3["🔗 POST /api/meal/log"]:::api A3 -->|"userId, childId, mealType, foods, note, mealDate"| S3["⚙️ MealLogService.create()"]:::service S3 -->|"写入一条记录"| D_ML P2 -->|"查看近期日志"| A4["🔗 POST /api/meal/logs"]:::api A4 -->|"userId, days"| S4["⚙️ MealLogService.getRecentMeals()"]:::service S4 -->|"近期饮食列表"| D_ML P2 -->|"查看营养统计"| A5["🔗 POST /api/meal/nutrition-summary"]:::api A5 -->|"userId, days"| S5["⚙️ MealLogService.getNutritionSummary()"]:::service S5 -->|"营养摄入汇总"| D_ML end %% ================================================================ %% 阶段三:食材推荐指数 %% ================================================================ subgraph 阶段三[阶段三:食材推荐指数] P2 -->|"打开食材指数"| P3["🏠 食材推荐指数页"]:::page P3 -->|"查看指数"| A6["🔗 POST /api/food/recommendation/index"]:::api A6 -->|"userId"| S6["⚙️ FoodRecommendService.getIndexByUser()"]:::service S6 -->|"食材推荐指数列表"| D_FRI["📦 food_recommend_index 表"]:::data P3 -->|"查看变更历史"| A7["🔗 POST /api/food/recommendation/history"]:::api A7 -->|"userId, foodId"| S7["⚙️ FoodRecommendService.getIndexHistory()"]:::service S7 -->|"指数变更记录"| D_FRIL["📦 food_recommend_idx_log 表"]:::data P3 -->|"按家庭分组"| A8["🔗 POST /api/food/recommendation/by-family"]:::api A8 -->|"familyId"| S8["⚙️ FoodRecommendService.getByFamily()"]:::service S8 -->|"家庭成员指数(按成员分组)"| D_FRI end %% ================================================================ %% 阶段四:营养偏好管理 %% ================================================================ subgraph 阶段四[阶段四:营养偏好管理] P3 -->|"管理偏好"| P4["🏠 营养偏好设置页"]:::page P4 -->|"获取偏好"| A9["🔗 POST /api/nutrition/profile/get"]:::api A9 -->|"userId"| S9["⚙️ UserNutritionProfileService.getByUserId()"]:::service S9 -->|"当前偏好配置"| D_NP P4 -->|"保存偏好"| A10["🔗 POST /api/nutrition/profile/save"]:::api A10 -->|"userId, budgetMonthly, familyTastePreferences, cuisineStyle, mealCount, cookingAbility"| S10["⚙️ UserNutritionProfileService.save()"]:::service S10 -->|"插入/更新"| D_NP end %% ================================================================ %% 阶段五:北京营养知识库 %% ================================================================ subgraph 阶段五[阶段五:北京营养知识库] P4 -->|"打开精准营养"| P5["🏠 北京精准营养页"]:::page P5 -->|"查询菌属"| A11["🔗 POST /api/nutrition/beijing/kb/query"]:::api A11 -->|"name(拉丁名/中文名)"| S11["⚙️ BacteriaFoodMapping.findAny()"]:::service S11 -->|"菌属详情: 功能, 升降建议, 食物"| D_KBB["📦 BacteriaFoodMapping(内存知识库)"]:::data P5 -->|"知识库列表"| A12["🔗 POST /api/nutrition/beijing/kb/list"]:::api A12 -->|"无参数"| S12["⚙️ BacteriaFoodMapping.getAllEntries()"]:::service S12 -->|"全部菌属列表"| D_KBB P5 -->|"知识库概况"| A13["🔗 POST /api/nutrition/beijing/kb/info"]:::api A13 -->|"无参数"| S13["⚙️ BacteriaFoodMapping.getMetadata()"]:::service S13 -->|"元数据 + 总菌属数"| D_KBB P5 -->|"保存菌群状态"| A14["🔗 POST /api/nutrition/beijing/bacteria/save"]:::api A14 -->|"userId, bacteriaList[{latinName, chineseName, status}]"| S14["⚙️ BeijingNutritionService.saveBacteriaStatus()"]:::service S14 -->|"写入菌群记录"| D_BAC["📦 bacteria_status 表"]:::data P5 -->|"基于菌群推荐食材"| A15["🔗 POST /api/nutrition/beijing/recommend"]:::api A15 -->|"userId, limit"| S15["⚙️ BeijingNutritionService.recommendFromDb()"]:::service S15 -->|"推荐食材, 原因, 注意事项"| D_BAC P5 -->|"自定义菌群推荐"| A16["🔗 POST /api/nutrition/beijing/recommend/custom"]:::api A16 -->|"userId, bacteriaList[{latinName, chineseName, status}]"| S16["⚙️ BeijingNutritionService.recommend()"]:::service S16 -->|"临时菌群状态→推荐食材"| D_KBB end %% ================================================================ %% 阶段六:营养师申请 %% ================================================================ subgraph 阶段六[阶段六:营养师申请] P5 -->|"申请营养师"| P6["🏠 营养师申请页"]:::page P6 -->|"提交申请"| A17["🔗 POST /api/nutritionist/apply"]:::api A17 -->|"userId(从token提取)"| S17["⚙️ NutritionistController — 设置nutritionistStatus=pending"]:::service S17 -->|"更新用户营养师状态 + 添加nutritionist角色"| D_USER["📦 users 表"]:::data P6 -->|"查询申请状态"| A18["🔗 POST /api/nutritionist/status"]:::api A18 -->|"userId(从token提取)"| S18["⚙️ NutritionistController — 查询状态字段"]:::service S18 -->|"nutritionistStatus, rejectReason"| D_USER end %% ================================================================ %% 阶段七:推荐商品与复购 %% ================================================================ subgraph 阶段七[阶段七:推荐商品与复购] P6 -->|"维度页推荐"| P7["🏠 五维推荐商品区"]:::page P7 -->|"获取推荐"| A19["🔗 POST /api/recommend/dimension-products"]:::api A19 -->|"dimensionCode, familyId, memberId, excludeProductIds, limit"| S19["⚙️ ProductRecommendationService.getDimensionRecommendations()"]:::service S19 -->|"维度相关商品列表"| D_PROD["📦 products 表"]:::data P7 -->|"复购提醒列表"| A20["🔗 POST /api/recommend/repurchase-reminders/list"]:::api A20 -->|"userId(从token提取)"| S20["⚙️ RepurchaseReminderService.getPendingReminders()"]:::service S20 -->|"待处理复购提醒"| D_RR["📦 repurchase_reminder_records 表"]:::data P7 -->|"标记已点击"| A21["🔗 POST /api/recommend/repurchase-reminders/click"]:::api A21 -->|"reminderId"| S21["⚙️ RepurchaseReminderService.onReminderClicked()"]:::service S21 -->|"更新点击状态和时间"| D_RR P7 -->|"标记已购买"| A22["🔗 POST /api/recommend/repurchase-reminders/purchased"]:::api A22 -->|"reminderId, orderId"| S22["⚙️ RepurchaseReminderService.onReminderPurchased()"]:::service S22 -->|"更新购买状态和订单关联"| D_RR end %% ================================================================ %% 角色关联 %% ================================================================ A0 -.- P1 A0 -.- P2 A0 -.- P3 A0 -.- P4 A0 -.- P5 A0 -.- P6 A0 -.- P7 ``` ## 端点明细 ### 食谱推荐与记录 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/meal/recommend` | 获取AI食谱推荐 | 📱小程序 | `{mealType, conversationId}` | `{answer(JSON食谱), conversationId, replaceOptions, nutritionSummary}` | | `POST /api/meal/replace` | 一换一菜 — 替换食材 | 📱小程序 | `{foodId}` | `Food`(同类替代食材) | | `POST /api/meal/log` | 记录饮食日志 | 📱小程序 | `{childId, mealType, foods, note, mealDate}` | `{id, message}` | | `POST /api/meal/logs` | 获取近期饮食日志 | 📱小程序 | `{days}` | `[MealLog]` | | `POST /api/meal/nutrition-summary` | 营养摄入统计 | 📱小程序 | `{days}` | `{calories, protein, fat, carb, ...}` | ### 食材推荐指数 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/food/recommendation/index` | 获取用户食材推荐指数 | 📱小程序 | `{userId}` | `[FoodRecommendIndex(食材, 推荐等级, 理由)]` | | `POST /api/food/recommendation/history` | 指数变更历史 | 📱小程序 | `{userId, foodId}` | `[FoodRecommendIdxLog(变更记录)]` | | `POST /api/food/recommendation/by-family` | 按家庭分组获取指数 | 📱小程序 | `{familyId}` | `Map<成员ID, [FoodRecommendIndex]>` | ### 营养偏好 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/nutrition/profile/get` | 获取当前用户营养偏好 | 📱小程序 | — | `UserNutritionProfile` | | `POST /api/nutrition/profile/save` | 保存营养偏好 | 📱小程序 | `{budgetMonthly, familyTastePreferences, cuisineStyle, mealCount, cookingAbility}` | `"ok"` | ### 北京精准营养(菌群知识库) | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/nutrition/beijing/bacteria/save` | 保存用户菌群状态 | 📱小程序 | `{userId, bacteriaList[{latinName, chineseName, status}]}` | `"保存成功"` | | `POST /api/nutrition/beijing/recommend` | 基于已保存菌群推荐食材 | 📱小程序 | `{userId, limit}` | `NutritionRecommendResponse(分类推荐列表)` | | `POST /api/nutrition/beijing/recommend/custom` | 自定义菌群状态推荐食材 | 📱小程序 | `{userId, bacteriaList[{latinName, chineseName, status}]}` | `NutritionRecommendResponse` | | `POST /api/nutrition/beijing/kb/query` | 查询菌属知识库 | 📱小程序 | `{name}` | `BacteriaEntry(功能, 升降建议, 适配食物)` | | `POST /api/nutrition/beijing/kb/list` | 知识库菌属列表 | 📱小程序 | — | `[BacteriaEntry]` | | `POST /api/nutrition/beijing/kb/info` | 知识库概况 | 📱小程序 | — | `{metadata, 总菌属数, 菌属列表}` | ### 营养师申请 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/nutritionist/apply` | 提交营养师申请 | 📱小程序 | — | `"营养师申请已提交,请等待审核"` | | `POST /api/nutritionist/status` | 查询营养师申请状态 | 📱小程序 | — | `{nutritionistStatus, nutritionistRejectReason}` | ### 推荐商品与复购提醒 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 | |------|------|------|---------|---------| | `POST /api/recommend/dimension-products` | 获取维度页推荐商品 | 📱小程序 | `{dimensionCode, familyId, memberId, excludeProductIds, limit}` | `[{productId, name, price, image, ...}]` | | `POST /api/recommend/repurchase-reminders/list` | 获取待处理复购提醒 | 📱小程序 | — | `[{id, productName, lastPurchaseDate, ...}]` | | `POST /api/recommend/repurchase-reminders/click` | 标记复购提醒已点击 | 📱小程序 | `{id}` | — | | `POST /api/recommend/repurchase-reminders/purchased` | 标记复购提醒已购买 | 📱小程序 | `{id, orderId}` | — | ## 数据实体关系 ```mermaid erDiagram User ||--o| UserNutritionProfile : "一个用户一个偏好" User ||--o{ MealLog : "一个用户多条日志" User ||--o{ FoodRecommendIndex : "一个用户多个食材指数" FamilyMember ||--o{ FoodRecommendIndex : "一个成员多个食材指数" User ||--o{ RepurchaseReminderRecord : "一个用户多个复购提醒" Product ||--o{ RepurchaseReminderRecord : "一个商品多个复购提醒" UserNutritionProfile { Long id PK Long userId FK int budgetMonthly "月预算(元)" string familyTastePreferences "口味偏好" string cuisineStyle "菜系偏好" int mealCount "每日餐数" string cookingAbility "烹饪能力" } MealLog { Long id PK Long userId FK Long childId "关联孩子(可选)" string mealType "早餐/午餐/晚餐/加餐" string foods "食物JSON数组" string note "备注" date mealDate "就餐日期" } FoodRecommendIndex { Long id PK Long userId FK Long foodId FK string recommendLevel "推荐/适量/谨慎/避免" string reason "推荐理由" } RepurchaseReminderRecord { Long id PK Long userId FK Long productId FK string status "pending/clicked/purchased" date lastPurchaseDate date remindDate } ``` ## 完整性分析 | # | 维度 | 评估 | 说明 | |---|------|------|------| | 1 | **流程完整性** | ✅ 完整 | 覆盖食谱推荐→饮食记录→食材指数→偏好管理→精准营养(北京菌群)→营养师申请→商品推荐7个阶段,构成完整"推荐→记录→分析→优化"闭环 | | 2 | **异常路径** | ⚠️ 部分覆盖 | 食谱推荐失败返回错误信息(catch Exception);食材替换无候选时返回"没有可替换的食材";营养师重复提交被拒绝;但AI服务超时/降级等未在流程图体现 | | 3 | **端点覆盖** | ✅ 完整 | 22个端点全部映射到流程图中,与MealRecommendController、FoodRecommendController、UserNutritionProfileController、BeijingNutritionController、NutritionistController、ProductRecommendationController、RepurchaseReminderController实际代码一致 | | 4 | **角色覆盖** | ✅ 完整 | 用户/家长端可操全部7个阶段;营养师申请需管理员审核(管理后台操作未在本图展开);商品推荐可被所有角色使用 | | 5 | **数据实体** | ✅ 完整 | user_nutrition_profile、meal_logs、foods、food_recommend_index、food_recommend_idx_log、health_reports、health_report_indicators、bacteria_status、repurchase_reminder_records、products 10个实体/数据源均在图中映射 | | 6 | **一致性** | ✅ 与代码一致 | 所有端点路径与实际Controller中的`@PostMapping`匹配;AI食谱推荐调用的Dify服务链路(aggregateContext→buildDifyInputs→AIService.sendNutritionMessage)与实际Service代码一致 |