# 健康管家 — 全面代码审查与Bug文档

> 审查日期：2026-06-10　|　范围：全栈（后端 .NET 10 + 前端 Flutter）

---

## 测试结果汇总

| 测试集 | 通过 | 失败 | 说明 |
|--------|------|------|------|
| 后端单元测试 (entity_tests) | 10 | 0 | 实体/数据库操作全通过 |
| 后端单元测试 (auth_tests) | 4 | 0 | 认证流程全通过 |
| 后端单元测试 (ai_agent_tests - PromptManager) | 8 | 0 | AI提示词全通过 |
| 后端集成测试 (ai_agent_tests - AI对话) | 0 | 5 | 需要后端运行中 |
| Flutter 测试 (widget_test) | - | - | sqlite3 原生库下载失败，无法运行 |

**总计：22/27 通过（5个集成测试需要后端在线）**

---

## 一、后端 Bug

### B1. 缺少 consultation DELETE 端点
- **位置**：`consultation_endpoints.cs`
- **问题**：问诊创建后无删除/取消接口。患者无法取消发起的问诊
- **影响**：患者发起错误问诊后无法撤销
- **建议**：添加 `MapDelete("/consultations/{id:guid}", ...)` 

### B2. ai_chat_endpoints 中 unified agent 缺少部分工具
- **位置**：`ai_chat_endpoints.cs:340-347`
- **问题**：unified agent 虽聚合了 7 种工具，但缺少 `manage_archive` 和 `request_doctor`
- **影响**：用户在 unified 模式无法通过对话修改档案或请求医生

### B3. CompressImage 未释放 GDI 资源
- **位置**：`ai_chat_endpoints.cs:484-503`
- **问题**：`Image.FromFile`、`Bitmap`、`Graphics` 均 `using` 包裹，但 `EncoderParameters` 未释放
- **影响**：每次食物识别可能泄漏少量非托管内存
- **修复**：`using var parameters = new EncoderParameters(1);`

### B4. 用药提醒服务可能定时查询过于频繁
- **位置**：`BackgroundServices/medication_reminder_service.cs`
- **问题**：需要检查轮询间隔，每分钟查一次可能对数据库压力大

### B5. 开发数据种子硬编码 API Key 检查
- **位置**：`dev_data_seeder.cs`
- **问题**：`DEVDATA_ENABLED=true` 创建测试用户，生产环境可能误开启

---

## 二、前端 Bug

### C1. ~~聊天列表从顶部跳到底部~~ ✅ 已修复
- **修复**：`chat_messages_view.dart` 改为 `reverse:true`
- **修复**：`home_page.dart` 滚动逻辑简化

### C2. ~~SwipeDeleteTile 内层手势冲突~~ ✅ 已修复  
- **修复**：`common_widgets.dart` 滑动状态下用 `AbsorbPointer` 屏蔽内部按钮

### C3. ~~SwipeDeleteTile 红色溢出~~ ✅ 已修复
- **修复**：margin 参数从 Stack 内部移到外部 Padding

### C4. ~~运动打卡 dayOfWeek 索引偏差~~ ✅ 已修复
- **修复**：`remaining_pages.dart` 改为 `weekday % 7` 与 C# DayOfWeek 对齐

### C5. ~~用药管理 Dismissible 一步删除~~ ✅ 已修复
- **修复**：统一使用 `SwipeDeleteTile`

### C6. Flutter 测试断言错误
- **位置**：`health_app/test/widget_test.dart:9`
- **代码**：`expect(AppTheme.primary, AppTheme.primaryLight);`
- **问题**：`primary` (0xFF6366F1) ≠ `primaryLight` (0xFFEEF2FF)，这个断言必然失败
- **修复**：改为 `expect(AppTheme.primary, const Color(0xFF6366F1));`

### C7. home_page.dart onTap 手误写了 `?.()`
- **位置**：`home_page.dart:115`
- **代码**：`onTap: () => pushRoute(ref, 'notificationPrefs')` 实际没有 `?.()` 问题（之前看错了），此处无误

