Files
msh-system/docs/食谱计算器后端接口开发文档.md

18 KiB
Raw Blame History

食谱计算器后端接口开发文档

版本v1.0
创建日期2026-02-01
所属模块Tool工具模块
配套前端msh_single_uniapp/pages/tool/calculator.vue / calculator-result.vue


一、功能概述

食谱计算器是慢生活智能营养专家的核心功能之一面向肾病CKD及透析患者根据用户输入的健康数据自动计算

  1. 健康指标eGFR、标准体重、BMI、CKD分期
  2. 每日营养目标蛋白质摄入量g/d、能量摄入量kcal/d
  3. 食物份数建议7 类食物的每日推荐份数
  4. 一日三餐配餐方案:早餐、午餐、晚餐的菜品清单(含图片、食材用量)
  5. 重要提示:根据患者情况给出饮食注意事项

用户可进一步采纳营养计划,进入打卡流程(另单独接口)。


二、接口清单

序号 接口名称 HTTP方法 路径 说明
1 计算营养方案 POST /api/front/tool/calculator/calculate 核心接口,输入健康数据,返回计算结果
2 获取计算结果详情 GET /api/front/tool/calculator/result/{id} 通过结果ID获取完整结果含配餐
3 采纳营养计划 POST /api/front/tool/calculator/adopt 用户采纳后创建营养计划,进入打卡流程

三、接口详细设计

3.1 计算营养方案

基础信息

项目
请求路径 POST /api/front/tool/calculator/calculate
是否鉴权 (需登录)
Content-Type application/json

请求参数Request Body

参数名 类型 必填 说明 校验规则
gender String 性别 可选值:male / female
age Integer 年龄(岁) 1 ≤ age ≤ 150
height Integer 身高cm 50 ≤ height ≤ 250
dialysis Boolean 是否透析 true / false
dialysisType String 透析类型 dialysis=true 时有效;可选值:hemodialysis(血透)/ peritoneal(腹透)
dryWeight Number 干体重kg 20 ≤ dryWeight ≤ 300支持1位小数
creatinine Number 血肌酐μmol/L 0 < creatinine ≤ 2000支持2位小数

请求示例

{
  "gender": "male",
  "age": 55,
  "height": 170,
  "dialysis": true,
  "dialysisType": "hemodialysis",
  "dryWeight": 65.5,
  "creatinine": 850
}

响应参数Response Body

参数名 类型 说明
code Integer 状态码,200 表示成功
message String 提示信息
data Object 计算结果对象

data 结构

字段名 类型 说明
id / resultId Long 计算结果唯一ID用于后续查询/采纳
healthData Object 健康数据计算结果
nutritionGoals Object 每日营养目标
foodList Array 食物份数建议列表
mealPlan Object 一日三餐配餐方案
importantTips Array<String> 重要提示文案列表
createdAt String 创建时间 ISO8601

healthData 结构

字段名 类型 说明 计算公式
eGFR String 肾小球滤过率ml/min/1.73m²) CKD-EPI 公式(见附录 A
standardWeight String 标准体重kg 男:(height - 80) × 0.7;女:(height - 70) × 0.6
bmi String 体重指数 dryWeight / (height/100)²
bmiStatus String BMI 状态描述 见附录 B
ckdStage String CKD 分期 见附录 C

nutritionGoals 结构

字段名 类型 说明 计算公式
protein String 每日蛋白质目标(克) 透析期:standardWeight × 1.2;非透析期:standardWeight × 0.8
energy String 每日能量目标(千卡) standardWeight × 35

foodList 结构数组7 项)

