# 健康项目深度分析报告

日期：2026-06-17

## 1. 分析范围与结论概览

本次分析覆盖了 Flutter 客户端、ASP.NET Core 后端、AI 问诊/饮食识别链路、路由、数据存储、UI 结构、测试与工程配置。执行过的主要检查包括：

- `flutter analyze`：当前客户端有 162 条 analyzer/lint 问题，其中包含错误导入、未使用代码、异步上下文风险、废弃 API、调试输出等。
- `dotnet test`：后端测试未能真正跑完，原因是正在运行的 `Health.WebApi` 进程锁住了 `bin/Debug/net10.0` 下的 DLL，导致构建复制失败。
- 重点阅读：`api_client.dart`、`chat_provider.dart`、`app_router.dart`、`data_providers.dart`、`local_database.dart`、`Program.cs`、`auth_endpoints.cs`、`ai_chat_endpoints.cs`、`file_endpoints.cs`、测试目录等。

整体判断：项目功能面铺得很广，已经具备“患者端健康管理 + AI 助手 + 饮食识别 + 用药/报告/日历/随访 + 医生/管理员”的雏形。但目前最大问题不是单个页面丑，而是功能深度、数据安全、接口契约、状态管理、UI 体系和测试可信度还没有收拢。现在已经到了需要“先稳核心链路，再美化体验”的阶段。

## 2. 最需要优先处理的问题

| 优先级 | 问题 | 影响 | 建议 |
| --- | --- | --- | --- |
| P0 | 短信验证码接口返回 `devCode`，登录页还会自动填充 | 真实环境下等于绕过验证码安全 | 仅开发环境返回，生产环境绝不返回；前端删除自动填充逻辑 |
| P0 | SSE token 放在 query 中，后端用 `ReadJwtToken` 读取但没有验证签名 | 可被伪造用户 ID，存在严重认证风险 | SSE 也必须走标准 JWT 验证，或使用一次性短票据 |
| P0 | AI 会话按 `conversationId` 查询后未确认归属用户 | 可能跨用户写入/读取会话 | 所有 conversation 查询都加 `UserId == currentUserId` |
| P1 | 文件上传前后端契约不一致 | 图片上传后前端拿不到 URL，AI 图片消息可能只保存本地路径 | 后端返回可访问 URL，或前端按后端返回结构处理 |
| P1 | token 和健康数据存在本地 SQLite，未加密 | 手机丢失或被调试时有泄露风险 | 使用 secure storage 保存 token，敏感健康数据考虑加密 |
| P1 | CORS 允许任意 Origin 且允许 Credentials | Web 场景下有跨站风险 | 按环境配置白名单 |
| P1 | 后端用 `EnsureCreatedAsync` 而不是 migrations | 数据库升级不可控 | 建立 EF migrations 流程 |
| P1 | UI 设计体系不统一 | 页面之间像不同产品拼接，后期维护难 | 建立颜色、间距、字体、组件规范 |
| P2 | Flutter 存在大量未使用代码、旧页面、调试输出 | 维护成本高，也容易藏 bug | 分模块清理，保留真实业务入口 |
| P2 | 测试依赖运行中的本地服务和不安全 dev 行为 | CI 不可靠，安全问题被测试固化 | 单元/集成测试分层，测试环境用隔离配置 |

## 3. 功能层面分析

### 3.1 功能广度够，但关键流程深度不够

目前功能入口很多：AI 问诊、记数据、饮食拍照、用药、报告、运动、健康档案、复查随访、医生端、管理员端等。这个方向是对的，但现在许多功能更像“有入口、有页面、有部分数据”，还没有形成足够扎实的闭环。

建议优先把以下闭环做深：

1. 健康数据闭环：记录数据、展示趋势、异常提示、医生/AI 解读、复查建议。
2. 饮食闭环：图片识别、用户修正、营养统计、长期趋势、与血糖/体重关联。
3. 用药闭环：药品计划、提醒、服药打卡、漏服记录、医生可见。
4. 报告闭环：上传报告、AI 解析、结构化指标、异常项追踪、历史对比。

现在的问题是入口比闭环多。用户第一次点进去可能觉得丰富，但长期使用时会发现很多地方缺少持续价值。

### 3.2 健康仪表盘需要从“展示数值”升级到“解释状态”

血压、心率、血糖、血氧、体重这些指标是同等级核心指标，UI 上横向平铺是合理的。但产品上还需要补足：

