feat: 二级页面色彩刷新 + 用药/通知/设备重构 + 后端健康档案/通知管线增强 + 大量测试

## 后端
- 健康档案: 新增手术状态字段 + EF 迁移; HealthArchiveService 新增查询方法
- 健康记录: HealthRecordService 新增批量/统计方法; 契约扩展
- 用药: 新增 MedicationScheduleStatus 枚举; MedicationService 排班逻辑调整
- 通知: EfUserNotificationPipeline 重构; 新增 EfReminderCatchUpService; 通知管线支持更多场景
- 用户: UserService 账号删除逻辑; 新增 local_account_file_cleanup; EfUserRepository 扩展
- AI: medication_agent_handler 微调; prompt_manager 优化; AiConversationService 上下文处理
- Endpoint: doctor/medication/exercise/health/notification/user 等多接口调整
- BackgroundService: health_record_reminder_service 重构, 提醒补漏逻辑
- 测试: 新增 account_deletion/doctor_endpoint/medication_schedule/medication_update/prompt_manager 测试

## 前端
- UI 系统: app_theme 大幅重构; app_colors/app_design_tokens/app_module_visuals 调整; 二级页面色彩刷新
- 主页: home_page 背景渐变 + 消息列表提取 _HomeMessages + 通知检查逻辑; chat_messages_view 全面重构
- 用药: medication_list/edit/checkin 三页重构, 新增 medication_ui_logic 抽取
- 通知: notification_prefs_page 重构, 新增 notification_prefs_logic; notification_center 优化
- 设备: device_management 重构, 新增 device_sync_ui_logic; device_scan 优化
- 趋势图: trend_page 大幅重构
- 登录: login_page 重构
- 个人资料: 新增 profile_edit_page; profile_page 优化
- 运动: 新增 exercise/ 目录 + care_plan_ui_logic
- 其他: remaining_pages/report_pages/health_drawer/admin/doctor 等多页面调整
- 组件: common_widgets/app_empty_state/app_error_state/app_future_view/app_toast/ai_content 优化
- Provider: chat_provider/consultation_provider/data_providers/auth_provider 调整
- AndroidManifest: 移除多余权限
- 测试: 新增 ai_content/care_plan/home_message/login_flow/medication_checkin/medication_ui/notification_prefs/profile_device/secondary_page/swipe_delete 等大量测试

## 文档
- 新增 ui-design-system.md 设计系统文档
- 新增 secondary-page-color-refresh 计划 + specs 目录
This commit is contained in:
MingNian
2026-07-15 23:22:52 +08:00
parent e654c1e0cc
commit fade61ac21
138 changed files with 12636 additions and 5013 deletions

View File

