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

570 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 食谱计算器后端接口开发文档
> **版本**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 | — | 初稿 |
---
*文档结束*