feat(auth,mp): 患者登录流程优化 — 智能合并 + 角色冻结 + 页面冻结

- 智能合并:微信注册时用手机号盲索引匹配已有患者档案,避免重复建
  档(AuthState 添加 PiiCrypto + ensure_patient_record 增加盲索引查询)
- 角色冻结:小程序仅允许患者角色登录,医护角色被拦截
  (auth_service.rs 添加反向拦截 + 登录页移除 credential login 表单)
- 页面冻结:10 个非核心页面替换为 FrozenPage 占位组件(用药/知情同意
  /透析/家属/诊断/事件),移除 profile 导航入口,移除医生端预加载
- 医生端代码保留,仅隐藏入口,后续可零成本恢复

讨论记录:docs/discussions/2026-05-23-account-registration-login-flow.md
This commit is contained in:
iven
2026-05-23 12:27:14 +08:00
parent f7d98a59f0
commit f11dd59382
21 changed files with 328 additions and 1510 deletions

View File

@@ -0,0 +1,171 @@
# 账号注册与登录流程讨论
> 日期: 2026-05-23 | 参与者: iven, Claude
## 背景
系统有 5 个角色admin/doctor/nurse/patient/operator和 2 个终端Web/小程序)。需要明确患者账号的创建路径、权限分配和业务流程边界。
## 现状梳理
### 患者账号创建路径
- **路径 A — 小程序自注册**:微信登录 → 手机号授权 → 自动在 `users` 表建账号 + 分配 patient 角色 + 在 `patient` 表建档(`wechat_service.rs`
- **路径 B — Web 后台建档**:管理员/医护在 Web 端手动或 CSV 批量创建,`patient.user_id = None`
- **路径 C — 自助绑定**:患者小程序注册后通过 `bind-by-phone` 盲索引匹配关联已有档案
### 权限模型
- patient 角色自动分配data_scope=`self`
- 纯 patient 角色被 Web 端拦截auth_service 中 `is_pure_patient && !is_miniprogram → 拒绝`
- 权限码 18 个 `.list` + 15 个 `.manage`,覆盖健康数据、预约、随访、咨询、积分等
### 患者相关业务流程26 个)
- 注册绑定4: 微信登录、账号密码登录、自助绑定、后台建档
- 健康互动6: 数据查看、体征录入、设备同步、AI 分析、AI 对话、用药管理
- 医疗服务4: 在线预约、在线咨询、随访任务、关怀计划
- 内容积分5: 文章阅读、轮播图、签到、积分查询、积分兑换
- 隐私授权3: 知情同意、家属代理、资料编辑
- 消息通知2: 消息通知、告警接收
- 透析专属2: 透析记录、透析排班
## 讨论要点与决策
### 要点 1患者注册路径的合并策略
**问题:** 同一人可能先被管理员建档,后来自注册产生两条记录。
**决策:需要智能合并策略。** 小程序注册时应检测已有档案并主动引导关联,避免重复。
### 要点 2混合角色支持
**问题:** 一个用户是否可以同时拥有 patient + nurse 等多角色?
**决策:不支持混合角色。** 保持角色单一、清晰,一个人只属于一个角色。如果一个医护也是患者,需要独立的 patient 账号。
### 要点 3小程序多角色支持
**问题:** 小程序是否需要支持 doctor/nurse/admin/operator 登录?
**决策:当前阶段小程序仅支持患者端。** 其他角色doctor/nurse/admin/operator/health_manager在小程序端冻结后续按需开放。现有 credential login 的医疗角色入口需要屏蔽或隐藏。
### 要点 4功能复杂度控制
**问题:** 患者登录后的功能是否一步到位?
**决策:先出一版稳定可用的给甲方测试。** 聚焦核心流程(登录、健康数据查看、预约、消息),不追求功能全覆盖,优先保证稳定性和基本体验。
## T1智能合并策略已决议
### 现状缺口
`wechat_service.rs``ensure_patient_record` 只按 `user_id` 检查,不按手机号查重。管理员先建档 → 患者后自注册 → 产生两条 patient 记录。
### 决策
| 项 | 决策 |
|----|------|
| 匹配字段 | 使用现有 `emergency_contact_phone` 盲索引做近似匹配 |
| 多匹配处理 | 一个手机号只允许关联一条 patient 记录(严格去重) |
| 冲突处理 | 以管理员建档数据为准,信息不一致由护士在 Web 端修改 |
### 实现思路
```
微信绑定手机号 → 解密 phone
→ 计算 HMAC(phone) → 查 blind_index(emergency_contact_phone)
→ 找到未绑定的 patient → 关联 user_id不新建
→ 未找到 → 创建新 patient 记录
```
### 影响范围
- `crates/erp-auth/src/service/wechat_service.rs``ensure_patient_record` 增加盲索引查询
- `crates/erp-health/src/entity/blind_index.rs` — 可能需要从 erp-auth 跨 crate 查询(或用 raw SQL
## T2医生端独立分包处理已决议
### 现状
| 分包 | 页面 | 代码量 | 类型 |
|------|------|--------|------|
| `pkg-doctor-core` | 8 页 | 104 KB | independent 分包 |
| `pkg-doctor-clinical` | 10 页 | 124 KB | independent 分包 |
| `DoctorTabBar` 组件 | — | 8 KB | 条件渲染 |
| 角色分流逻辑 | — | ~18 个文件引用 | 散布各处 |
### 性能影响分析
| 维度 | 影响 | 程度 |
|------|------|------|
| **包体积(提交)** | 注册在 app.config.ts 中的页面增加总包体积 ~236 KB | 微小(微信主包限制 2MB当前远未触及 |
| **运行时加载** | `independent: true` 分包只在用户主动跳转时下载,不跳转 = 不加载 | **零影响** |
| **预加载** | 首页 preloadRule 包含 `pkg-doctor-core`,会触发提前下载 | **需移除预加载项** |
| **首屏渲染** | auth.ts 中 `isMedicalStaff()` 是纯数组比较DoctorTabBar 条件渲染 | **零影响** |
| **内存** | 未加载的分包代码不占用运行内存 | **零影响** |
### 决策:保留代码,隐藏入口
保留全部医生端代码,仅做以下调整:
1. **后端拦截**`auth_service.rs` 中拒绝非 patient 角色登录小程序(仅保留 `client_type=miniprogram + patient` 组合)
2. **前端隐藏入口** — 小程序登录页移除账号密码登录入口(仅保留微信一键登录)
3. **移除预加载**`app.config.ts` 的 preloadRule 中移除 `pkg-doctor-core`
4. **保留但不触发** — 角色分流逻辑isMedicalStaff/DoctorTabBar保留在代码中但不会被触发
### 后续恢复路径
需要重新开放医生端时:
1. 恢复 credential login 入口
2. 后端放开角色限制
3. preloadRule 加回预加载
4. 零代码修改,纯配置变更
## T3甲方测试版功能裁剪已决议
### 冻结页面清单
| 模块 | 页面 | 页面数 | 冻结方式 |
|------|------|--------|----------|
| **医生端**`pkg-doctor-core` | 工作台/患者/咨询/随访/行动收件箱 | 8 | 隐藏入口 + 移除预加载 |
| **医生端**`pkg-doctor-clinical` | 透析/处方/报告/告警 | 10 | 隐藏入口 |
| **透析相关** | pkg-profile/dialysis-records + dialysis-prescriptions (各 list+detail) | 4 | 移除导航入口 |
| **家属管理** | pkg-profile/family + family-add | 2 | 移除导航入口 |
| **用药管理** | pkg-profile/medication | 1 | 移除导航入口 |
| **知情同意** | pkg-profile/consents | 1 | 移除导航入口 |
| **诊断记录** | pkg-profile/diagnoses | 1 | 移除导航入口 |
| **事件日志** | pkg-profile/events | 1 | 移除导航入口 |
| **小计冻结** | | **28 页** | |
### 保留页面清单(甲方测试版)
| 模块 | 页面 | 页面数 | 说明 |
|------|------|--------|------|
| **主包** | login / index / health / messages / consultation / create / mall / profile / legal×2 | 10 | TabBar 全部保留 |
| **pkg-health** | trend / input / daily-monitoring / alerts / device-sync | 5 | 健康核心 |
| **pkg-mall** | exchange / orders / detail / product | 4 | 商城 |
| **pkg-profile** | reports / reports-detail / followups / followups-detail / settings / health-records / elder-mode / notifications | 8 | 档案+设置 |
| **ai-report** | list / detail | 2 | AI 报告 |
| **article** | index / detail | 2 | 文章 |
| **appointment** | index / create / detail | 3 | 预约 |
| **pkg-consultation** | detail | 1 | 咨询详情 |
| **小计保留** | | **35 页** | |
### 裁剪实施方式
冻结页面的代码保留在仓库中,通过以下方式隐藏:
1. **移除导航入口** — profile 页面中移除冻结模块的菜单项
2. **保留路由注册** — app.config.ts 中的页面注册保留(避免深度链接崩溃)
3. **直接访问容错** — 被冻结页面若被直接访问,显示"功能即将上线"占位
## 全部决策汇总
| # | 决策 | 影响范围 | 优先级 |
|---|------|----------|--------|
| D1 | 智能合并 — 用 emergency_contact_phone 盲索引匹配 | wechat_service.rs | 高 |
| D2 | 不支持混合角色 | auth 模块 | 已实现 |
| D3 | 小程序仅患者端,冻结其他角色 | auth_service.rs + 登录页 | 高 |
| D4 | 功能精简,先稳定再迭代 | 小程序全局 | — |
| D5 | 医生端保留代码隐藏入口,移除预加载 | app.config.ts + auth_service.rs | 中 |
| D6 | 冻结 28 页透析×4 + 家属×2 + 用药 + 知情同意 + 诊断 + 事件 + 医生端×18 | profile 页菜单 + 页面容错 | 中 |