Files
AI-Health/docs/BUG_REVIEW.md
MingNian 63d2092c24 feat: UI全面改造 + 后端多项修复
【后端修复】
- 删除diet/consultation agent假工具声明
- 修复DayOfWeek实体注释
- 修复Vision API content序列化
- cleanup_service级联删除修复
- 用药提醒时区偏差修复
- 统一DateTime处理(UtcNow+8)
- 新增UTC DateTime JSON转换器

【前端UI重构】
- 配色体系全面更新(#8B5CF6淡紫+#F0ECFF背景)
- 登录页重设计
- 首页重设计(透明顶栏、渐变背景、胶囊输入区)
- 聊天卡片加白蓝边框、渐变标题
- 侧边栏重构(渐变背景、合并顶部、删除底部设置)
- 确认卡片可编辑字段恢复
- 所有子页面加返回按钮
- catch异常加日志
- 删除后refresh provider缓存
2026-06-10 18:27:38 +08:00

439 lines
20 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 健康管家 — 全面代码审查与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` 无 disposeprovider 销毁即泄漏。
### 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 个低。*