字段名 类型 说明
number Integer 序号 1-7
name String 食物类别名称(如"谷薯50g"
portion String 推荐份数

mealPlan 结构

字段名 类型 说明
breakfast Array 早餐菜品列表
lunch Array 午餐菜品列表
dinner Array 晚餐菜品列表

菜品对象结构

字段名 类型 说明
name String 菜品名称
image String 菜品图片 URL
ingredients Array<String> 食材列表(如 ["牛奶 120g", "面条 90g"]

响应示例

{
  "code": 200,
  "message": "success",
  "data": {
    "id": 100234,
    "healthData": {
      "eGFR": "7.9",
      "standardWeight": "63.0",
      "bmi": "22.7",
      "bmiStatus": "正常",
      "ckdStage": "CKD 5期"
    },
    "nutritionGoals": {
      "protein": "75.6",
      "energy": "2205"
    },
    "foodList": [
      { "number": 1, "name": "谷薯50g", "portion": "5.7" },
      { "number": 2, "name": "淀粉100g", "portion": "0.77" },
      { "number": 3, "name": "绿叶蔬菜200g", "portion": "1" },
      { "number": 4, "name": "瓜果蔬菜200g", "portion": "2" },
      { "number": 5, "name": "奶类230g", "portion": "1" },
      { "number": 6, "name": "肉蛋类50/60g", "portion": "7" },
      { "number": 7, "name": "油脂类10g", "portion": "5.7" }
    ],
    "mealPlan": {
      "breakfast": [
        {
          "name": "牛奶",
          "image": "https://cdn.xxx.com/images/milk.jpg",
          "ingredients": ["牛奶 120g"]
        },
        {
          "name": "鸡蛋拌面",
          "image": "https://cdn.xxx.com/images/egg-noodle.jpg",
          "ingredients": ["面条 90g", "鸡蛋 120g", "葱花 5g"]
        },
        {
          "name": "凉拌黄瓜",
          "image": "https://cdn.xxx.com/images/cucumber.jpg",
          "ingredients": ["黄瓜 100g"]
        }
      ],
      "lunch": [
        {
          "name": "米饭",
          "image": "https://cdn.xxx.com/images/rice.jpg",
          "ingredients": ["大米 100g"]
        },
        {
          "name": "清蒸鲈鱼",
          "image": "https://cdn.xxx.com/images/bass.jpg",
          "ingredients": ["鲈鱼 120g", "生姜 5g", "葱 5g"]
        },
        {
          "name": "蒜蓉西兰花",
          "image": "https://cdn.xxx.com/images/broccoli.jpg",
          "ingredients": ["西兰花 150g", "大蒜 5g", "植物油 8g"]
        },
        {
          "name": "冬瓜汤",
          "image": "https://cdn.xxx.com/images/wax-gourd-soup.jpg",
          "ingredients": ["冬瓜 150g"]
        }
      ],
      "dinner": [
        {
          "name": "杂粮饭",
          "image": "https://cdn.xxx.com/images/mixed-rice.jpg",
          "ingredients": ["大米 70g", "小米 30g"]
        },
        {
          "name": "香菇炒鸡丁",
          "image": "https://cdn.xxx.com/images/chicken-mushroom.jpg",
          "ingredients": ["鸡胸肉 100g", "香菇 50g", "植物油 8g"]
        },
        {
          "name": "清炒油菜",
          "image": "https://cdn.xxx.com/images/bok-choy.jpg",
          "ingredients": ["油菜 150g", "植物油 5g"]
        },
        {
          "name": "番茄蛋花汤",
          "image": "https://cdn.xxx.com/images/tomato-egg-soup.jpg",
          "ingredients": ["番茄 100g", "鸡蛋 60g"]
        }
      ]
    },
    "importantTips": [
      "以上配餐由 AI 生成,仅适用于无其他并发症的单纯尿毒症人群",
      "透析患者需严格控制水分摄入",
      "建议低盐饮食(每日少于 5g",
      "注意限制高钾食物(香蕉、橙子、土豆等)",
      "限制高磷食物(坚果、动物内脏等)",
      "特别提醒:尿毒症合并其他并发症人群不适用此配餐,请务必咨询专业营养师调整方案"
    ],
    "createdAt": "2026-02-01T10:30:00+08:00"
  }
}

错误码

code message 说明
400 参数校验失败 请求参数不符合校验规则
401 / 410000-410002 未登录/登录过期 需重新登录
500 系统异常 服务端内部错误

3.2 获取计算结果详情

基础信息

项目
请求路径 GET /api/front/tool/calculator/result/{id}
是否鉴权
路径参数 id计算结果IDLong

响应

与 3.1 的 data 结构相同,用于结果页刷新或分享后重新加载。


3.3 采纳营养计划

基础信息

项目
请求路径 POST /api/front/tool/calculator/adopt
是否鉴权
Content-Type application/json

请求参数

参数名 类型 必填 说明
resultId Long 计算结果ID

请求示例

{
  "resultId": 100234
}

响应

字段名 类型 说明
code Integer 200 成功
message String 提示信息
data.planId Long 新创建的营养计划ID
data.startDate String 计划开始日期
data.endDate String 计划结束日期(默认 +7 天)

响应示例

{
  "code": 200,
  "message": "采纳成功",
  "data": {
    "planId": 56789,
    "startDate": "2026-02-01",
    "endDate": "2026-02-07"
  }
}

业务规则

  1. 同一用户同一时间只能有 1 个激活状态(status=active 的营养计划;若已存在则自动将旧计划标记为 abandoned
  2. 采纳成功后赠送 +20 积分(积分任务:采纳营养方案)。

四、数据库表设计

4.1 计算结果表calculator_results

存储每次计算的输入与输出,方便查询历史、分析

CREATE TABLE calculator_results (
  result_id              BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
  user_id         BIGINT NOT NULL COMMENT '用户ID',
  
  -- 输入参数
  gender          VARCHAR(10) NOT NULL COMMENT '性别male/female',
  age             INT NOT NULL COMMENT '年龄(岁)',
  height          INT NOT NULL COMMENT '身高cm',
  has_dialysis    TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否透析0-否 1-是',
  dialysis_type   VARCHAR(20) COMMENT '透析类型hemodialysis/peritoneal',
  dry_weight      DECIMAL(5,2) NOT NULL COMMENT '干体重kg',
  creatinine      DECIMAL(8,2) NOT NULL COMMENT '血肌酐μmol/L',
  
  -- 计算结果
  egfr            DECIMAL(6,2) NOT NULL COMMENT 'eGFRml/min/1.73m²)',
  standard_weight DECIMAL(5,2) NOT NULL COMMENT '标准体重kg',
  bmi             DECIMAL(4,1) NOT NULL COMMENT 'BMI',
  bmi_status      VARCHAR(20) NOT NULL COMMENT 'BMI状态描述',
  ckd_stage       VARCHAR(50) NOT NULL COMMENT 'CKD分期',
  protein_intake  DECIMAL(5,1) NOT NULL COMMENT '每日蛋白质目标g',
  energy_intake   INT NOT NULL COMMENT '每日能量目标kcal',
  
  -- 配餐方案JSON
  food_list_json  TEXT COMMENT '食物份数建议 JSON',
  meal_plan_json  TEXT COMMENT '配餐方案 JSON',
  tips_json       TEXT COMMENT '重要提示 JSON',
  
  created_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updated_at      DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  
  INDEX idx_user_id (user_id),
  INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营养计算结果表';

4.2 营养计划表nutrition_plans

PRD 中已定义,补充关联

-- 见 PRD 4.6.5 节
-- 关键字段:
-- plan_id, user_id, gender, age, height, weight, has_dialysis, dialysis_type, creatinine,
-- egfr, standard_weight, bmi, ckd_stage, protein_intake, energy_intake,
-- meal_plan_json, status, start_date, end_date, created_at, updated_at

关联关系:nutrition_plans.source_result_id → calculator_results.id(可选字段,用于溯源)


五、核心算法

5.1 eGFR 计算CKD-EPI 2021 公式)

/**
 * CKD-EPI 2021 公式(无种族系数)
 * @param creatinine 血肌酐 (μmol/L)
 * @param age 年龄 (岁)
 * @param isFemale 是否女性
 * @return eGFR (ml/min/1.73m²)
 */
public static double calculateEGFR(double creatinine, int age, boolean isFemale) {
    // 将 μmol/L 转换为 mg/dL
    double scr = creatinine / 88.4;

    double kappa = isFemale ? 0.7 : 0.9;
    double alpha = isFemale ? -0.241 : -0.302;
    double multi = isFemale ? 1.012 : 1.0;

    double minRatio = Math.min(scr / kappa, 1.0);
    double maxRatio = Math.max(scr / kappa, 1.0);

    double eGFR = 142 * Math.pow(minRatio, alpha) * Math.pow(maxRatio, -1.200)
                  * Math.pow(0.9938, age) * multi;
    return Math.round(eGFR * 100.0) / 100.0; // 保留2位小数
}

5.2 标准体重

public static double calculateStandardWeight(int height, boolean isFemale) {
    return isFemale 
           ? (height - 70) * 0.6 
           : (height - 80) * 0.7;
}

5.3 BMI

public static double calculateBMI(double weight, int height) {
    double heightM = height / 100.0;
    return Math.round(weight / (heightM * heightM) * 10.0) / 10.0;
}

5.4 BMI 状态

BMI 范围 状态描述
< 18.5 体型过轻
18.5 - 23.9 正常
24.0 - 27.9 超重
≥ 28.0 肥胖

5.5 CKD 分期

eGFR 范围 分期
≥ 90 CKD 1期
60 - 89 CKD 2期
45 - 59 CKD 3a期
30 - 44 CKD 3b期
15 - 29 CKD 4期
< 15非透析 CKD 5期
透析患者 透析期

5.6 营养目标

public static double calculateProtein(double standardWeight, boolean hasDialysis) {
    return hasDialysis 
           ? standardWeight * 1.2  // 透析期
           : standardWeight * 0.8; // 非透析期
}

public static int calculateEnergy(double standardWeight) {
    return (int) Math.round(standardWeight * 35);
}

5.7 食物份数

基于能量和蛋白质目标按照《慢性肾脏病患者膳食指导2017》推荐比例分配具体算法可参考专业营养软件或配置表。

5.8 配餐方案生成

  1. 规则匹配:基于 CKD 分期、透析类型、营养目标,从预置的 配餐模板库recipes 中匹配合适方案。
  2. AI 生成(可选):调用 AI 模型根据营养目标动态生成菜品及食材用量。
  3. 输出:早/午/晚三餐各 2-4 道菜,包含图片 URL 和食材列表。

六、性能与安全

6.1 缓存策略

数据 缓存方式 过期时间 说明
计算结果 Redis Hash 24 小时 key = calc_result:{id}
配餐模板库 本地缓存 / Redis 1 小时 减少 DB 查询

6.2 限流

接口 QPS 限制 说明
calculate 10/用户/分钟 防止恶意刷算
result/{id} 60/用户/分钟 允许刷新
adopt 5/用户/分钟 防重复提交

6.3 幂等性

  • calculate:每次计算生成新记录,允许重复调用。
  • adopt:采纳接口需幂等设计,同一 resultId 多次调用只创建一个计划;可用 user_id + result_id 唯一索引或分布式锁。

6.4 数据安全

  • 所有接口均需登录鉴权Header: Authori-zation)。
  • 用户只能查询/采纳自己的计算结果(user_id 校验)。
  • 敏感健康数据传输全程 HTTPS。

七、测试用例

7.1 正常场景

用例编号 描述 输入 预期输出
TC01 男性透析患者计算 gender=male, age=55, height=170, dialysis=true, dryWeight=65.5, creatinine=850 返回 code=200healthData/nutritionGoals/mealPlan 完整
TC02 女性非透析患者 gender=female, age=48, height=160, dialysis=false, dryWeight=52, creatinine=180 返回 code=200ckdStage 按 eGFR 判断
TC03 查询结果详情 GET /result/100234 返回与 calculate 相同结构
TC04 采纳营养计划 resultId=100234 返回 planId计划 status=active

7.2 异常场景

用例编号 描述 输入 预期输出
TC11 年龄超出范围 age=200 code=400, message 包含"年龄"
TC12 未登录调用 无 Token code=401/410000
TC13 查询他人结果 id 属于其他用户 code=403
TC14 重复采纳 同一 resultId 调用两次 第二次返回已存在的 planId幂等

八、附录

附录 ACKD-EPI 2021 公式

无种族系数版本,适用于中国人群

eGFR = 142 × min(Scr/κ, 1)^α × max(Scr/κ, 1)^(-1.200) × 0.9938^Age × (1.012 if female)

其中:
- Scr = 血肌酐 (mg/dL) = μmol/L ÷ 88.4
- κ = 0.7 (女) / 0.9 (男)
- α = -0.241 (女) / -0.302 (男)

附录 BBMI 分类标准(中国)

BMI 分类
< 18.5 体型过轻
18.5 - 23.9 正常
24.0 - 27.9 超重
≥ 28.0 肥胖

附录 CCKD 分期标准

分期 eGFR (ml/min/1.73m²) 描述
1期 ≥ 90 肾功能正常或升高
2期 60-89 轻度下降
3a期 45-59 轻-中度下降
3b期 30-44 中-重度下降
4期 15-29 重度下降
5期 < 15 肾衰竭
透析期 已接受透析治疗

九、变更记录

版本 日期 作者 变更内容
v1.0 2026-02-01 初稿

文档结束