- 每个指标显示采集时间，避免用户误以为是最新状态。
- 显示单位和正常范围。
- 显示趋势，例如较上次升高/下降。
- 异常时给出明确状态，而不是只换颜色。
- 区分数据来源：手动录入、蓝牙设备、报告解析、医生录入。

医疗健康类应用不能只好看，还要让用户理解“现在是否安全、下一步做什么”。

### 3.3 AI 功能需要更强的边界

AI 问诊、饮食分析、报告解读是项目亮点，但要注意：

- AI 建议不能替代医生诊断，需要在关键场景给出边界提示。
- AI 生成结果要允许用户修正，尤其是饮食热量、报告指标、药品信息。
- AI 使用了用户健康上下文，应有授权说明、数据范围说明、删除机制。
- AI 工具调用失败时，前端需要展示可理解的失败状态，而不是静默失败。

## 4. UI/UX 分析

### 4.1 当前最大 UI 问题：没有稳定设计系统

项目里已经有 `AppColors`、`AppTheme`，也有多处页面自己的渐变、阴影、圆角、卡片样式。最近的页面修改又加入了更多独立渐变。这样短期能调好某个页面，但长期会导致页面之间不统一。

建议建立一套稳定规则：

- 主色：用于导航、主要按钮、健康仪表盘重点区域。
- 功能色：饮食、用药、报告、运动、问诊等可以不同，但要同一明度和饱和度等级。
- 状态色：正常、警告、危险、完成、禁用必须固定。
- 卡片规则：圆角、阴影、边框、内边距统一。
- 图标规则：同一模块内图标线宽、尺寸、背景形状统一。

现在用户已经多次指出“颜色太花”“图标不一致”“侧边栏不好看”，本质就是设计 token 没有统一。

### 4.2 侧边栏应该是高频操作中心，不是杂物入口

侧边栏现在承担了个人信息、仪表盘、功能入口、设置等很多内容。建议侧边栏只保留高频且有明确层级的内容：

- 顶部：用户身份和健康状态摘要。
- 中部：核心健康指标，横向一屏看完。
- 功能入口：报告管理、饮食记录、用药管理、健康日历、复查随访、运动计划，两行三列。
- 底部：健康档案、设置。

个人信息区域不一定要卡片包起来，可以用更轻的排版。健康仪表盘可以做成视觉重点，但功能入口不应该抢它的层级。

### 4.3 页面质感不均衡

部分页面已经开始有精致的渐变和卡片，但另一些页面仍像占位页或默认列表页。尤其是：

- 个人信息页：信息架构需要重做，当前不够像一个正式健康档案/账号资料页。
- 设置页：应按账号、安全、通知、设备、隐私、关于分组，而不是堆按钮。
- 饮食分析页：餐次选择、识别结果、热量展示、AI 建议这几个区域需要更统一的卡片层级。
- 医生端/管理员端：如果后续要真实使用，需要比患者端更重视密度、筛选、表格和状态。

### 4.4 可访问性风险

当前大量使用渐变、浅色图标、小号文字和颜色区分状态。健康类应用的用户可能包含中老年人，建议：

- 核心数字字号足够大。
- 不只靠颜色表达异常，还要有文字。
- 保证按钮文字和图标对比度。
- 支持系统字体缩放。
- 重要按钮触控面积至少 44x44。

## 5. 前端工程分析

### 5.1 API 配置不适合真实移动端

> 2026-06-20 更新：已支持 `--dart-define=API_BASE_URL=...`，并保留 `http://localhost:5000` 作为 USB `adb reverse` 本地开发默认值。本节的环境配置问题已处理。

`health_app/lib/core/api_client.dart` 中 `baseUrl` 默认是 `http://localhost:5000`。这在 Android/iOS 真机上通常不可用，也不适合测试/生产环境切换。

建议：

- 使用 `--dart-define=API_BASE_URL=...`。
- 区分 dev/staging/prod。
- 本地 Android 模拟器使用 `10.0.2.2` 或局域网地址。

### 5.2 Token 存储不安全

当前 token 存在本地 SQLite key-value 中，例如 `access_token`、`refresh_token`。这对健康类应用风险偏高。

建议：

- token 改用 `flutter_secure_storage` 或平台安全存储。
- SQLite 中的敏感健康数据考虑加密。
- 退出登录时确认清理 token、用户缓存、会话缓存。

### 5.3 上传接口前后端不一致

前端 `uploadFile` 期望后端返回：

- `url`
- 或 `data.url`

但后端 `file_endpoints.cs` 返回的是文件列表，每项包含：

- `id`
- `name`
- `size`

