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

570 lines
18 KiB
Markdown
Raw Permalink Normal View 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位小数 |
**请求示例**
```json
{
"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"]` |
**响应示例**
```json
{
"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 |
**请求示例**
```json
{
"resultId": 100234
}
```
#### 响应
| 字段名 | 类型 | 说明 |
|--------|------|------|
| `code` | Integer | 200 成功 |
| `message` | String | 提示信息 |
| `data.planId` | Long | 新创建的营养计划ID |
| `data.startDate` | String | 计划开始日期 |
| `data.endDate` | String | 计划结束日期(默认 +7 天) |
**响应示例**
```json
{
"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
> 存储每次计算的输入与输出,方便查询历史、分析
```sql
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 中已定义,补充关联
```sql
-- 见 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 公式)
```java
/**
* 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 标准体重
```java
public static double calculateStandardWeight(int height, boolean isFemale) {
return isFemale
? (height - 70) * 0.6
: (height - 80) * 0.7;
}
```
### 5.3 BMI
```java
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 营养目标
```java
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 | — | 初稿 |
---
*文档结束*