@@ -0,0 +1,261 @@
# 小脉健康项目全面检查报告
检查日期2026-07-14
检查范围Flutter Android 真机主要用户路径、Flutter 静态检查与测试、.NET 后端测试、前后端接口对应关系、用户/医生/管理员业务逻辑、权限安全、合规页面与 Android/iOS 发布准备。
本轮只检查,没有修改业务代码,也没有使用 Docker。
## 一、总体结论
项目主体功能已经比较完整,普通用户主路径能进入,当前 Flutter 静态检查通过Flutter 47 项测试和后端 44 项测试全部通过。但测试通过不等于已经稳定:本次仍确认了多项自动测试没有覆盖的真实断点。
当前不适合进入正式环境或商店审核。最优先的不是继续统一小间距,而是先处理问诊鉴权、管理员验证码、上传文件访问、蓝牙上传、账号注销清理和正式包权限。这些问题会影响真实数据、安全或用户是否能完成任务。
## 二、必须先修的问题
### 1. AI 流式聊天的 token 只被“读出来”,没有验证真伪
- 用户表现:正常用户不一定马上发现,但攻击者可以自己伪造一个 token冒充任意用户进入 AI 对话、读取该用户上下文,并可能触发健康数据写入。
- 原因SSE 接口没有强制登录;备用 token 处理只解析 JWT 内容,没有校验签名、过期时间、签发方和接收方。
- 位置:`backend/src/Health.WebApi/Endpoints/ai_chat_endpoints.cs``GetUserIdFromToken`
- 优先级:最高。
### 2. 医生问诊实时通道没有登录和问诊归属校验
- 用户表现:知道问诊编号的人可以加入房间,甚至把自己声明成“用户”或“医生”发送消息;消息还会真实写入数据库。
- 原因SignalR Hub 没有鉴权,加入房间和发送消息时也没有核对当前账号是否属于该患者或医生。
- 位置:`backend/src/Health.WebApi/Hubs/ConsultationHub.cs`
- 优先级:最高。
### 3. 管理员存在固定手机号和固定验证码
- 用户表现:正式环境若沿用当前代码,知道固定号码的人可用 `000000` 登录管理员端。
- 原因:管理员手机号和验证码直接写在认证服务中,而且不只在开发环境生效。
- 位置:`backend/src/Health.Infrastructure/Auth/AuthService.cs`
- 优先级:最高。
### 4. 上传的报告、聊天图片和 PDF 可被未登录访问
- 用户表现:文件链接一旦泄露,不登录也能打开健康报告或聊天附件。
- 原因:整个 `/uploads` 目录作为公开静态目录提供,没有按用户、医生关系或临时授权校验。
- 位置:`backend/src/Health.WebApi/Program.cs` 的上传目录静态映射。
- 优先级:最高。
### 5. 蓝牙血压计能读到数据,但自动上传会失败
- 用户表现:设备连接成功、血压已经读取,最后却提示上传失败;血压和心率不会真正保存到后端。
- 原因:前端提交到 `/api/health-records/batch`,当前后端没有这个接口。
- 位置:`health_app/lib/pages/device/device_management_page.dart``backend/src/Health.WebApi/Endpoints/health_endpoints.cs`
- 优先级:高。
### 6. 每次打开医生问诊页都会创建一条新问诊并消耗次数
- 用户表现:只是进入页面看一下、没有发送任何内容,也会新增问诊;反复进出会快速消耗每月次数。显示剩余 0 次时仍可能继续创建,甚至出现负数。
- 原因:前端初始化先查次数,然后无条件创建;后端创建接口没有真正执行三次上限,也没有验证医生仍然有效。
- 位置:`health_app/lib/providers/consultation_provider.dart``backend/src/Health.WebApi/Endpoints/consultation_endpoints.cs`
- 优先级:高。
### 7. 问诊消息发送失败时仍显示为已发送
- 用户表现:网络断开或服务器失败后,消息仍留在聊天里,没有失败标记和重试入口,用户会误以为医生已经收到。
- 原因:发送异常被静默忽略,本地消息没有回滚或转成失败状态。
- 位置:`health_app/lib/providers/consultation_provider.dart``sendMessage`
- 优先级:高,且不符合“不能假成功”的项目要求。
### 8. Android 正式包没有网络权限
- 用户表现Debug 真机可联网,但以后构建 release 包后所有登录、数据、AI 和报告请求都无法联网。
- 原因:`INTERNET` 只在 debug/profile Manifest 中main Manifest 没有release 不会合并 debug 配置。
- 位置:`health_app/android/app/src/main/AndroidManifest.xml`
- 优先级:正式包前必须修。
### 9. 账号注销只删数据库记录,不会清理服务器上传文件
- 用户表现App 显示账号已删除但报告原图、聊天图片、PDF 等仍可能留在服务器目录;公开链接仍可能继续访问。
- 原因:账号注销批量删除数据库数据时没有收集并删除物理文件。单独删除报告有删文件,注销账号没有走同一流程。
- 位置:`backend/src/Health.Infrastructure/Users/EfUserRepository.cs`
- 优先级:高,涉及合规承诺和真实删除。
### 10. iOS 相机和相册权限说明缺失
- 用户表现:以后在 iPhone 上拍饮食、拍报告或选相册时,系统可能直接终止 App而不是正常弹出权限框。
- 原因Info.plist 有蓝牙说明,但没有 `NSCameraUsageDescription``NSPhotoLibraryUsageDescription`
- 位置:`health_app/ios/Runner/Info.plist`
- 优先级iOS 调试前必须修。
## 三、重要功能和稳定性问题
### 11. 多个请求同时遇到登录过期时,可能刚刷新成功又被强制退出
多个 401 会同时拿同一个刷新 token。第一个请求刷新并保存新 token 后,第二个请求可能因旧 token 已失效而失败,随后把刚保存的新 token 一并清掉。用户会偶发跳回登录页。
位置:`health_app/lib/core/api_client.dart` 的 401 刷新逻辑。
### 12. 启动时只要服务器暂时连不上,就把用户当成未登录
刷新 token 的请求遇到断网或本地后端未启动时,认证状态直接变为未登录,但本地 token 又没有清除。用户会看到登录页,网络恢复后也不会自动重试,只能重启或重新操作。
位置:`health_app/lib/providers/auth_provider.dart``_checkAuth`
### 13. 问诊轮询用随机 GUID 判断“之后的消息”,可能漏消息
SignalR 失败后会回退轮询。后端把随机消息 ID 与上一条随机 ID 做大小比较,这不等于消息时间顺序,可能漏掉医生新回复。正确游标应基于服务器时间或稳定递增字段。
位置:`backend/src/Health.WebApi/Endpoints/consultation_endpoints.cs`
### 14. 问诊 AI 开场白只存在前端,没有保存
用户看到的“AI 分身问候”不是数据库消息,医生端看不到;重新进入又会新建问诊并生成另一条本地问候,历史不连续。
位置:`health_app/lib/providers/consultation_provider.dart`
### 15. 短信验证码没有频率、次数和防刷限制
发送接口可反复调用,验证码校验也没有失败次数限制。正式短信接入后会产生短信轰炸、费用和验证码暴力尝试风险。
位置:认证端点和 `AuthService`
### 16. 管理员新增、停用、修改、删除医生时,几套医生数据可能不同步
- 手机号已属于普通用户时,仍可创建医生实体,但不会把已有用户升级并绑定医生档案。
- 修改医生手机号/姓名时,没有同步医生登录账号与医生档案。
- 停用医生只改一处状态,医生档案的状态可能仍是启用。
- 删除医生后,医生登录用户仍可能保留 Doctor 角色,但档案已失去关联,登录后只能看到“档案未关联”。
- 医生手机号没有唯一约束,可能出现重复医生。
位置:`backend/src/Health.Infrastructure/Admin/AdminService.cs`
### 17. 附件本地路径没有限制在 uploads 目录内
附件 URL 中的相对路径直接拼到服务器路径,没有做规范化后再确认最终路径仍位于 uploads 内,也没有核对文件是否属于当前用户。这会带来跨用户读取或路径越界风险。
位置:`backend/src/Health.Infrastructure/AI/AttachmentContextBuilder.cs`
### 18. 通知点击先联网标记已读,失败时目的页面也打不开
用户点一条未读通知时,先请求后端标记已读;如果这一步失败,后续报告/用药/健康页面不会打开,也没有明确提示。
位置:`health_app/lib/pages/notifications/notification_center_page.dart`
### 19. 用户端与医生端部分“今天/本月”按 UTC 计算
中国时区的凌晨时段可能被算到上一天或上个月,影响问诊次数、今日随访和提醒归类。正式环境应统一明确业务时区。
位置:问诊和医生工作台中使用 `DateTime.UtcNow.Date` 或 UTC 月份的逻辑。
## 四、UI 与交互问题
### 20. 用药和运动列表默认露出整片红色删除背景
真机截图中,用药行和运动行在没有滑动时已经整行变红;运动行还出现“打卡”和“删除”重叠。用户会误以为数据异常或正处于危险删除状态。
原因是通用滑动删除组件始终铺红色底层,而这些列表行本身透明。测试只验证滑动动作,没有验证未滑动时的最终视觉。
证据:`05-medications.png``09-exercise.png`
位置:`health_app/lib/widgets/common_widgets.dart`
### 21. 首页多个纯图标按钮缺少无障碍名称
真机可访问性结构中,菜单、附件、输入框和发送按钮等多个可点击区域没有名称。普通用户能凭图标猜,读屏用户无法知道用途。
证据:`01-current.xml`
### 22. 删除动作风格不统一
报告、用药、运动使用滑动删除,通知使用整行侧滑,蓝牙设备直接显示红色垃圾桶,设置页则用大按钮。交互成本和误触风险不同。建议统一为“列表行滑动后显示删除 + 二次确认用于高价值数据”。
### 23. 设置页的红色占比过重
“删除账号”和“退出登录”两个大面积危险操作占据页面最强视觉层级,反而压过消息、隐私和服务协议。删除账号保持红色是合理的,但退出登录不必使用整块高饱和红色。
证据:`12-settings.png`
### 24. 侧边栏健康仪表盘过于抢眼
大面积蓝紫粉渐变和四个小框成为最强区域,与项目“克制、专业、模块色只做识别”的偏好有偏差;指标小字和透明边框的对比度也偏弱。
证据:`02-drawer.png`
### 25. 空状态表达不统一
复查随访使用居中图标和说明;饮食使用一条白色容器;其他页面可能只留空白。建议统一空状态的标题、原因和下一步操作,但不需要每页再套一张大卡片。
证据:`06-diet-records.png``08-followups.png`
### 26. 部分图标按钮只有约 40dp
例如蓝牙页顶部新增按钮使用固定 40×40低于常见的 48dp 触控建议;对手部不便或视力较弱的健康类用户不够友好。
### 27. 大字体、横屏、小屏和读屏尚无验证证据
许多页面使用固定高度、固定行布局和多个并排信息块。当前只在一台 1440×3200 Android 真机、默认字体比例下检查,不能据此确认大字体和 iOS 横屏不会溢出。
## 五、合规与发布准备问题
### 28. 合规文档仍有主体和联系方式占位符
隐私政策、服务协议、个人信息清单和 SDK 清单里仍有 `xxx`、待定短信服务商等内容,不能用于正式审核。
### 29. App 内文档与 H5 文档存在多套日期和内容
App 内隐私/协议日期为 6 月 29 日,个人信息与 SDK 清单为 7 月 9 日H5 清单日期为 7 月 7 日。以后修改一处很容易漏另一处。建议确定单一内容源或至少增加一致性测试。
### 30. “关于我们”写了并不存在的意见反馈入口
文档提示“设置 → 意见反馈”,实际设置页没有该入口;版本还写成 `Build 20260101`,与当前 `1.0.0+1` 不一致。
### 31. 用户同意协议没有服务端记录
当前“同意”只是登录页内存中的一个布尔值,未保存同意时间、协议版本或政策更新后的重新同意记录。服务端接口也不要求同意状态,无法证明用户同意了哪一版条款。
### 32. Android 权限声明偏多且缺少版本限制
当前声明了蓝牙广播权限,但 App 主要是扫描/连接;旧版存储和蓝牙权限也没有按 Android 版本加 `maxSdkVersion`。正式个人信息清单应与实际最小权限保持一致。
### 33. 本地 token 使用普通 SQLite 明文保存
符合项目“不用 shared_preferences”的约定但 access token 和 refresh token 没有系统级加密保护;同时 Android 也未显式关闭备份。正式版本需要决定是否改用系统安全存储或加密数据库,并限制备份。
### 34. iOS 名称和方向仍是模板状态
Info.plist 的显示名称还是 `Health App`,并允许 iPhone 横屏当前页面主要按竖屏设计尚未验证横屏。Bundle ID、签名和证书则仍按项目计划待配置。
### 35. CORS 当前允许任意来源并携带凭据
本地调试方便,但正式环境应只允许确定的 H5/管理端域名,不能继续接受任意 Origin。
## 六、已确认正常或已有保护的部分
- 当前 Flutter `analyze` 无错误。
- Flutter 47 项测试全部通过。
- 后端 44 项测试全部通过。
- 报告、用药、饮食、运动、健康数据等主要删除接口都按当前用户筛选,不是只删前端显示。
- 单独删除报告时会同时删除报告原文件。
- AI 历史对话恢复后仍保留原 conversationId可以继续发送新消息历史确认卡只读是合理保护。
- 管理员和医生普通 HTTP 端点已有角色过滤,问题主要集中在固定管理员验证码、医生数据同步和未鉴权 SignalR Hub。
- 设置、通知、报告列表已经较接近“一行一行、文字区分隔线”的目标风格。
- 隐私政策、服务协议、个人信息清单、SDK 清单和关于我们均已有 App 内入口与 H5 文件。
## 七、真机流程健康度
1. 首页与 AI 对话可进入视觉基本完整但纯图标无障碍名称不足AI SSE 鉴权有严重后端风险。
2. 侧边栏:可打开;功能入口齐全,仪表盘色彩和层级偏重。
3. 健康档案:可加载和编辑;固定保存区需要继续验证大字体遮挡。
4. 报告管理:列表正常;文件公开访问是严重隐私风险。
5. 用药管理:数据能加载;列表红底显示错误明显。
6. 饮食记录:日期、空状态和趋势能显示;空状态样式与其他页不统一。
7. 健康日历:日期和计划能显示;业务时区需要后端统一。
8. 复查随访:空状态清楚;本次账号没有数据,未实测详情。
9. 运动计划:数据能加载;红底和操作文字重叠明显。
10. 蓝牙设备:绑定设备能显示;自动读取后的批量上传链路断开。
11. 通知中心:历史和未读数能显示;未读通知在网络失败时可能打不开目的页。
12. 设置与合规入口:入口齐全;危险操作权重过高,文档仍有占位和不一致。
13. 个人信息:真机运行版本可进入;检查期间源代码刚加入新的基本资料编辑入口,当前运行包尚未热更新到这一版。
14. 医生端:已完成代码与接口核对;没有医生测试账号,本次未做完整真机流程。
15. 管理员端:已完成代码与接口核对;没有实际执行新增/停用/删除,避免改变真实数据。
16. iOS仅做配置与代码检查没有证书、Mac 构建和 iPhone 实测,不能确认界面与权限流程。
## 八、证据限制
- 真机截图来自本次检查时设备上正在运行的调试包。检查过程中项目源代码仍在被更新,后加入的个人资料编辑和设备错误文案没有重新构建到该运行包,因此相关结论以当前代码为准、旧界面截图只用于对应未变化页面。
- 没有执行真实删除、账号注销、医生管理、短信发送或蓝牙测量,避免改变现有数据;这些部分通过前后端代码和接口对应关系确认。
- 没有正式域名、HTTPS、短信服务商、对象存储、iOS 签名和商店账号,因此只检查了当前准备缺口。