这会导致 `ChatNotifier.sendImage` 中的 `uploadedUrl` 很可能是 null，最终消息只保存本地路径，跨设备、重启、后端 AI 处理都会出问题。

建议统一契约：

- 后端返回 `{ id, name, size, url, contentType }`。
- 前端保存 `remoteUrl`，本地路径只做临时预览。
- 上传失败要有明确 UI 反馈。

### 5.4 路由可靠性不足

`app_router.dart` 使用字符串 switch 和 `params['id']!`。如果参数缺失会直接崩溃。

建议：

- 至少对参数做空值保护。
- 核心页面可以逐步迁移到 typed route。
- 路由表按模块拆分，避免一个文件不断膨胀。

### 5.5 状态管理有隐患

`chat_provider.dart` 中有一些状态直接修改对象字段的写法，例如修改 `ChatMessage` 的 `content`、`type`、`metadata`、`confirmed`。这容易造成 Riverpod rebuild 不稳定、历史消息状态串联、难以调试。

建议：

- 消息对象尽量不可变。
- 更新消息时使用 copy/update list。
- loading、streaming、error 状态显式建模。

### 5.6 错误处理过于安静

> 2026-06-20 更新：`ApiClient` 已统一识别 `{ code, message }` 业务错误及 HTTP/Dio 网络错误，并转换为可直接展示的 `ApiException`。后端历史 endpoint 的 HTTP 状态码仍需按模块逐步规范，避免一次性破坏现有前端协议。

一些 provider 会 catch 异常后返回 fallback 数据，例如医生列表、运动计划。这个方式能让页面不崩，但会掩盖真实后端错误。

建议：

- demo 数据和真实数据明确分离。
- 网络失败时展示“加载失败/重试”，不要假装成功。
- 对关键接口增加日志和错误上报。

### 5.7 当前 Flutter analyzer 需要清理

本次 `flutter analyze` 返回 162 条问题。较重要的包括：

- `settings_pages.dart` 从 `data_providers.dart` 导入 `apiClientProvider`，但 provider 实际不在该文件中，属于当前明显错误。
- 多处 `use_build_context_synchronously`，异步后使用 `context` 前没有判断 mounted。
- BLE 使用废弃的 `BluetoothDevice.localName`。
- 大量 `avoid_print`，可能泄露请求、token、健康数据。
- 多个未使用方法、变量、页面，说明旧代码堆积严重。

建议先定一个目标：把 analyzer 从 162 条降到 0 或只剩明确允许的少量规则。

## 6. 后端工程与安全分析

### 6.1 短信验证码存在严重安全问题

`auth_endpoints.cs` 中发送短信验证码后会返回 `devCode`，前端登录页还会自动填充。这个行为如果进入真实环境，会直接破坏验证码意义。

另外还存在：

- 管理员手机号 `12345678910`。
- 固定验证码 `000000`。
- 缺少短信发送频率限制。
- 缺少验证码尝试次数限制。
- 验证码在部分失败流程中可能提前被标记已使用。

建议：

- 只有开发环境允许返回验证码。
- 生产环境必须接入真实短信服务。
- 加入 IP/手机号频率限制。
- 管理员登录改为独立安全流程。

### 6.2 SSE 认证方式有严重漏洞

AI SSE 接口允许从 query string 读取 token，而且后端使用 `ReadJwtToken` 读取用户 ID。`ReadJwtToken` 只解析 token，不验证签名、不验证过期、不验证 issuer/audience。

这意味着攻击者可能构造一个看似 JWT 的字符串，放入任意用户 ID claim，后端就可能当成有效用户。

建议：

- 禁止直接信任 query token。
- SSE 鉴权也必须经过 `JwtBearer` 验证。
- 如果浏览器 EventSource 不方便加 header，可以先用标准授权接口换一个短期一次性 stream token，服务端存储并校验。

### 6.3 会话归属校验不足

AI 聊天接口中根据 `conversationId` 查询会话后，需要确认该会话属于当前用户。否则只要知道或猜到 conversationId，就有跨用户写入/读取风险。

建议：

```csharp
var conversation = await db.Conversations
    .FirstOrDefaultAsync(c => c.Id == convId && c.UserId == currentUserId);
```

所有报告、健康档案、饮食、聊天记录等用户资源都应遵循这个模式。

### 6.4 CORS 配置过宽

`Program.cs` 中 CORS 允许任意 origin，同时允许 credentials。这在 Web 客户端场景下风险很大。

建议：

- dev 环境允许 localhost。
- staging/prod 使用固定域名白名单。
- 不要在任意 origin 下允许 credentials。

