5.5 KiB
架构决策:门店开通、一次性员工邀请与操作审计
日期: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:
- 门店开通状态枚举合法;
- completed 必须有完成回执;
- unknown/in_progress 不得带完成回执;
- 邀请状态枚举合法;
- accepted 与接受回执一致;
- revoked 与撤销回执一致。
5. 前端行为
- Admin 设置页顶部显示开通清单,员工页改为邀请中心;创建后必须立即复制,之后只能撤销并重建。
- 小程序老板可直接用
open-type=share转发带受控 path 的邀请卡片。 - 员工从卡片进入可先匿名预览,再点击微信手机号授权接受。
- H5 允许预览,但不会提供绕过微信手机号核验的接受路径。
6. 明确不做
- 不引入多门店组织、复杂 RBAC、StoreMembership、审批流或通用 IAM。
- 不接入短信生产登录,不恢复明文密码交付。
- 不调用微信 URL Link/小程序码后台 API;试点先使用小程序原生分享卡片,Admin 提供复制消息作为补充。
- 不自动执行生产迁移、不改生产数据。
7. 回滚
应用回滚只切回前一 backend/Admin/H5/小程序版本;数据库新增列/表/索引保留,不执行 down migration,不覆盖已经产生的邀请、审计或员工账号。旧应用会忽略新增结构,但 legacy 注册仍应保持关闭,避免重新暴露永久凭证路径。