# 小脉健康项目全面检查报告

检查日期：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 签名和商店账号，因此只检查了当前准备缺口。