### 6.5 数据库初始化方式不适合长期维护

后端使用 `EnsureCreatedAsync()`。这适合原型阶段，不适合真实项目迭代。后续字段变更、索引变更、数据迁移都会困难。

建议：

- 建立 EF Core migrations。
- 启动时只在开发环境自动迁移，生产环境由部署流程控制。
- 所有 schema 变更进入版本管理。

### 6.6 文件上传风险

`file_endpoints.cs` 当前只保存上传文件，没有看到严格的大小、类型、内容校验。

建议：

- 限制文件大小。
- 限制 MIME 类型和扩展名。
- 图片重新编码或扫描。
- 不直接信任用户文件名。
- 返回可访问 URL，并控制访问权限。

### 6.7 System.Drawing 跨平台风险

后端 AI 图片压缩使用 `System.Drawing` 相关 API，analyzer 给出了 CA1416 警告。这些 API 在非 Windows 环境不可靠。如果部署到 Linux 容器，可能出问题。

建议换成：

- ImageSharp
- SkiaSharp
- 或云端对象存储/图片处理服务

## 7. AI 与隐私合规分析

项目会把用户健康档案、健康记录、饮食图片、报告等数据送入 AI。健康数据属于高敏感数据，需要明确处理策略。

建议补齐：

- 用户授权：哪些数据会发送给 AI。
- 数据最小化：只传当前任务必要字段。
- 日志脱敏：不要记录完整模型响应、token、报告原文、图片隐私信息。
- 删除机制：用户删除账号后，AI 会话、上传文件、日志也要清理。
- 医疗免责声明：AI 只做健康建议，不替代诊断。
- 审计日志：医生/管理员访问患者数据需要留痕。

当前 `vlm_log_*.txt` 会记录模型响应，可能包含敏感结果，建议尽快调整为脱敏日志或仅开发环境启用。

## 8. 测试与工程流程

### 8.1 后端测试当前不可靠

`dotnet test` 未跑完，因为正在运行的 WebApi 进程锁住了构建输出 DLL。这说明本地开发/测试流程还不稳定。

建议：

- 测试使用独立输出目录或先停止运行中的服务。
- CI 中从干净环境执行。
- 单元测试不要依赖手动启动的 localhost 服务。

### 8.2 测试内容还偏浅

目前测试更像验证部分服务逻辑和开发流程，没有覆盖高风险业务：

- 验证码频控和错误次数。
- JWT 过期、刷新、伪造 token。
- 用户 A 不能访问用户 B 的会话/报告/健康数据。
- 文件上传大小/类型限制。
- AI SSE 中断、重试、失败展示。
- 饮食识别后用户修正和保存。

建议先补安全边界测试，再补 UI golden/screenshot 测试。

## 9. 具体 bug 与风险点清单

| 文件 | 问题 | 建议 |
| --- | --- | --- |
| `health_app/lib/pages/settings/settings_pages.dart` | `apiClientProvider` 导入来源错误，analyzer 已报错 | 从正确 provider 文件导入，或统一 provider 暴露位置 |
| `health_app/lib/core/api_client.dart` | `baseUrl` 写死 localhost | 使用环境配置 |
| `health_app/lib/core/api_client.dart` | 上传返回结构与后端不匹配 | 统一上传响应 |
| `health_app/lib/core/api_client.dart` | `LogInterceptor` 打印请求/响应 body | 生产关闭，敏感字段脱敏 |
| `health_app/lib/core/local_database.dart` | token 存 SQLite | 改 secure storage |
| `health_app/lib/providers/chat_provider.dart` | 图片消息上传 URL 可能为空 | 修复上传契约和失败 UI |
| `health_app/lib/providers/chat_provider.dart` | 静默 catch | 展示错误状态并记录日志 |
| `health_app/lib/core/app_router.dart` | `params['id']!` 可能崩溃 | 参数校验和 fallback 页面 |
| `backend/src/Health.WebApi/Endpoints/auth_endpoints.cs` | 返回 `devCode` | 仅 dev 环境启用 |
| `backend/src/Health.WebApi/Endpoints/auth_endpoints.cs` | 固定管理员验证码 | 改正式认证流程 |
| `backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs` | query token 未验证签名 | 改标准 JWT 鉴权 |
| `backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs` | conversation 归属校验不足 | 查询时加入 UserId |
| `backend/src/Health.WebApi/Endpoints/file_endpoints.cs` | 文件类型/大小校验不足 | 加白名单和限制 |
| `backend/src/Health.WebApi/Program.cs` | CORS 过宽 | 按环境配置白名单 |
| `backend/src/Health.WebApi/Program.cs` | `EnsureCreatedAsync` | 改 migrations |

