Files
AI-Health/docs/project_deep_review.md
MingNian 13714d9ed8 feat: 应用内通知系统 + 结构化手术史/用药等相关改动
- 新增用户通知 outbox 流水线(EfUserNotificationPipeline)与后台投递 worker
- 通知中心页面及前端通知服务接入
- 健康指标异常、用药/运动提醒等事件统一产出站内通知
- 健康档案结构化手术史、用药提醒扫描、医生/用户端点等配套调整
- AppDbContext 注册通知相关实体
2026-06-21 21:06:29 +08:00

571 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# 健康项目深度分析报告
日期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 | 文件上传前后端契约不一致 | 图片上传后前端拿不到 URLAI 图片消息可能只保存本地路径 | 后端返回可访问 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` 相关 APIanalyzer 给出了 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 上不要继续单页单独调色,应该先统一设计体系,再逐步替换页面。工程上先修安全和接口契约,再做大面积美化。这样项目会从“原型功能很多”变成“真正像一个可信赖的健康产品”。