diff --git a/docs/project_deep_review.md b/docs/project_deep_review.md new file mode 100644 index 0000000..366aa81 --- /dev/null +++ b/docs/project_deep_review.md @@ -0,0 +1,386 @@ +# 健康项目深度分析报告 + +日期: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 配置不适合真实移动端 + +`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 错误处理过于安静 + +一些 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. 总体建议 + +这个项目最有价值的方向是“围绕患者长期健康数据做 AI 辅助管理”,不是简单堆功能入口。接下来建议把项目重心从“页面多”转为“核心闭环扎实”: + +- 患者每天愿意记录。 +- 数据能被看懂。 +- 异常能被发现。 +- AI 建议能被修正和追踪。 +- 医生能看到有价值的摘要。 +- 安全和隐私经得起真实使用。 + +UI 上不要继续单页单独调色,应该先统一设计体系,再逐步替换页面。工程上先修安全和接口契约,再做大面积美化。这样项目会从“原型功能很多”变成“真正像一个可信赖的健康产品”。 + diff --git a/health_app/lib/pages/home/home_page.dart b/health_app/lib/pages/home/home_page.dart index 2dd5e77..968fbd6 100644 --- a/health_app/lib/pages/home/home_page.dart +++ b/health_app/lib/pages/home/home_page.dart @@ -186,7 +186,7 @@ class _HomePageState extends ConsumerState { Widget _buildAgentBar() { return SizedBox( - height: 46, + height: 42, child: ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 14), @@ -296,9 +296,9 @@ class _HomePageState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const SizedBox(height: 10), + const SizedBox(height: 6), _buildAgentBar(), - const SizedBox(height: 8), + const SizedBox(height: 6), if (_pickedImagePath != null) _buildImagePreview(), _buildInputBar(), ], diff --git a/health_app/lib/widgets/health_drawer.dart b/health_app/lib/widgets/health_drawer.dart index 0f11b1d..868b426 100644 --- a/health_app/lib/widgets/health_drawer.dart +++ b/health_app/lib/widgets/health_drawer.dart @@ -415,6 +415,11 @@ class _NavigationSection extends StatelessWidget { return _Panel( title: '功能入口', + backgroundGradient: const LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [Color(0xFFA8EDEA), Color(0xFFFED6E3)], + ), child: GridView.builder( itemCount: items.length, shrinkWrap: true, @@ -466,7 +471,7 @@ class _NavTile extends StatelessWidget { borderRadius: BorderRadius.circular(20), child: Container( decoration: BoxDecoration( - color: Colors.white.withValues(alpha: 0.78), + color: Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.white, width: 1.2), ),