- 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
1255 lines
41 KiB
Markdown
1255 lines
41 KiB
Markdown
# 健康管家 — 技术设计文档 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 返回统一结构:
|
||
|
||
```json
|
||
// 成功
|
||
{ "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)
|
||
- 自动重试
|
||
|
||
```csharp
|
||
// 调用示例
|
||
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 对话的核心编排逻辑:
|
||
|
||
```csharp
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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
|
||
|
||
```sql
|
||
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 索引设计
|
||
|
||
```sql
|
||
-- 健康记录:按用户+时间查询高频
|
||
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`(支持多张)
|
||
|
||
**响应:**
|
||
```json
|
||
{
|
||
"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 模式:
|
||
|
||
```dart
|
||
// 认证
|
||
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 请求层
|
||
|
||
```dart
|
||
// 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` 模式:
|
||
|
||
```dart
|
||
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 依赖
|
||
|
||
```yaml
|
||
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` 每分钟扫描一次:
|
||
|
||
```csharp
|
||
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 | 联调测试 |
|