### C8. 报告详情返回按钮：popRoute 在 setState 后
- **位置**：`report_pages.dart:449-454`
- **问题**：`clearAnalysis()` 调用 `state.copyWith(...)` 然后立即 `popRoute(ref)`，在 pop 过程中可能访问已 dispose 的 provider
- **风险**：中等

### C9. DietCapturePage TextField 内存泄漏
- **位置**：`diet_capture_page.dart:465,479,495`
- **问题**：每个食物项创建 `TextEditingController(text: food.name)` 但从不 dispose
- **修复**：用 `TextFormField` + `initialValue` 或缓存 controller

### C10. 报告上传错误吞没
- **位置**：`report_pages.dart:199`
- **代码**：`} catch (_) {}` — 上传失败无任何反馈
- **修复**：至少显示 snackbar 提示

---

## 三、前端缺失功能

### M1. 饮食记录无编辑/修改功能
- 当前只能查看（`DietRecordDetailPage`）和删除，无法修改已有记录

### M2. 运动计划详情页缺失
- `ExercisePlanPage` 的 `onTap: () {}` 为空，点卡片无反应
- 没有类似 `ExercisePlanDetailPage` 的页面

### M3. 问诊列表无前端入口
- 后端有 `/api/consultations` GET，但前端没有问诊历史列表页

### M4. 对话无删除/清空功能
- 后端有 `DELETE /api/ai/conversations/{id}`，但前端无对应UI

### M5. 无数据导出功能
- 用户无法导出健康数据（血压记录、饮食记录等）

---

## 四、安全与代码质量

### S1. JWT Secret 开发默认值
- **位置**：`Program.cs:46`
- **问题**：`jwtSecret ??= "dev-secret-key-change-in-production-min-32-chars!!";`
- **风险**：若忘记设环境变量，生产环境用弱密钥

### S2. token 通过 query string 传输（SSE）
- **位置**：`ai_chat_endpoints.cs:25`, `sse_handler.dart:17`
- **问题**：`token` 放在 URL query string，会被服务器日志、代理缓存
- **风险**：中等（含过期时间的 token，但仍有泄露风险）

### S3. CORS 全开
- **位置**：`Program.cs:96-98`
- **代码**：`policy.SetIsOriginAllowed(_ => true)`
- **风险**：生产环境应限定具体 origin

### S4. 全局异常中间件可能泄露内部错误
- **位置**：`exception_middleware.cs`
- **需要检查**：是否在生产环境返回了调用栈

### S5. Flutter 硬编码后端 IP
- **位置**：`api_client.dart:6`
- **代码**：`const String baseUrl = 'http://10.4.164.158:5000';`
- **问题**：每次换网络都需改代码

---

## 五、未使用代码（可清理）

- `chat_messages_view.dart`: `_cardFilledBtn`, `_cardOutlineBtn`, `_agentColors`, `_taskRow` 未使用
- `chat_provider.dart`: `_parseAgent` 未使用
- `remaining_pages.dart`: `shadcn_ui` 导入未使用
- `report_pages.dart`: `shadcn_ui` 导入未使用，`reportId` 变量未使用
- `service_package_detail_page.dart`: `navigation_provider` 导入未使用

---

## 六、Agent 自动审查新增发现

### A1. app_router.dart 无防御 null 断言（🔴严重）
- **位置**：`app_router.dart:41-73`
- **代码示例**：`ReportDetailPage(id: params['id']!)` 等多处
- **问题**：`params['id']!` 若 params 中无 'id' 键，会抛出 null 断言异常导致崩溃
- **影响**：任何路由参数拼写错误或缺少都会 crash
- **修复**：使用 `params['id'] ?? ''` 并提供 fallback

### A2. device_scan_page.dart BLE 流订阅未取消（🔴严重）
- **位置**：`device_scan_page.dart`
- **问题**：BLE stream subscription 在 dispose 时未 cancel，导致蓝牙连接泄漏
- **影响**：多次进出扫描页会积累未释放的 BLE 连接

### A3. 静默吞错误（🟡中）
- **范围**：全前端 28 处 `catch (_) {}`
- **问题**：所有 API 错误、解析错误均无日志或用户提示
- **修复**：至少加上 `debugPrint('Error: $e')` 或显示 snackbar