## 10. 建议整改路线

### 10.1 0 到 3 天：先堵住安全和明显 bug

1. 修复 SSE token 验证和 conversation 用户归属校验。
2. 删除生产环境 `devCode` 返回和前端自动填充验证码。
3. 修复文件上传返回结构。
4. 修复 `settings_pages.dart` 的错误导入。
5. 关闭生产环境请求/响应 body 日志。
6. 把 Flutter analyzer 里的真正错误先清零。

### 10.2 1 到 2 周：收拢核心体验

1. 建立统一设计 token：颜色、渐变、按钮、卡片、图标。
2. 重做侧边栏、个人信息页、设置页、饮食分析页的信息层级。
3. 健康仪表盘增加趋势、时间、单位、状态解释。
4. 清理 `remaining_pages.dart` 中旧页面和无入口页面。
5. 前端 API baseUrl 改成环境配置。
6. token 改安全存储。

### 10.3 1 个月：提升产品完整度

1. 建立 EF migrations 和 CI。
2. 增加安全测试：验证码、JWT、跨用户访问、上传。
3. 增加 AI 结果修正、置信度、失败重试。
4. 补齐隐私授权、数据删除、访问审计。
5. 医生端做成真正可用的工作台：患者筛选、风险排序、随访提醒。
6. 报告、饮食、用药、运动做长期趋势关联。

## 11. 第二轮深挖补充

这一轮额外对已有 `docs/BUG_REVIEW.md`、后端端点、AI Agent handler、SignalR、Flutter provider 生命周期、饮食/蓝牙/问诊页面做了交叉检查。需要注意：旧 bug 文档中有一些问题已经被修复或情况已经变化，本节以当前代码为准。

### 11.1 已确认仍然存在的高风险问题

| 优先级 | 位置 | 当前问题 | 风险说明 | 修复方向 |
| --- | --- | --- | --- | --- |
| P0 | `backend/src/Health.WebApi/Endpoints/auth_endpoints.cs` | `/api/auth/send-sms` 仍返回 `devCode` | 任何客户端都能拿到验证码，短信验证等同失效 | 只在 Development 返回；生产环境完全移除 |
| P0 | `health_app/lib/pages/auth/login_page.dart` | 前端拿到 `devCode` 后自动填入验证码 | 把后端安全问题固化成产品行为 | 删除自动填充；开发环境可用 debug banner 或日志 |
| P0 | `backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs` | SSE token 走 query，且 fallback 用 `ReadJwtToken` 解析 | query token 会进日志；`ReadJwtToken` 不验证签名 | SSE 改标准鉴权，或先换一次性 stream ticket |
| P0 | `backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs` | 创建/续写 AI 会话时 `FindAsync(conversationId)` 未校验 `UserId` | 可能跨用户写入/读取会话 | `FirstOrDefaultAsync(c => c.Id == id && c.UserId == userId)` |
| P0 | `backend/src/Health.WebApi/Hubs/ConsultationHub.cs` | SignalR Hub 没有鉴权，入组只靠 `consultationId` | 任意连接可加入任意问诊房间 | `MapHub().RequireAuthorization()`，Join 前校验用户或医生权限 |
| P0 | `backend/src/Health.WebApi/Hubs/ConsultationHub.cs` | `SendMessage` 按传入 `senderType` 和 `consultationId` 写库 | 客户端可冒充 Doctor/User，向任意会话发消息 | senderType 从 Claims/角色推导，不信任客户端 |
| P0 | `backend/src/Health.WebApi/Endpoints/consultation_endpoints.cs` | 患者发消息只查会话存在，未校验会话属于当前用户 | 用户 A 可向用户 B 的问诊发 HTTP 消息 | 查询加 `c.UserId == userId` |
| P0 | `backend/src/Health.WebApi/Endpoints/exercise_endpoints.cs` | `/items/{itemId}/checkin` 只 `FindAsync(itemId)` | 用户可打卡/取消他人的运动计划项 | Include Plan 后校验 `Plan.UserId` |
| P0 | `backend/src/Health.Infrastructure/AI/AgentHandlers/exercise_agent_handler.cs` | AI 运动 checkin 只按 itemId 操作 | 通过 AI 工具也可越权打卡 | 查询 item 时联表校验当前 userId |
| P0 | `backend/src/Health.Infrastructure/AI/AgentHandlers/medication_agent_handler.cs` | AI 用药 confirm 直接写 `MedicationLog`，不验证药品归属 | 可给他人药品写入服药记录 | 先查 `Medication.Id == medId && UserId == userId` |
| P1 | `backend/src/Health.WebApi/Endpoints/medication_endpoints.cs` | `/medications/{id}/confirm` 查 existing log 未限制 `UserId`，也未先确认药品归属 | 可能误删/写入不属于当前用户药品的日志 | 先查药品归属，再按 `MedicationId + UserId` 查日志 |
| P1 | `backend/src/Health.WebApi/Endpoints/doctor_endpoints.cs` | 医生端已鉴权，但患者详情/报告/随访操作多处只按 id 查询 | 医生可能访问或修改非自己负责患者的数据 | 所有医生端资源都按医生关联患者过滤 |
| P1 | `backend/src/Health.WebApi/Endpoints/file_endpoints.cs` | 上传缺少大小、类型、内容校验，且返回结构与前端不匹配 | 恶意文件/超大文件风险，前端图片上传链路失败 | 加限制、白名单、URL 返回和访问控制 |
| P1 | `health_app/lib/pages/diet/diet_capture_page.dart` | `_fieldCtrls` 缓存 controller，但页面没有 dispose | 多次进入饮食分析页会泄漏 TextEditingController | 在 State `dispose()` 中遍历释放 |
| P1 | `health_app/lib/providers/consultation_provider.dart` | Hub/轮询 stop 需要手动调用，provider 自身没有自动释放 | 离开页面后可能继续 SignalR 或 5 秒轮询 | Notifier build 中注册 `ref.onDispose(stop)` |
| P1 | `health_app/lib/providers/chat_provider.dart` | SSE `_subscription` 和 timer 没看到 provider dispose 释放 | 聊天流中断/页面销毁后可能残留监听 | 注册 `ref.onDispose`，切换会话时取消旧流 |
| P1 | `backend/src/Health.WebApi/Program.cs` | `MapHub("/hubs/consultation")` 未 RequireAuthorization | 即使 API 鉴权，实时通道仍裸露 | Hub 映射处加鉴权并处理 token 传递 |

