# 小脉健康项目全面检查报告 检查日期: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 签名和商店账号,因此只检查了当前准备缺口。