petstore-docs/架构决策-门店开通与员工邀请-2026-08-02.md

88 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 架构决策:门店开通、一次性员工邀请与操作审计
> 日期2026-08-02
> 状态Accepted / Code Complete待生产迁移与微信真机验收
> OwnerBackend Core + Store Miniapp FE + Store Admin FE
> ReviewData Model / Architecture / Access & Privacy = Yes
## 1. 决策背景
旧流程把 `Store.invite_code` 当作永久共享凭证,公开员工注册只凭 8 位码和手填手机号即可创建 staff老板注册也接受未核验手机号和明文密码。Admin/小程序的“直接创建员工”还生成无法安全交付的初始密码。该模型不满足真实试点的身份核验、撤销、有效期、审计和离职失效要求。
本批目标不是扩展组织/权限系统,而是让 35 家独立单店能自行完成开通,并让受邀员工本人安全加入。
## 2. 已冻结决策
### 2.1 门店开通
- 新老板只走 `POST /api/onboarding/register-boss`,服务端用微信 `phoneCode` 换取并核验手机号;不接受手填手机号或明文密码。
- 新门店状态为 `in_progress`;历史门店无法可靠判断是否已完成开通,迁移统一写 `unknown`
- 开通清单四步:门店资料、预约容量、服务项目为必填;邀请员工为选填,支持单人门店。
- 只有 boss 可显式完成;完成回执保存 `completed_at/by` 并写 AuditLog。清单不阻断已有业务接口避免历史门店升级即停摆。
### 2.2 员工邀请
- legacy `invite_code` 仅保留数据库兼容,不返回 Admin/小程序,不再参与查询或注册。
- 老板创建 `StaffInvitation`姓名、本人微信手机号、130 天有效期。
- token 使用 32 字节 `SecureRandom`256-bit原文只在创建响应返回一次数据库仅保存 SHA-256。
- 邀请状态:`pending → accepted`,或 `pending → revoked/expired`;只能首次接受,已接受不能撤销。
- 预览只返回门店名、受邀姓名、脱敏手机号、状态和有效期。
- 接受时必须由微信换得的手机号与邀请绑定手机号精确匹配;成功后才创建 staff、消费邀请并签发 session。
- 已存在 User/openid/unionid 冲突均拒绝。本期 User 仍是单角色模型,不把 customer 静默升级为 staff试点遇到冲突时使用独立工作手机号后续如有真实频次再引入 Account + StoreMembership。
### 2.3 会话与离职
HMAC 签名和过期校验之后,每次受保护请求继续读取活跃 User校验账号未删除且 `role/storeId` 与 token 一致boss/staff 还必须归属仍有效的 Store。删除员工、停用门店或权限范围变化后旧 token 立即 401无需等待 7 天到期。
### 2.4 AuditLog 与 BusinessEvent
- `BusinessEvent` 记录可统计经营事实;本批补齐 `store_registered`
- `AuditLog` 记录门店开通/设置与员工权限操作,只追加、不更新/软删。
- Audit metadata 只允许服务端白名单标量禁止手机号、token/URL、openid/unionid、密码/密钥、IP、地址/坐标、备注/内容和原始请求体。
## 3. API 契约
| 方法 | 路径 | 权限 | 关键语义 |
|------|------|------|----------|
| POST | `/api/onboarding/register-boss` | public + 微信手机号核验 | 原子创建门店/老板,返回 session |
| GET | `/api/admin/onboarding` | boss/staff + 本店 | 计算开通清单 |
| POST | `/api/admin/onboarding/complete` | boss + 本店 | 必填项齐全后显式完成 |
| POST | `/api/admin/staff-invitations` | boss + 本店 | 原始 token 仅本响应一次 |
| GET | `/api/admin/staff-invitations` | boss + 本店 | 脱敏列表,无 token/hash |
| DELETE | `/api/admin/staff-invitations` | boss + 本店 | 撤销 pending 邀请 |
| GET | `/api/staff-invitations/preview` | public | 脱敏预览 |
| POST | `/api/staff-invitations/accept` | public + 微信手机号核验 | 手机号匹配才创建 staff |
`/api/user/register-boss`、`/api/user/register-staff`、`/api/user/create-staff`、`/api/store/register`、`/api/store/invite-code` 保留兼容入口但只返回明确 410 业务码,不产生写入。
## 4. 数据迁移与不变量
迁移:`backend/db/migrations/20260802_create_store_onboarding.sql`,在前五个迁移之后第六个执行。
新增 6 项生产只读不变量,总数由 18 增至 24
1. 门店开通状态枚举合法;
2. completed 必须有完成回执;
3. unknown/in_progress 不得带完成回执;
4. 邀请状态枚举合法;
5. accepted 与接受回执一致;
6. revoked 与撤销回执一致。
## 5. 前端行为
- Admin 设置页顶部显示开通清单,员工页改为邀请中心;创建后必须立即复制,之后只能撤销并重建。
- 小程序老板可直接用 `open-type=share` 转发带受控 path 的邀请卡片。
- 员工从卡片进入可先匿名预览,再点击微信手机号授权接受。
- H5 允许预览,但不会提供绕过微信手机号核验的接受路径。
## 6. 明确不做
- 不引入多门店组织、复杂 RBAC、StoreMembership、审批流或通用 IAM。
- 不接入短信生产登录,不恢复明文密码交付。
- 不调用微信 URL Link/小程序码后台 API试点先使用小程序原生分享卡片Admin 提供复制消息作为补充。
- 不自动执行生产迁移、不改生产数据。
## 7. 回滚
应用回滚只切回前一 backend/Admin/H5/小程序版本;数据库新增列/表/索引保留,不执行 down migration不覆盖已经产生的邀请、审计或员工账号。旧应用会忽略新增结构但 legacy 注册仍应保持关闭,避免重新暴露永久凭证路径。