# 健康管家 — 技术设计文档 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 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.new); // 聊天(核心) final chatProvider = NotifierProvider(ChatNotifier.new); // ChatState: 当前 agent_type、消息列表、conversationId、是否流式中 // 各业务域 final healthDataProvider = NotifierProvider(...); final medicationProvider = NotifierProvider(...); ... ``` ### 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 | 联调测试 |