### A4. 删除后未刷新 Provider（🟡中）
- **范围**：多个页面
- **问题**：删除操作后调用了 `_load()` 或 `_refresh()`（setState），但未调用 `ref.invalidate(someProvider)`，导致 Riverpod 缓存未更新
- **影响**：切换到其他页面再回来，旧数据可能仍显示

### A5. FutureProvider + setState 双重模式（🟢低）
- **范围**：`DietRecordListPage`, `ExercisePlanPage` 等
- **问题**：同时使用 Riverpod FutureProvider 和本地 setState，导致 provider 失效无效

---

## 七、全部问题汇总（含Agent发现）

| # | 优先级 | 位置 | 问题描述 |
|---|--------|------|----------|
| 1 | 🔴 | `app_router.dart` | null 断言无防御，参数缺失即崩溃 |
| 2 | 🔴 | `device_scan_page.dart` | BLE 流订阅未取消 |
| 3 | 🔴 | `B1` consultation | 缺少 DELETE 端点 |
| 4 | 🔴 | `S1` Program.cs | JWT 开发默认弱密钥 |
| 5 | 🔴 | `C9` diet_capture | TextEditingController 泄漏 |
| 6 | 🟡 | 全局 28处 | catch(_){} 静默吞错 |
| 7 | 🟡 | 多页面 | 删除后无 ref.invalidate |
| 8 | 🟡 | `C6` widget_test | 断言 primary == primaryLight 错误 |
| 9 | 🟡 | `C10` report | 上传失败无提示 |
| 10 | 🟡 | `M2` exercise | 运动详情页缺失，onTap 空 |
| 11 | 🟡 | `C8` report | pop 时序问题 |
| 12 | 🟡 | `A5` 多页面 | FutureProvider+setState 双重模式 |
| 13 | 🟢 | `B3` ai_chat | EncoderParameters 未释放 |
| 14 | 🟢 | `B4` bg_service | 用药提醒轮询频率需审视 |
| 15 | 🟢 | `B5` dev_data | 生产环境可能误开启测试数据 |
| 16 | 🟢 | `S2` SSE | token 走 query string |
| 17 | 🟢 | `S3` CORS | 全开 AllowCredentials |
| 18 | 🟢 | `S5` api_client | 硬编码 IP |
| 19 | 🟢 | 多处 | 未使用代码可清理 |
| 20 | 🟢 | `B2` unified | unified agent 缺少 manage_archive 工具 |

---

## 八、后端 Agent 审查新增发现（关键）

### D1. 运动计划 DayOfWeek 跨层不一致（🔴严重）
- **位置**：`prompt_manager.cs` vs `exercise_plan.cs` 实体注释
- **问题**：Prompt 告诉 AI `day_of_week: 0-6(周日=0)`，但实体注释 `// 0=周一, 6=周日`。AI 按周日=0 生成数据，后端按周一=0 解析，运动计划星期全偏一天
- **修复**：统一为 C# DayOfWeek 枚举（0=周日）

### D2. SMS 验证码使用伪随机数（🔴安全漏洞）
- **位置**：`sms_service.cs`
- **问题**：`Random.Shared.Next(100000, 1000000)` 使用 PRNG，攻击者可预测验证码
- **修复**：改用 `RandomNumberGenerator.GetInt32(100000, 999999)`

### D3. checkin 无所有权验证（🔴越权漏洞）
- **位置**：`medication_agent_handler.cs:99`、`exercise_agent_handler.cs:69`
- **问题**：`confirm_medication` 和 `exercise checkin` 直接通过 itemId 操作，不验证是否属于当前用户。任何认证用户可操作他人数据
- **修复**：添加 `&& item.Plan.UserId == userId` 检查

### D4. VisionAsync content 序列化错误（🔴严重）
- **位置**：`open_ai_compatible_client.cs:136`
- **问题**：将图片 contentParts 先序列化为 JSON 字符串再赋值给 Content，但 OpenAI 兼容 API 期望 Content 为数组格式
- **影响**：食物识别 VLM 调用可能失败