### 11.2 旧问题中已经变化或需要修正的判断

这些点在旧 `BUG_REVIEW.md` 里出现过，但当前代码已经不是原始状态，后续不要按旧结论机械修：

- `doctor_endpoints.cs` 不是“零授权”了：当前已有 `.RequireAuthorization()` 和医生角色过滤。真正问题是医生端数据授权粒度不够，部分详情/报告/随访接口没有限制到当前医生负责的患者。
- `open_ai_compatible_client.cs` 的 Vision content 当前已经是 `Content = contentParts`，不是把多模态数组序列化成字符串。旧的 VLM 序列化 bug 看起来已修复。
- `diet_agent_handler.cs` 和 `consultation_agent_handler.cs` 当前没有声明未实现工具，而是主动缩减为档案/记录查询。问题应描述为“AI Agent 能力和首页胶囊/欢迎卡片承诺不一致”，不是“声明工具但未实现”。
- `cleanup_service.cs` 当前已经先删 ConversationMessages 再删 Conversations，旧的 FK 删除顺序问题已修。
- `device_scan_page.dart` 当前 `dispose()` 已取消 scan/read/connection 订阅，不能继续作为确定泄漏 bug。仍建议检查 `OmronBleService` 的全局 provider 生命周期和断线重连边界。
- `widget_test.dart` 中 `primary == primaryLight` 的错误断言已经改掉，目前测试更大的问题是覆盖面太浅。

### 11.3 后端授权边界需要系统性重查

项目里很多接口已经 `.RequireAuthorization()`，但“已登录”不等于“有权操作这个资源”。建议建立统一规则：

| 资源 | 当前风险 | 应该怎么查 |
| --- | --- | --- |
| AI Conversation | 续写时未绑定 `UserId` | `Conversation.Id == id && Conversation.UserId == currentUserId` |
| Consultation HTTP | POST message 未绑定 `UserId` | `Consultation.Id == id && Consultation.UserId == currentUserId` |
| Consultation Hub | 入组/发消息无服务端权限判断 | Join 和 SendMessage 都查用户或医生是否有权进入该 consultation |
| ExercisePlanItem | checkin 只按 itemId | `ExercisePlanItem.Id == itemId && Item.Plan.UserId == currentUserId` |
| Medication confirm | 部分确认接口未先校验药品归属 | `Medication.Id == id && Medication.UserId == currentUserId` |
| Doctor patient detail | 医生按任意 patient id 查详情 | `User.Id == patientId && User.DoctorId == currentDoctorId` |
| Doctor report review | 医生按任意 report id 审阅 | `Report.User.DoctorId == currentDoctorId` |
| Doctor follow-up update/delete | 医生按任意 followUp id 操作 | `FollowUp.User.DoctorId == currentDoctorId` 或 `DoctorName/DoctorId` 绑定 |

