88 lines
5.5 KiB
Markdown
88 lines
5.5 KiB
Markdown
# 架构决策:门店开通、一次性员工邀请与操作审计
|
||
|
||
> 日期:2026-08-02
|
||
> 状态:Accepted / Code Complete,待生产迁移与微信真机验收
|
||
> Owner:Backend Core + Store Miniapp FE + Store Admin FE
|
||
> Review:Data Model / Architecture / Access & Privacy = Yes
|
||
|
||
## 1. 决策背景
|
||
|
||
旧流程把 `Store.invite_code` 当作永久共享凭证,公开员工注册只凭 8 位码和手填手机号即可创建 staff;老板注册也接受未核验手机号和明文密码。Admin/小程序的“直接创建员工”还生成无法安全交付的初始密码。该模型不满足真实试点的身份核验、撤销、有效期、审计和离职失效要求。
|
||
|
||
本批目标不是扩展组织/权限系统,而是让 3~5 家独立单店能自行完成开通,并让受邀员工本人安全加入。
|
||
|
||
## 2. 已冻结决策
|
||
|
||
### 2.1 门店开通
|
||
|
||
- 新老板只走 `POST /api/onboarding/register-boss`,服务端用微信 `phoneCode` 换取并核验手机号;不接受手填手机号或明文密码。
|
||
- 新门店状态为 `in_progress`;历史门店无法可靠判断是否已完成开通,迁移统一写 `unknown`。
|
||
- 开通清单四步:门店资料、预约容量、服务项目为必填;邀请员工为选填,支持单人门店。
|
||
- 只有 boss 可显式完成;完成回执保存 `completed_at/by` 并写 AuditLog。清单不阻断已有业务接口,避免历史门店升级即停摆。
|
||
|
||
### 2.2 员工邀请
|
||
|
||
- legacy `invite_code` 仅保留数据库兼容,不返回 Admin/小程序,不再参与查询或注册。
|
||
- 老板创建 `StaffInvitation`:姓名、本人微信手机号、1~30 天有效期。
|
||
- 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 注册仍应保持关闭,避免重新暴露永久凭证路径。
|