### D5. diet/consultation agent 工具声明但未实现（🔴严重）
- **位置**：`diet_agent_handler.cs`、`consultation_agent_handler.cs`
- **问题**：`EstimateFoodTool` 和 `RequestDoctorTool` 在 Tools 列表中声明，但 Execute 方法无对应 case，调用返回"未知工具"
- **影响**：饮食识别和请求医生功能不可用

### D6. CleanupService 删除未级联（🔴严重）
- **位置**：`cleanup_service.cs:28`
- **问题**：`db.Conversations.RemoveRange(oldConversations)` 未先删除关联的 ConversationMessage，可能因 FK 约束抛异常
- **修复**：先删除 Messages 再删 Conversations，或使用 ExecuteDeleteAsync

### D7. 用药提醒时区计算 bug（🔴严重）
- **位置**：`medication_reminder_service.cs:37`
- **问题**：`DateTime.SpecifyKind(beijingNow.Date, DateTimeKind.Utc)` — 北京时间 00:00 被标记为 UTC 00:00，导致打卡检测有 8 小时偏差
- **影响**：提醒时间错位、重复提醒或漏提醒

### D8. DbContext 未配置外键和级联删除（🟡中等）
- **位置**：`app_db_context.cs`
- **问题**：`OnModelCreating` 没有任何 `HasOne/WithMany/HasForeignKey/OnDelete` 配置，全凭约定
- **影响**：数据库无 FK 约束、级联行为不明确、RefreshToken 全表扫描

### D9. 缺少多个数据库索引（🟡中等）
- RefreshToken: 无索引，认证查询全表扫描
- FollowUp: 无 (UserId, ScheduledAt) 索引
- DeviceToken: 无 UserId 索引
- Report: 无 (UserId, CreatedAt) 索引

### D10. ExceptionMiddleware 统一返回500（🟡中等）
- **位置**：`exception_middleware.cs`
- **问题**：所有异常都返回 500，不区分 400/401/404
- **影响**：客户端无法根据状态码处理不同类型的错误

### D11. 用药提醒未实际推送（🟡中等）
- **位置**：`medication_reminder_service.cs`
- **问题**：TODO 注释表明推送尚未实现，只记录日志

### D12. Prompt 文本硬编码（🟢低）
- 不支持热更新，修改需重新编译

---

## 九、最终汇总（所有发现）

| # | 优先级 | 类别 | 位置 | 问题 |
|---|--------|------|------|------|
| 1 | 🔴 | 安全 | sms_service | 验证码用 PRNG 可预测 |
| 2 | 🔴 | 安全 | agent handlers | checkin 越权漏洞 |
| 3 | 🔴 | 逻辑 | prompt/entity | DayOfWeek 跨层不一致 |
| 4 | 🔴 | 功能 | diet/consult agent | 工具声明但未实现 |
| 5 | 🔴 | 功能 | open_ai_client | Vision content 序列化错误 |
| 6 | 🔴 | 稳定性 | cleanup_service | 删除未级联 FK 冲突 |
| 7 | 🔴 | 功能 | reminder_service | 时区计算 8h 偏差 |
| 8 | 🔴 | 崩溃 | app_router.dart | null 断言无防御 |
| 9 | 🔴 | 泄漏 | device_scan_page | BLE 流未取消 |
| 10 | 🔴 | 安全 | Program.cs | CORS 全开+AllowCredentials |
| 11 | 🔴 | 安全 | Program.cs | JWT 默认弱密钥 |
| 12 | 🔴 | 泄漏 | diet_capture | TextEditingController 未释放 |
| 13 | 🟡 | 体验 | 28处 | catch(_){} 静默吞错 |
| 14 | 🟡 | 逻辑 | 多页面 | 删除后未 invalidate provider |
| 15 | 🟡 | 配置 | app_db_context | 无 FK/级联/索引 |
| 16 | 🟡 | 体验 | exception_middleware | 统一返回 500 |
| 17 | 🟡 | 测试 | widget_test | 断言永远失败 |
| 18 | 🟡 | 功能 | 用药提醒 | 推送未实现 |
| 19 | 🟡 | 架构 | 多页面 | FutureProvider+setState 混用 |
| 20 | 🟡 | 缺失 | exercise | 计划详情页缺失 |
| 21 | 🟢 | 代码 | 多处 | 未使用代码/本地时间/冗余 |