建议在后端加一层可复用 helper，例如：

```csharp
static IQueryable<User> ScopePatientsToDoctor(AppDbContext db, Guid doctorId) =>
    db.Users.Where(u => u.Role == "User" && u.DoctorId == doctorId);
```

所有医生端接口都从这个 scope 派生，不要每个 endpoint 手写判断。

### 11.4 API 语义和错误码问题

现在不少接口用 HTTP 200 包业务错误码，比如 401/403/404/400 都包成 `{ code, message }`。这种风格可以保留，但要注意两个问题：

- 对认证授权失败，HTTP 状态码最好仍返回 401/403，方便客户端、网关、日志系统识别。
- 业务错误码需要统一枚举，否则前端只能靠字符串判断。

建议定义统一错误码：

- `0` 成功
- `40001` 参数错误
- `40002` 登录过期
- `40003` 无权限
- `40004` 资源不存在
- `40005` 业务状态冲突
- `50000` 服务端异常

并让 `ExceptionMiddleware` 只处理意外异常，业务错误由 endpoint 明确返回。

### 11.5 数据模型和索引补充

当前 `AppDbContext` 已经配置了不少索引和枚举转换，比旧报告里“完全没有 FK/索引”的描述更好。但仍建议补：

- `RefreshToken(Token)` 唯一或普通索引：刷新 token 查询会频繁发生。
- `RefreshToken(UserId, IsRevoked, ExpiresAt)`：便于清理和会话管理。
- `Report(UserId, CreatedAt)`：报告列表按用户和时间查询。
- `FollowUp(UserId, ScheduledAt)`：随访日历、医生待办会用到。
- `DeviceToken(UserId)`：推送服务上线后需要。
- `Consultation(UserId, CreatedAt)` 和 `Consultation(Status, CreatedAt)`：患者历史和医生待办都会用到。

另外，核心关系建议显式配置删除行为，尤其是 User 删除时关联 Consultation、Report、Conversation、MedicationLog 的级联或手动删除策略。

### 11.6 AI Agent 产品能力不一致

现在首页上有多个 agent/胶囊入口，但后端能力不完全一致：

- 饮食 Agent 当前只保留健康档案查询，真正饮食识别在专门图片接口。
- 问诊 Agent 当前只保留健康记录和档案查询，转医生走专门问诊流程。
- 用药/运动 Agent 有创建、查询、确认能力，但确认工具存在所有权校验问题。
- 通用 Agent 如果聚合多个工具，需要明确“哪些动作会写数据，哪些只是查询”。

建议 UI 文案和后端能力统一：

- 欢迎卡片不要暗示当前 agent 能完成它实际上做不到的写操作。
- 会写入健康数据、药品、运动计划、档案的 AI 动作，必须有确认卡片。
- AI 工具调用结果应返回结构化状态，前端不要只靠自然语言判断成功。

### 11.7 前端状态和生命周期问题

> 2026-06-20 更新：健康最新值、用药列表、用药提醒、当前运动计划已改为 `autoDispose`；AI 确认写入后会同时刷新这些核心数据。报告和饮食使用各自 Notifier/页面加载流程，尚未强行合并成一个全局缓存。

前端现在能跑起来，但长期运行会有状态残留风险：

- `ConsultationChatNotifier` 里 Hub 和轮询 timer 需要自动释放。建议 `build()` 中调用 `ref.onDispose(stop)`。
- `ChatNotifier` 的 SSE 订阅、流式响应 timer 需要在 provider 销毁、切换 agent、重新发送时取消。
- 饮食页 `_fieldCtrls` 需要 `dispose()`，否则每次识别食物后 controller 累积。
- 多个 `FutureProvider` 没有 `autoDispose`，页面级数据会缓存很久。健康最新值、药品提醒、当前运动计划这类数据建议明确刷新策略。
- 很多页面删除后只本地 `_load()`，没有 `ref.invalidate(...)`，跨页面缓存可能不同步。

### 11.8 UI 深层问题：不是再加渐变，而是建立层级

