# 健康管家 — 全面代码审查与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 个低。*