---

## 十、后端端点 Agent 审查新增发现（关键）

### E1. 医生端点零授权（🔴阻断级）
- **位置**：`doctor_endpoints.cs:19`
- **问题**：`MapGroup("/api/doctor")` **没有 `.RequireAuthorization()`**，所有医生端点（患者详情、健康数据、问诊、报告、随访）对公网开放
- **影响**：任何人可查看所有患者隐私数据、修改报告、创建/删除随访

### E2. consultation POST 消息无所有权检查（🔴阻断级）
- **位置**：`consultation_endpoints.cs:48-75`
- **问题**：发消息只检查 consultation 存在，不验证是否属于当前用户。用户A可向用户B的问诊发消息

### E3. exercise checkin 无所有权检查（🔴阻断级）
- **位置**：`exercise_endpoints.cs:119`
- **问题**：`FindAsync([itemId])` 只按ID查，不验证 `item.Plan.UserId == userId`。用户可操作他人运动计划

### E4. SMS验证码在响应中暴露（🔴严重）
- **位置**：`auth_endpoints.cs:34`
- **问题**：`devCode = code` 将6位验证码直接返回在JSON中，无 `#if DEBUG` 守卫

### E5. Task.Run 火后不理模式（🔴严重）
- **位置**：`report_endpoints.cs:67`
- **问题**：`_ = Task.Run(async () => { ... }, CancellationToken.None)` 在请求结束后 scope 可能已释放，后台任务崩溃

### E6. System.Drawing 仅Windows（🔴严重）
- **位置**：`ai_chat_endpoints.cs:486-494`
- **问题**：`Image.FromFile`/`Bitmap`/`Graphics` 在Linux容器中崩溃

### E7. 健康数据 N+1 查询（🟡中等）
- **位置**：`health_endpoints.cs:78-89`
- **问题**：`/latest` 对5种指标类型分别发一次SQL查询

### E8. 日历用药事件显示错误（🟡中等）
- **位置**：`calendar_endpoints.cs:49`
- **问题**：用户有任意活跃用药就在每月每天标记"用药"，不区分具体哪天该吃药

### E9. 手动JSON解析绕过模型绑定（🟡中等）
- **范围**：diet、medication、exercise、doctor endpoints
- **问题**：`JsonDocument.Parse` 手动解析导致 Swagger 无法文档化、无自动验证、拼写错误抛500

### E10. 缺失端点
- health：缺 DELETE
- diet：缺 PUT
- exercise：缺 PUT  
- report：缺 DELETE
- file：缺 GET/DELETE/list
- followup：缺 detail/confirm

---

## 十一、完整问题排名

| # | 等级 | 文件 | 问题 |
|---|------|------|------|
| 1 | 🔴🔴 | doctor_endpoints | **零授权** — 所有患者数据公开 |
| 2 | 🔴🔴 | consultation | 发消息无所有权检查 |
| 3 | 🔴🔴 | exercise | checkin无所有权检查 |
| 4 | 🔴 | auth | SMS验证码响应暴露 |
| 5 | 🔴 | report | Task.Run火后不理 |
| 6 | 🔴 | ai_chat | System.Drawing仅Windows |
| 7 | 🔴 | sms_service | PRNG可预测验证码 |
| 8 | 🔴 | agent handlers | checkin越权 |
| 9 | 🔴 | prompt_manager | DayOfWeek不一致 |
| 10 | 🔴 | diet/consult agent | 工具声明未实现 |
| 11 | 🔴 | open_ai_client | Vision序列化错误 |
| 12 | 🔴 | cleanup_service | 删除未级联 |
| 13 | 🔴 | reminder_service | 时区8h偏差 |
| 14 | 🔴 | app_router | null断言崩溃 |
| 15 | 🔴 | device_scan | BLE泄漏 |
| 16 | 🔴 | Program.cs | CORS全开 |
| 17 | 🔴 | Program.cs | JWT弱密钥 |
| 18 | 🔴 | diet_capture | Controller泄漏 |
| 19 | 🟡 | 28处 | catch(_){}吞错 |
| 20 | 🟡 | 多页面 | 删除未invalidate |
| 21 | 🟡 | health | N+1查询 |
| 22 | 🟡 | calendar | 用药事件逻辑错误 |
| 23 | 🟡 | app_db_context | 无FK/索引配置 |
| 24 | 🟡 | exception_mw | 统一500 |
| 25 | 🟡 | 提醒服务 | 推送未实现 |
| 26 | 🟡 | 多端点 | 缺PUT/DELETE |
| 27 | 🟡 | 多端点 | 手动JSON无验证 |