最近 UI 调整集中在侧边栏、欢迎卡片、饮食页、设置页、个人信息页等。颜色已经比最初丰富，但仍需要注意：

- 颜色角色要固定：蓝色用于主行动/健康状态，橙色用于饮食，紫色用于报告，绿色用于记录/恢复，青色用于设备或运动，浅红用于提醒/风险。
- 欢迎卡片和胶囊按钮的图标必须同语义、同线宽、同背景形状。用户已经多次指出“不只是颜色一样，图标内容也要一样”，这说明视觉一致性比单个渐变更重要。
- 健康仪表盘应优先展示数字、单位、状态、更新时间。图标可以弱化，避免抢数字层级。
- 设置页不应只是按钮列表，应分为账号、安全、通知、设备、隐私、关于。
- 个人信息页应像正式档案：基础信息、医疗信息、健康偏好、绑定医生、账号安全分区展示。
- 功能入口两行三列是合理的，但每个入口不要堆摘要，图标 + 名称 + 必要状态即可。

### 11.9 功能缺口再细化

| 模块 | 当前缺口 | 建议 |
| --- | --- | --- |
| 饮食记录 | 已有识别和保存，但历史记录编辑能力弱 | 支持编辑食物、份量、热量、餐次；支持从历史复制 |
| 报告管理 | 上传后异步 AI 分析，但失败/处理中状态不够细 | 增加 pending/analyzing/failed/retry 状态和轮询刷新 |
| 问诊 | 患者端创建即新会话，历史问诊入口弱 | 增加问诊历史、继续问诊、关闭问诊、评价医生 |
| 用药 | 提醒后台只记录日志，未实际推送 | 接入推送，支持漏服/补服/跳过原因 |
| 运动 | 有计划和打卡，但计划解释和详情不足 | 计划详情页、运动禁忌、完成趋势 |
| 健康日历 | 汇总用药/运动/随访，但和打卡状态联动有限 | 日历上直接展示已完成、未完成、逾期 |
| 医生端 | 已有工作台，但患者范围和流程需加强 | 风险患者排序、未读消息、待审报告、随访待办 |
| 管理员端 | 医生管理已有基础 | 增加操作审计、禁用账号、数据统计 |

### 11.10 更细的整改顺序

第一批必须先修安全：

1. 移除生产 `devCode` 和前端自动填充。
2. 修 SSE 认证，不再解析未验证 JWT。
3. AI conversation 按用户归属查询。
4. Consultation Hub 加鉴权、入组校验、senderType 服务端判定。
5. Consultation HTTP 发消息校验当前用户。
6. Exercise/Medication 的普通接口和 AI 工具都补所有权校验。
7. 医生端详情、报告、随访接口限制到当前医生负责患者。

第二批修接口契约和稳定性：

1. 文件上传返回 `{ id, name, size, url, contentType }`。
2. 前端 `uploadFile` 兼容后端 envelope 和 list 返回，失败要提示。
3. 饮食页 controller dispose。
4. Chat/Consultation provider 注册 `ref.onDispose`。
5. 后端补上传大小/类型限制。
6. 关闭生产 body 日志。

第三批做 UI 体系：

1. 把颜色、渐变、圆角、阴影、图标背景抽成设计 token。
2. 侧边栏、个人信息、设置、饮食分析页按同一设计语言重做。
3. 首页欢迎卡片和胶囊入口统一图标语义。
4. 健康仪表盘增加更新时间、单位、状态文字。
5. 对中老年用户做字号、对比度、触控面积检查。

第四批补测试：

1. 后端加越权测试：用户 A 不能操作用户 B 的 conversation/consultation/exercise/medication。
2. 加短信验证码生产环境不返回测试。
3. 加 SignalR Hub 入组权限测试。
4. 加文件上传类型/大小测试。
5. Flutter 加饮食保存、问诊连接释放、上传失败 UI 的 widget/provider 测试。

## 12. 总体建议

这个项目最有价值的方向是“围绕患者长期健康数据做 AI 辅助管理”，不是简单堆功能入口。接下来建议把项目重心从“页面多”转为“核心闭环扎实”：

- 患者每天愿意记录。
- 数据能被看懂。
- 异常能被发现。
- AI 建议能被修正和追踪。
- 医生能看到有价值的摘要。
- 安全和隐私经得起真实使用。

UI 上不要继续单页单独调色，应该先统一设计体系，再逐步替换页面。工程上先修安全和接口契约，再做大面积美化。这样项目会从“原型功能很多”变成“真正像一个可信赖的健康产品”。
