- Backend: .NET 10 Minimal API + EF Core + PostgreSQL - Frontend: Flutter + Riverpod + GoRouter + Dio - AI: DeepSeek LLM + Qwen VLM (OpenAI-compatible) - Auth: SMS + JWT (access/refresh tokens) - Features: AI chat, health tracking, medication management, diet analysis, exercise plans, doctor consultations, report analysis
41 KiB
41 KiB
健康管家 — 技术设计文档 V2
基于需求规格文档 V2,技术栈确定为 .NET 10 + Flutter + PostgreSQL。
一、整体架构
Flutter App(iOS + Android)
│
├── HTTPS REST(业务 CRUD)
├── SSE(AI 对话流式输出)
└── HTTPS 轮询(医患消息,15s间隔)
▼
.NET 10 ASP.NET Core Minimal API
│
├── HttpClient → DeepSeek(LLM 对话 + Tool Calling)
├── HttpClient → 千问 VL(食物图片识别)
├── EF Core → PostgreSQL(主数据库)
├── MinIO SDK → MinIO(文件/图片存储)
└── 极光推送 SDK → 极光推送(Android/iOS 通知)
- 后端:.NET 10,C# 12,4 层 Clean Architecture
- 前端:Flutter,Dart,Riverpod
- AI 调用:纯 HttpClient 直连各模型厂商 API(OpenAI 兼容协议),不使用任何模型厂商 SDK
- 不允许前端直接调 AI 模型 API,全部经 .NET 后端中转
二、后端架构
2.1 目录结构
HealthManager/
├── src/
│ ├── Health.Domain/ # 领域层
│ │ ├── Entities/ # 实体:User, HealthRecord, Medication, DietRecord 等
│ │ ├── Enums/ # 枚举:HealthMetricType, MealType, ReportStatus 等
│ │ └── Interfaces/ # 仓储接口
│ │
│ ├── Health.Application/ # 应用层
│ │ ├── DTOs/ # 数据传输对象
│ │ ├── Services/ # 业务服务
│ │ │ ├── AuthService.cs # 认证
│ │ │ ├── HealthService.cs # 健康数据
│ │ │ ├── DietService.cs # 饮食记录
│ │ │ ├── MedicationService.cs # 用药管理
│ │ │ ├── ReportService.cs # 报告管理
│ │ │ ├── ConsultationService.cs # 问诊
│ │ │ ├── ExerciseService.cs # 运动计划
│ │ │ └── UserService.cs # 用户/健康档案
│ │ └── Interfaces/ # 服务接口
│ │
│ ├── Health.Infrastructure/ # 基础设施层
│ │ ├── Data/
│ │ │ ├── AppDbContext.cs # EF Core 上下文
│ │ │ └── Migrations/ # 数据库迁移
│ │ ├── Services/
│ │ │ ├── JwtProvider.cs # JWT 生成与验证
│ │ │ ├── SmsService.cs # 短信发送
│ │ │ ├── PushService.cs # 极光推送
│ │ │ └── MinioStorageService.cs # 文件存储
│ │ └── AI/ # AI 服务模块
│ │ ├── OpenAiCompatibleClient.cs # OpenAI 兼容 HTTP 客户端(统一调 DeepSeek/千问)
│ │ ├── AiChatService.cs # AI 对话编排(意图识别 + Tool Calling)
│ │ ├── AgentHandlers/ # 7 个 Agent 的处理器
│ │ │ ├── DefaultAgentHandler.cs # 默认对话
│ │ │ ├── ConsultationAgentHandler.cs # AI 问诊
│ │ │ ├── HealthDataAgentHandler.cs # 记数据
│ │ │ ├── DietAgentHandler.cs # 拍饮食
│ │ │ ├── MedicationAgentHandler.cs # 药管家
│ │ │ ├── ReportAgentHandler.cs # 看报告
│ │ │ └── ExerciseAgentHandler.cs # 运动计划
│ │ └── PromptManager.cs # System Prompt 模板管理
│ │
│ └── Health.WebApi/ # 接口层
│ ├── Program.cs # 启动配置
│ ├── Endpoints/ # Minimal API 端点
│ │ ├── AuthEndpoints.cs
│ │ ├── HealthEndpoints.cs
│ │ ├── DietEndpoints.cs
│ │ ├── MedicationEndpoints.cs
│ │ ├── ReportEndpoints.cs
│ │ ├── ConsultationEndpoints.cs
│ │ ├── ExerciseEndpoints.cs
│ │ ├── UserEndpoints.cs
│ │ ├── AiChatEndpoints.cs # AI 对话 SSE
│ │ └── FileEndpoints.cs
│ ├── Middleware/ # 中间件
│ │ └── ExceptionMiddleware.cs
│ └── BackgroundServices/ # 后台服务
│ └── MedicationReminderService.cs # 用药提醒定时扫描
2.2 API 响应格式
所有 API 返回统一结构:
// 成功
{ "code": 0, "data": { ... }, "message": null }
// 业务错误
{ "code": 40001, "data": null, "message": "验证码错误" }
// 系统异常
{ "code": 50000, "data": null, "message": "服务器内部错误" }
code = 0:成功code = 4xxxx:客户端错误code = 5xxxx:服务端错误
2.3 7 个 Agent 设计
每个 Agent 本质上是:一个 SSE 端点 + 独立的 System Prompt + 独立的 Tool Set。
Agent 架构模式
Flutter 端 → GET /api/ai/{agent_type}/chat?message=xxx
│
▼
AgentHandler.Process(message, userId, sessionId)
│
├── 1. 加载上下文(System Prompt + 患者数据 + 对话历史)
├── 2. 调用 DeepSeek(stream: true)
├── 3. Tool Calling 循环(如果 LLM 返回 tool_calls)
│ ├── 执行 Tool
│ ├── 结果追加到消息历史
│ └── 再次调用 LLM
├── 4. 流式输出回复(SSE data: 逐 token)
└── 5. 保存对话记录到 PostgreSQL
7 个 Agent 对照表
| Agent | Path | System Prompt 定位 | Tool Set |
|---|---|---|---|
| 默认对话 | /api/ai/default/chat |
通用健康管家 | query_health_records, check_abnormal |
| AI 问诊 | /api/ai/consultation/chat |
医生助手,多轮追问 | query_health_records, check_archive, request_doctor |
| 记数据 | /api/ai/health/chat |
健康数据录入助手 | record_health_data, query_health_records |
| 拍饮食 | /api/ai/diet/chat |
营养分析专家 | analyze_food_image(VLM), estimate_food_text, check_archive |
| 药管家 | /api/ai/medication/chat |
用药管理专家 | manage_medication(create/query/confirm), check_archive |
| 看报告 | /api/ai/report/chat |
报告解读专家 | analyze_report(VLM), query_health_records |
| 运动计划 | /api/ai/exercise/chat |
运动康复教练 | manage_exercise_plan(create/query/checkin) |
2.4 Tool Calling 设计
| Tool | 功能 | 参数 |
|---|---|---|
record_health_data |
录入健康数据 | type, systolic?, diastolic?, heart_rate?, glucose?, spo2?, weight?, recorded_at |
query_health_records |
查询近期健康数据 | type?, days? |
check_abnormal |
检查是否有异常指标 | — |
check_archive |
查询患者档案 | — |
analyze_food_image |
VLM 食物识别 | image_url |
estimate_food_text |
文字描述估算食物 | text |
manage_medication |
用药管理 | action(create/query/confirm/update), name?, dosage?, time? |
manage_exercise_plan |
运动计划 | action(create/query/checkin), exercises?, day? |
analyze_report |
报告 AI 预解读 | image_url |
request_doctor |
转医生 | reason, urgency_level |
2.5 AI 服务模块
OpenAiCompatibleClient
统一的 OpenAI 兼容协议 HTTP 客户端,支持:
- Chat Completions(含 streaming)
- Vision(图片理解)
- Tool Calling(Function Calling)
- JSON Mode
- 多 BaseUrl 切换(DeepSeek / 千问 VL)
- 自动重试
// 调用示例
var client = new OpenAiCompatibleClient(new()
{
BaseUrl = "https://api.deepseek.com/v1",
ApiKey = config["DEEPSEEK_API_KEY"],
Model = "deepseek-chat"
});
await foreach (var token in client.ChatStreamAsync(messages, tools))
{
// SSE 输出给前端
}
AiChatService
每个 Agent 对话的核心编排逻辑:
public async IAsyncEnumerable<string> ProcessAgentChat(
AgentType agentType,
string message,
string userId,
string? conversationId)
{
// 1. 加载 System Prompt
var systemPrompt = _promptManager.GetSystemPrompt(agentType);
// 2. 注入患者上下文
var patientContext = await LoadPatientContext(userId);
// 3. 加载对话历史
var history = await LoadConversationHistory(conversationId, userId);
// 4. 构建消息
var messages = BuildMessages(systemPrompt, patientContext, history, message);
// 5. Tool Calling 循环
var tools = _toolRegistry.GetTools(agentType);
var maxIterations = 5;
for (int i = 0; i < maxIterations; i++)
{
var response = await _aiClient.ChatAsync(messages, tools, stream: true);
if (response.FinishReason == "stop")
{
// 流式输出文本
await foreach (var token in response.Stream)
yield return token;
break;
}
else if (response.FinishReason == "tool_calls")
{
// 执行 Tool
foreach (var toolCall in response.ToolCalls)
{
var result = await _toolExecutor.Execute(toolCall);
messages.Add(new { role = "tool", content = result, tool_call_id = toolCall.Id });
}
// 继续循环
}
}
// 6. 保存对话记录
await SaveConversation(userId, conversationId, messages);
}
PromptManager
System Prompt 模板管理:
默认对话:
你是一个心脏术后康复患者的私人 AI 健康管家。
你的名字叫"阿福",语气温暖专业。
每次回复末尾,如果有需要提醒的事项,简短提醒一句。
AI 问诊:
你是一个心血管内科医生助手,负责对患者进行多轮问诊。
规则:
1. 每次只问一个问题,不要一次问多个
2. 给出快捷选项让患者点击
3. 遇到胸痛/呼吸困难/心悸 → 建议立即就医
4. 血压持续>160/100 或心率异常 → 建议转医生
5. 问诊结束后给出初步分析
记数据:
你是一个健康数据录入助手。
规则:
1. 解析用户口中的指标和数值
2. 指标模糊时追问确认
3. 时间模糊时取当前时间并告知用户
4. 异常值时附带提醒
5. 录入后展示确认卡片
拍饮食:
你是一个营养分析专家。
规则:
1. 等待VLM识别结果后再做分析
2. 结合患者档案判断"能不能吃"
3. 给出单项警告和整体建议
4. 追问餐次归类
...
(其余 Agent 同理)
2.6 配置管理
所有敏感配置放环境变量(.env / appsettings):
# LLM
DEEPSEEK_API_KEY=sk-xxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_MODEL=deepseek-chat
# VLM
QWEN_API_KEY=sk-xxxxx
QWEN_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
QWEN_VISION_MODEL=qwen-vl-max
# 基础设施
JWT_SECRET=xxxxx
DB_CONNECTION=Host=localhost;Database=health_manager;...
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=xxxxx
MINIO_SECRET_KEY=xxxxx
# 推送
JPUSH_APP_KEY=xxxxx
JPUSH_MASTER_SECRET=xxxxx
# 短信
SMS_PROVIDER=xxx
SMS_API_KEY=xxxxx
三、数据库设计
3.1 核心表
| 表名 | 说明 |
|---|---|
users |
用户表 |
health_records |
健康记录(血压/心率/血糖/血氧/体重) |
medications |
用药计划 |
medication_logs |
用药打卡记录 |
diet_records |
饮食记录 |
diet_food_items |
饮食记录中的食物条目 |
exercise_plans |
运动计划(按周) |
exercise_plan_items |
运动计划每日条目 |
reports |
检查报告 |
conversations |
AI 对话会话 |
conversation_messages |
AI 对话消息 |
consultations |
问诊会话 |
consultation_messages |
问诊消息 |
follow_ups |
复查/随访计划 |
health_archives |
健康档案 |
refresh_tokens |
刷新令牌 |
verification_codes |
验证码 |
notification_preferences |
通知偏好 |
device_tokens |
设备推送 token |
devices |
绑定设备(预留) |
3.2 关键表结构
users
id UUID PRIMARY KEY
phone VARCHAR(20) UNIQUE NOT NULL
name VARCHAR(50)
gender VARCHAR(10)
birth_date DATE
avatar_url VARCHAR(500)
created_at TIMESTAMP
updated_at TIMESTAMP
health_records
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
metric_type VARCHAR(20) NOT NULL -- blood_pressure / heart_rate / glucose / spo2 / weight
systolic INTEGER -- 血压专用
diastolic INTEGER -- 血压专用
value DECIMAL -- 通用数值(心率/血糖/血氧/体重)
unit VARCHAR(20)
source VARCHAR(20) NOT NULL -- ai_entry / device_sync / manual
is_abnormal BOOLEAN
recorded_at TIMESTAMP
created_at TIMESTAMP
medications
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
name VARCHAR(100) NOT NULL
dosage VARCHAR(50)
frequency VARCHAR(50) -- daily / twice_daily / weekly 等
time_of_day TIME[] -- PostgreSQL 数组,如 {08:00,20:00}
start_date DATE
end_date DATE
is_active BOOLEAN
source VARCHAR(20) -- prescription / ai_entry / manual
created_at TIMESTAMP
updated_at TIMESTAMP
diet_records
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
meal_type VARCHAR(10) -- breakfast / lunch / dinner / snack
total_calories INTEGER
health_score INTEGER -- 1-5
recorded_at DATE
created_at TIMESTAMP
diet_food_items
id UUID PRIMARY KEY
diet_record_id UUID REFERENCES diet_records(id)
name VARCHAR(100)
portion VARCHAR(50)
calories INTEGER
protein_g DECIMAL
carbs_g DECIMAL
fat_g DECIMAL
warning TEXT
sort_order INTEGER
health_archives
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id) UNIQUE
diagnosis TEXT -- 主要诊断
surgery_type VARCHAR(100) -- 手术类型
surgery_date DATE -- 手术日期
allergies TEXT[] -- 过敏信息
diet_restrictions TEXT[] -- 饮食限制
chronic_diseases TEXT[] -- 慢病史
family_history TEXT -- 家族病史
updated_at TIMESTAMP
medication_logs
id UUID PRIMARY KEY
medication_id UUID REFERENCES medications(id)
user_id UUID REFERENCES users(id)
status VARCHAR(20) NOT NULL -- taken / missed / skipped
scheduled_time TIME NOT NULL
confirmed_at TIMESTAMP
created_at TIMESTAMP
exercise_plans
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
week_start_date DATE NOT NULL -- 本周一
created_at TIMESTAMP
updated_at TIMESTAMP
exercise_plan_items
id UUID PRIMARY KEY
plan_id UUID REFERENCES exercise_plans(id)
day_of_week INTEGER NOT NULL -- 0=周一, 6=周日
exercise_type VARCHAR(100) NOT NULL -- 散步/慢跑/游泳 等
duration_minutes INTEGER NOT NULL
is_completed BOOLEAN DEFAULT FALSE
completed_at TIMESTAMP
is_rest_day BOOLEAN DEFAULT FALSE -- 休息日
reports
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
file_url VARCHAR(500) NOT NULL
file_type VARCHAR(20) -- image / pdf
report_type VARCHAR(50) -- blood_test / ecg / ultrasound / other
ai_summary TEXT -- AI 预解读结果(JSON)
ai_indicators JSONB -- 提取的指标 [{name, value, unit, range, status}]
status VARCHAR(20) NOT NULL -- pending_doctor / doctor_reviewed
doctor_comment TEXT -- 医生审核意见
doctor_name VARCHAR(50) -- 审核医生
reviewed_at TIMESTAMP
created_at TIMESTAMP
conversations
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
agent_type VARCHAR(20) NOT NULL -- default / consultation / health / diet 等
title VARCHAR(200)
summary VARCHAR(500) -- 侧滑抽屉显示用的摘要
message_count INTEGER DEFAULT 0
created_at TIMESTAMP
updated_at TIMESTAMP
conversation_messages
id UUID PRIMARY KEY
conversation_id UUID REFERENCES conversations(id)
role VARCHAR(10) NOT NULL -- user / assistant / tool
content TEXT NOT NULL
intent VARCHAR(30) -- health_record / diet / medication / exercise / report / chat
metadata_json JSONB -- 结构化数据(录入数值、食物列表、卡片数据等)
created_at TIMESTAMP
consultations
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
doctor_id UUID REFERENCES doctors(id)
status VARCHAR(20) NOT NULL -- ai_talking / waiting_doctor / doctor_replied / closed
month INTEGER NOT NULL -- 所属月份,用于配额计算
created_at TIMESTAMP
closed_at TIMESTAMP
consultation_messages
id UUID PRIMARY KEY
consultation_id UUID REFERENCES consultations(id)
sender_type VARCHAR(10) NOT NULL -- user / doctor / ai
sender_name VARCHAR(50)
content TEXT NOT NULL
created_at TIMESTAMP
doctors
id UUID PRIMARY KEY
name VARCHAR(50) NOT NULL
title VARCHAR(50) -- 主任医师/副主任医师
department VARCHAR(50) -- 心血管内科/营养科 等
avatar_url VARCHAR(500)
introduction TEXT
is_active BOOLEAN DEFAULT TRUE
created_at TIMESTAMP
follow_ups
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
title VARCHAR(200) NOT NULL
doctor_name VARCHAR(50)
department VARCHAR(50)
scheduled_at TIMESTAMP NOT NULL
notes TEXT
status VARCHAR(20) NOT NULL -- upcoming / completed / cancelled
created_at TIMESTAMP
refresh_tokens
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
token VARCHAR(500) UNIQUE NOT NULL
expires_at TIMESTAMP NOT NULL
is_revoked BOOLEAN DEFAULT FALSE
created_at TIMESTAMP
verification_codes
id UUID PRIMARY KEY
phone VARCHAR(20) NOT NULL
code VARCHAR(6) NOT NULL
expires_at TIMESTAMP NOT NULL
is_used BOOLEAN DEFAULT FALSE
created_at TIMESTAMP
notification_preferences
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id) UNIQUE
medication_reminder BOOLEAN DEFAULT TRUE
followup_reminder BOOLEAN DEFAULT TRUE
doctor_reply BOOLEAN DEFAULT TRUE
abnormal_alert BOOLEAN DEFAULT TRUE
updated_at TIMESTAMP
device_tokens
id UUID PRIMARY KEY
user_id UUID REFERENCES users(id)
platform VARCHAR(10) NOT NULL -- ios / android
push_token VARCHAR(500) NOT NULL
is_active BOOLEAN DEFAULT TRUE
created_at TIMESTAMP
updated_at TIMESTAMP
3.3 索引设计
-- 健康记录:按用户+时间查询高频
CREATE INDEX idx_health_records_user_time ON health_records(user_id, recorded_at DESC);
CREATE INDEX idx_health_records_user_type ON health_records(user_id, metric_type);
-- 用药:按时段扫描提醒
CREATE INDEX idx_medications_active_time ON medications(user_id, is_active);
CREATE INDEX idx_medication_logs_medication ON medication_logs(medication_id, created_at DESC);
-- 对话:按用户+时间
CREATE INDEX idx_conversations_user_time ON conversations(user_id, updated_at DESC);
CREATE INDEX idx_conversation_messages_conv ON conversation_messages(conversation_id, created_at);
-- 问诊:轮询新消息
CREATE INDEX idx_consultation_messages_consult ON consultation_messages(consultation_id, created_at DESC);
-- 饮食:按日期查询
CREATE INDEX idx_diet_records_user_date ON diet_records(user_id, recorded_at DESC);
-- 验证码:清理过期
CREATE INDEX idx_verification_codes_phone ON verification_codes(phone, created_at DESC);
四、API 设计
4.1 认证 API
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/auth/send-sms |
发送短信验证码 {phone}(开发阶段直接返回成功,不做真实发送) |
| POST | /api/auth/login |
验证码登录 {phone, smsCode}(开发阶段任意6位数字通过) |
| POST | /api/auth/refresh |
刷新 token {refreshToken} → {accessToken, refreshToken} |
| POST | /api/auth/logout |
登出 {refreshToken} |
Token 策略:
access_token:30 分钟,前端内存持有refresh_token:30 天,flutter_secure_storage 存储- 前端收到 401 → Dio 拦截器自动用 refresh_token 换新 access_token → 重试原请求
- 刷新时同时下发新的 refresh_token(续期 30 天),旧 refresh_token 立即失效
- 连续 30 天未使用 App 导致 refresh_token 过期 → 需重新登录
- 一次登录长期有效,除非主动退出或 30 天未使用
4.2 AI 对话 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/ai/{agent_type}/chat |
SSE 流式对话 |
| GET | /api/ai/conversations |
获取对话列表 |
| GET | /api/ai/conversations/{id} |
获取对话历史 |
| DELETE | /api/ai/conversations/{id} |
删除对话 |
查询参数: message, conversationId?(不传则新建对话)
agent_type:default / consultation / health / diet / medication / report / exercise
SSE 事件格式:
data: {"action":"notice","message":"正在分析..."} // Tool Calling 前先发提示
data: {"action":"notice","message":"正在记录血压数据..."} // 每次调 Tool 前都发
data: {"action":"answer","data":"你"}
data: {"action":"answer","data":"的"}
data: {"action":"answer","data":"血压"}
data: {"action":"answer","data":"是"}
...
data: {"action":"tool_result","tool":"record_health_data","data":{...}}
data: {"action":"answer","data":"已记录"}
...
data: {"action":"conversation_id","data":"abc123"}
data: [DONE]
每次 Tool Calling 循环调 Tool 前,先发一条 notice 事件,避免用户对着空白等待。
4.3 VLM 食物识别 API
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/ai/analyze-food-image |
上传食物照片 |
请求: multipart/form-data,字段 images(支持多张)
响应:
{
"foods": [
{
"name": "米饭", "portion": "约1碗", "calories": 174,
"proteinGrams": 3.9, "carbsGrams": 38.9, "fatGrams": 0.3
}
],
"totalCalories": 644,
"warnings": ["红烧肉脂肪偏高"],
"score": 3
}
4.4 健康数据 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/health-records |
查询 ?type=&days= |
| POST | /api/health-records |
新增(AI Tool 或手动) |
| PUT | /api/health-records/{id} |
修改 |
| GET | /api/health-records/latest |
获取各指标最新值 |
| GET | /api/health-records/trend |
趋势数据 ?type=&period=7/30/90 |
4.5 用药管理 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/medications |
获取用药列表 |
| POST | /api/medications |
添加用药 |
| PUT | /api/medications/{id} |
修改用药 |
| DELETE | /api/medications/{id} |
删除用药 |
| POST | /api/medications/{id}/confirm |
确认服药打卡 |
4.6 饮食记录 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/diet-records |
查询 ?date=&meal_type= |
| POST | /api/diet-records |
新增 |
| PUT | /api/diet-records/{id} |
修改 |
| DELETE | /api/diet-records/{id} |
删除 |
4.7 运动计划 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/exercise-plans/current |
获取本周计划 |
| POST | /api/exercise-plans |
创建计划 |
| PUT | /api/exercise-plans/{id} |
修改计划 |
| POST | /api/exercise-plans/items/{id}/checkin |
打卡某日运动 |
4.8 报告管理 API
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/reports/upload |
上传报告文件 |
| GET | /api/reports |
报告列表 |
| GET | /api/reports/{id} |
报告详情(含 AI 解读) |
| PUT | /api/reports/{id}/doctor-review |
医生审核(医生端) |
4.9 问诊 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/doctors |
可咨询的医生列表 |
| POST | /api/consultations |
创建问诊 |
| GET | /api/consultations |
问诊列表 |
| GET | /api/consultations/{id} |
问诊详情(含消息) |
| POST | /api/consultations/{id}/messages |
发送消息 |
| GET | /api/consultations/{id}/messages?after={msgId} |
轮询新消息 |
| GET | /api/user/consultation-quota |
剩余问诊次数 |
4.10 用户与档案 API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/user/profile |
获取个人信息 |
| PUT | /api/user/profile |
修改资料 |
| GET | /api/user/health-archive |
获取健康档案 |
| PUT | /api/user/health-archive |
更新健康档案 |
| DELETE | /api/user/account |
注销账号 |
4.11 文件上传 API
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/files/upload |
上传文件(食物图/报告/处方) |
| GET | /api/files/{id} |
获取文件 |
| DELETE | /api/files/{id} |
删除文件 |
4.12 推送与通知 API
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/notifications/register-device |
注册设备推送 token |
| GET | /api/notifications/preferences |
获取通知偏好 |
| PUT | /api/notifications/preferences |
更新通知偏好 |
五、前端架构(Flutter)
5.1 目录结构
参考 YYZ 项目的组织方式:
lib/
├── main.dart # 入口
├── app.dart # MaterialApp + 主题 + 路由
│
├── core/ # 全局基础设施
│ ├── api_client.dart # Dio 封装:token 注入、401 自动刷新、统一报错
│ ├── app_theme.dart # 薰衣草紫主题
│ ├── app_router.dart # GoRouter 配置
│ └── secure_storage.dart # Token 安全存储
│
├── models/ # 数据模型(freezed)
│ ├── user.dart
│ ├── health_record.dart
│ ├── food_record.dart
│ ├── medication.dart
│ ├── exercise_plan.dart
│ ├── consultation.dart
│ ├── report.dart
│ └── ai_message.dart
│
├── providers/ # Riverpod 状态管理
│ ├── auth_provider.dart # 认证状态
│ ├── chat_provider.dart # 对话状态(当前 Agent + 消息列表)
│ ├── health_data_provider.dart # 健康数据
│ ├── medication_provider.dart # 用药
│ ├── diet_provider.dart # 饮食
│ ├── exercise_provider.dart # 运动计划
│ ├── consultation_provider.dart # 问诊
│ └── report_provider.dart # 报告
│
├── services/ # 业务服务层
│ ├── auth_service.dart # 认证
│ ├── ai_service.dart # AI 对话(SSE 流式)
│ ├── health_service.dart # 健康数据 CRUD
│ ├── diet_service.dart # 饮食 CRUD
│ ├── medication_service.dart # 用药 CRUD
│ ├── exercise_service.dart # 运动计划
│ ├── consultation_service.dart # 问诊
│ ├── report_service.dart # 报告
│ └── user_service.dart # 用户/档案
│
├── pages/ # 页面
│ ├── auth/
│ │ └── login_page.dart # 登录
│ │
│ ├── home/
│ │ ├── home_page.dart # 首页骨架(抽屉 + 卡片 + 对话流 + 输入框)
│ │ └── widgets/ # 首页专属组件
│ │ ├── task_card_area.dart # 任务卡片区
│ │ ├── agent_panel.dart # Agent 功能面板
│ │ ├── chat_bubble.dart # 聊天气泡
│ │ ├── data_confirm_card.dart# 数据确认卡片
│ │ ├── diet_result_card.dart # 饮食分析卡片
│ │ ├── food_edit_sheet.dart # 食物编辑面板
│ │ ├── medication_card.dart # 用药确认卡片
│ │ ├── report_card.dart # 报告解读卡片
│ │ └── health_drawer.dart # 侧滑抽屉
│ │
│ ├── profile/
│ │ ├── profile_page.dart # 个人中心
│ │ ├── edit_profile_page.dart # 编辑资料
│ │ └── health_archive_page.dart # 健康档案
│ │
│ ├── chart/
│ │ └── trend_page.dart # 趋势图表
│ │
│ ├── calendar/
│ │ └── health_calendar_page.dart # 健康日历
│ │
│ ├── medication/
│ │ ├── medication_list_page.dart # 用药列表
│ │ └── medication_edit_page.dart # 编辑用药
│ │
│ ├── report/
│ │ ├── report_list_page.dart # 报告列表
│ │ └── report_detail_page.dart # 报告详情
│ │
│ ├── consultation/
│ │ ├── doctor_list_page.dart # 医生列表
│ │ └── doctor_chat_page.dart # 问诊对话
│ │
│ ├── exercise/
│ │ └── exercise_plan_page.dart # 运动计划管理
│ │
│ ├── diet/
│ │ └── diet_record_list_page.dart# 饮食记录列表
│ │
│ ├── followup/
│ │ └── followup_list_page.dart # 复查列表
│ │
│ └── settings/
│ ├── settings_page.dart # 设置
│ ├── notification_prefs_page.dart
│ └── static_text_page.dart # 隐私/协议/关于(纯文本)
│
├── widgets/ # 通用组件
│ ├── app_button.dart # 主/次/文字按钮
│ ├── app_card.dart # 卡片容器
│ ├── app_input.dart # 输入框
│ ├── capsule_button.dart # 胶囊按钮
│ ├── loading_widget.dart # 加载动画
│ └── empty_state.dart # 空状态
│
└── utils/
├── sse_handler.dart # SSE 流处理
├── markdown_utils.dart # Markdown 渲染
└── format.dart # 日期/数值格式化
5.2 状态管理
使用 Riverpod(Notifier + NotifierProvider),参考 YYZ 模式:
// 认证
final authProvider = NotifierProvider<AuthNotifier, AuthState>(AuthNotifier.new);
// 聊天(核心)
final chatProvider = NotifierProvider<ChatNotifier, ChatState>(ChatNotifier.new);
// ChatState: 当前 agent_type、消息列表、conversationId、是否流式中
// 各业务域
final healthDataProvider = NotifierProvider<HealthDataNotifier, HealthDataState>(...);
final medicationProvider = NotifierProvider<MedicationNotifier, MedicationState>(...);
...
5.3 HTTP 请求层
// Dio 封装
class ApiClient {
late final Dio _dio;
// 拦截器 1:自动带 Authorization: Bearer {accessToken}
// 拦截器 2:收到 401 → 自动调 /api/auth/refresh → 重试
// 拦截器 3:统一错误处理
}
所有 Service 层返回类型安全的模型对象,页面不直接处理 JSON。
5.4 路由设计
| 路由 | 页面 | 说明 |
|---|---|---|
/login |
登录页 | 手机号+验证码登录 |
/home |
首页 | 对话流 + 任务卡片区 + Agent 面板 + 输入框 |
/trend/:type |
趋势图表 | 单指标折线图,7/30/90天切换 |
/calendar |
健康日历 | 月视图,标记用药/运动/复查 |
/medications |
用药列表 | 当前用药计划列表 |
/medications/:id/edit |
编辑用药 | 修改药品名/剂量/时间 |
/reports |
报告列表 | 按时间倒序 |
/reports/:id |
报告详情 | AI解读 + 医生审核 + 原始图片 |
/doctors |
医生列表 | 可选医生(3-4名) |
/consultation/:id |
问诊对话 | 患者与医生文字聊天 |
/exercise-plan |
运动计划 | 本周每天的运动安排+打卡 |
/diet-records |
饮食记录列表 | 按日期查看历史饮食 |
/profile |
个人中心 | 头像+诊断+菜单入口 |
/profile/edit |
编辑资料 | 姓名/性别/出生日期 |
/health-archive |
健康档案 | 完整健康信息(6大类) |
/followups |
复查列表 | 即将到来/已完成 |
/settings |
设置 | 隐私/通知/字体/协议/关于/退出 |
/settings/notifications |
通知偏好 | 四类推送独立开关 |
/page/:type |
静态文本页 | 隐私政策/服务协议/关于 |
5.5 首页组件交互
- 任务卡片区:
AnimatedContainer+GestureDetector折叠/展开 - 对话流:
ListView.builder+reverse: true - Agent 面板:底部弹出
AnimatedContainer,面板内容随 agent_type 切换 - 输入框:
TextField+Row,附件按钮 📎 + 系统键盘语音转文字 - 侧滑抽屉:
Scaffold+Drawer
5.6 Agent 切换流程
用户点击胶囊 [记数据]
→ 胶囊高亮
→ chatProvider.setAgentType(AgentType.health)
→ 底部弹出记数据面板(手动录入入口)
→ AI 对话上下文切换到记数据
→ 用户输入 → POST /api/ai/health/chat → SSE 流式
用户再次点击 [记数据]
→ 胶囊恢复
→ 面板收起
→ chatProvider.setAgentType(AgentType.default)
5.7 消息类型 Widget
| 消息类型 | Widget | 说明 |
|---|---|---|
| 纯文本 | ChatBubble | AI 左白底紫左边框,用户右紫底白字 |
| 数据确认 | DataConfirmCard | "已记录:血压 135/85 ✅" + 可点击编辑 |
| 饮食分析 | DietResultCard | 食物列表 + 热量 + 评分 + 警告 + "能不能吃" |
| 用药确认 | MedicationConfirmCard | 药名 + 剂量 + [确认] [修改] |
| 报告解读 | ReportCard | 指标列表 + AI 分析 + "待医生确认" |
| 快捷选项 | QuickOptionsRow | 一行可点击按钮,如 [闷痛] [刺痛] |
5.8 SSE 流处理
参考 YYZ 的 ChatStreamHandler 模式:
class SseHandler {
// 订阅 Dio 流式响应
// 解析 SSE 事件:answer / notice / tool_result / conversation_id / [DONE]
// 逐步追加 token 到当前 AI 消息
// 处理 tool_result 更新消息中的结构化数据
// onDone → 标记消息 status: 'done'
}
5.9 平台支持
- Android 最低:7.0(API 24)
- iOS 最低:15.0
- 不做暗黑模式
5.10 Flutter 依赖
dependencies:
flutter:
sdk: flutter
# 状态管理
flutter_riverpod: ^3.2.0
# HTTP
dio: ^5.4.0
# 安全存储
flutter_secure_storage: ^9.2.0
# 路由
go_router: ^14.0.0
# 图表
fl_chart: ^0.68.0
# 本地存储
shared_preferences: ^2.2.0 # 轻量键值存储(字体大小等设置)
# 相机 & 图片
image_picker: ^1.0.0
file_picker: ^10.3.7
# 推送
jpush_flutter: ^4.0.0 # 极光推送
dev_dependencies:
flutter_test:
sdk: flutter
freezed: ^2.5.0
json_serializable: ^6.8.0
build_runner: ^2.4.0
六、定时任务
6.1 用药提醒扫描
.NET BackgroundService 每分钟扫描一次:
public class MedicationReminderService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 1. 查询:reminder_enabled = true AND 服药时间 ≤ 当前时间 AND 今天未打卡
// 2. 对每条 → 查用户 device_token → 调用极光推送
// 3. 记录推送日志
// 4. 检查漏服(超时未打卡 → 再推 → 标记漏服)
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
6.2 漏服检测
- 服药时间已过 15 分钟未打卡 → 再推一次
- 服药时间已过 30 分钟未打卡 → 再推一次
- 服药时间已过 1 小时未打卡 → 标记漏服,停止提醒
6.3 其他定时任务
| 任务 | 频率 | 说明 |
|---|---|---|
| 复查提醒 | 每天 1 次 | 检查未来 3 天内的复查计划 → 推送 |
| 对话记录清理 | 每天 1 次 | 删除 30 天前的对话 |
| 验证码清理 | 每小时 1 次 | 删除过期验证码 |
七、文件存储
7.1 MinIO 存储桶
| 存储桶 | 内容 | 生命周期 |
|---|---|---|
food-images |
食物照片 | 30 天 |
report-images |
报告图片/PDF | 永久 |
avatars |
用户头像 | 永久 |
7.2 上传流程
Flutter → Dio multipart → .NET API → 存入 MinIO → 返回文件 URL
→ VLM 食物识别时:文件 URL 传给千问 VL
7.3 图片上传规格
| 参数 | 食物照片 | 报告图片/PDF |
|---|---|---|
| 支持格式 | JPEG / PNG / HEIC | JPEG / PNG / HEIC / PDF |
| 最大文件 | 10MB | 10MB |
| 前端处理 | 原图上传,不压缩 | 原图上传,不压缩 |
八、推送通知
8.1 推送架构
.NET BackgroundService → 极光推送 SDK → 极光服务器 → APNs (iOS) / 极光通道 (Android)
- 设备注册时 Flutter 获取极光 registration ID → 上报后端存入 device_tokens 表
- 后端推送时查 device_tokens → 调用极光推送 API
8.2 推送类型
| 类型 | 触发 | iOS | Android |
|---|---|---|---|
| 用药提醒 | BackgroundService | 极光 → APNs | 极光通道 |
| 复查提醒 | BackgroundService | 同上 | 同上 |
| 医生回复 | 医生发消息时 | 同上 | 同上 |
| 异常警告 | 硬件上传异常时(后期) | 同上 | 同上 |
8.3 点击行为
| 通知类型 | 点击后跳转 |
|---|---|
| 用药提醒 | 首页 + 自动打开药管家 |
| 复查提醒 | 复查详情 |
| 医生回复 | 该问诊对话 |
| 异常警告 | 该指标趋势页 |
九、AI 模型配置
| 用途 | 模型 | API 地址 |
|---|---|---|
| LLM 对话 | DeepSeek deepseek-chat |
https://api.deepseek.com/v1 |
| VLM 食物识别 | 千问 qwen-vl-max |
https://dashscope.aliyuncs.com/compatible-mode/v1 |
| VLM 备用 | 火山引擎 doubao-vision-pro |
https://ark.cn-beijing.volces.com/api/v3 |
十、部署
10.1 本地开发
dotnet run启动后端- PostgreSQL + MinIO 用 Docker 本地拉起
- Flutter
flutter run连模拟器/真机调试
10.2 后期上云
- .NET 10 Web API,Docker 单容器部署
- PostgreSQL → 阿里云 RDS
- MinIO → 阿里云 OSS
- 配置通过环境变量注入
十一、开发顺序
第一阶段:基础设施
| # | 任务 |
|---|---|
| 1 | .NET 后端项目搭建 + Clean Architecture 骨架 |
| 2 | PostgreSQL 数据库初始化 + EF Core 迁移 |
| 3 | JWT 认证系统 |
| 4 | Flutter 项目搭建 + 路由 + 主题 |
| 5 | Dio 封装 + 401 拦截器 |
| 6 | OpenAiCompatibleClient 实现 |
第二阶段:核心 AI
| # | 任务 |
|---|---|
| 7 | PromptManager + 7 个 Agent 的 System Prompt |
| 8 | Tool Calling 引擎 |
| 9 | 默认对话 Agent + SSE 流式 |
| 10 | 记数据 Agent |
| 11 | 药管家 Agent |
| 12 | 拍饮食 Agent(含 VLM) |
| 13 | AI 问诊 Agent |
| 14 | 看报告 Agent |
| 15 | 运动计划 Agent |
第三阶段:业务功能
| # | 任务 |
|---|---|
| 16 | 健康数据 CRUD API |
| 17 | 饮食记录 API |
| 18 | 用药管理 API + 服药提醒后台任务 |
| 19 | 运动计划 API |
| 20 | 报告上传 + AI 解读 API |
| 21 | 问诊 API(医生列表 + 消息轮询) |
第四阶段:前端
| # | 任务 |
|---|---|
| 22 | 登录页 |
| 23 | 首页骨架(抽屉 + 卡片区 + 对话流 + 输入框) |
| 24 | 6 个 Agent 面板 UI |
| 25 | SSE 流处理 + 消息气泡 |
| 26 | 数据确认卡片 / 饮食分析卡片 / 用药确认卡片 |
| 27 | 侧滑抽屉(健康概览 + 历史对话) |
| 28 | 各功能页面(个人中心、健康档案、趋势图、报告等) |
第五阶段:联调 + 完善
| # | 任务 |
|---|---|
| 29 | 极光推送集成 |
| 30 | MinIO 文件存储集成 |
| 31 | 短信服务对接 |
| 32 | 异常处理完善 |
| 33 | 联调测试 |