---

## 优先级排序

| 优先级 | Bug | 影响范围 |
|--------|-----|----------|
| 🔴 高 | C9: TextEditingController 内存泄漏 | 饮食识别页面 |
| 🔴 高 | B1: 缺少 consultation DELETE | 问诊功能 |
| 🔴 高 | S1: JWT 弱密钥默认值 | 安全 |
| 🟡 中 | C6: Flutter 测试断言错误 | CI/CD |
| 🟡 中 | C10: 报告上传错误吞没 | 用户体验 |
| 🟡 中 | M2: 运动计划详情页缺失 | 用户体验 |
| 🟡 中 | C8: report pop 时序问题 | 潜在崩溃 |
| 🟢 低 | B3: EncoderParameters 未释放 | 极少量泄漏 |
| 🟢 低 | S5: 硬编码 IP | 开发体验 |
| 🟢 低 | 未使用代码 | 代码质量 |

---

*共发现 15 个问题，5 个缺失功能。其中 5 个 Bug 已在本轮修复。*

---

## 第二轮换角度审查（新增 10 个问题）

### F1. uploadFile 响应解析崩溃 🔴
`api_client.dart:60-69` — 后端返回 `data: [{id, name, size}]`（List），前端做 `data['data']?['url']` 对List用字符串索引，运行时异常。聊天图片上传全部无声失败。

### F2. consultationChatProvider SignalR 永停不了 🔴
`consultation_provider.dart` — `stop()` 定义了但无 Widget dispose 调用。离开问诊页后 SignalR 连接和 5秒轮询永续运行。

### F3. chatProvider 流订阅无 dispose 🔴
`chat_provider.dart` — `_subscription`/`_streamTimer` 无 dispose，provider 销毁即泄漏。

### F4. ChatMessage 原地修改破坏不可变性 🔴
`msg.confirmed = true` 直接修改 state 中的对象 → 违反 Riverpod 不可变契约。

### F5. 所有 FutureProvider 无 autoDispose 🟡
6 个 FutureProvider 缓存永不过期，页面级数据不释放。

### F6. authProvider Token 刷新窗口期 id/phone 为空 🟡
`auth_provider.dart:59` — `UserInfo(id: '', phone: '')` 然后等 `_loadProfile()` 异步补全。

### F7. 医生 Web 零认证 🔴🔴
`doctor_web/src/services/api-client.ts` — 不发送 Authorization 头。叠加后端 doctor_endpoints 零授权（E1），任何人可访问所有患者数据。

### F8. 医生 Web SignalR handler 泄漏 🔴
`ChatPage.tsx:28-65` — 组件卸载时若在 Reconnecting 状态，跳过 `off('ReceiveMessage')`，重复挂载累积 handler。

### F9. 6个后端端点前端从未调用 🟢
`GET/DELETE /api/ai/conversations`、`GET /api/consultations`、`PUT /api/health-records/{id}`、全部 `/api/doctor/*`

### F10. 37 对 API 契约中 1 对崩溃 + 多端手动JSON脆弱 🟡
`uploadFile` 是唯一崩溃的。其余 36 对匹配但 `TryGetProperty` 区分大小写，依赖前端恰好用 camelCase。

---

*两轮共发现 37 个问题，其中 5 个阻断级、19 个严重、10 个中等、3 个低。*
