2
This commit is contained in:
331
doc/vip_development.md
Normal file
331
doc/vip_development.md
Normal file
@@ -0,0 +1,331 @@
|
||||
### VIP 功能开发文档
|
||||
|
||||
#### 1. 范围与目标
|
||||
- 目标:为“配件查询”应用提供面向店铺(租户)的会员体系(VIP),支持会员开通、审核、生效与到期管理,并为后续增值能力(如数据永久存储、高级报表、优先审核等)提供统一的权限判定入口。
|
||||
- 范围:后端服务与管理端优先落地;移动端展示会员状态与开通入口,后续再接入支付与自动续期。
|
||||
|
||||
#### 2. 现状与差距
|
||||
- 前端 App:`frontend/pages/my/vip.vue` 当前为静态演示页,读取 `VIP_PRICE_PER_MONTH` 展示,未接入后端状态与支付。
|
||||
- 管理端:已存在页面
|
||||
- `admin/src/views/vip/VipList.vue`:查询、编辑、启停。
|
||||
- `admin/src/views/vip/VipReview.vue`:列出待启用(审核)并提供通过/驳回。
|
||||
- 后端:`AdminVipController` 提供基础接口:列表、创建、更新、审核通过/驳回(部分实现)。依赖表 `vip_users`。
|
||||
- OpenAPI:`/doc/openapi.yaml` 已定义管理端 VIP 接口,标注为「❌ Partially Implemented」。
|
||||
- 数据库:本地 `backend/db/db.sql` 尚未创建 `vip_users` 表;而远程数据库文档 `/doc/database_documentation.md` 已包含该表结构。这是“脚本未对齐线上库”的差距,需要弥合(见实施计划)。
|
||||
|
||||
#### 3. 业务与权限
|
||||
- 归属维度:VIP 为“用户级”。每个用户仅归属一个店铺(无店员概念)。
|
||||
- 状态定义:
|
||||
- `isVip`:是否为会员标记(1/0)。
|
||||
- `status`:启用状态(1=生效,0=未启用/停用)。
|
||||
- `expireAt`:到期时间(为空代表永久或未设置)。
|
||||
|
||||
#### 4. 数据模型设计(多方案)
|
||||
- 方案A:单表 `vip_users`
|
||||
- 字段:`id, shop_id, user_id, is_vip, status, expire_at, remark, reviewer_id, reviewed_at, created_at, updated_at`
|
||||
- 优点:简单直观,满足当前开通/续期/停用/审核的核心需求。
|
||||
- 局限:后续若需要等级(gold/platinum)、付费订单、优惠券、权益包等,需要再扩展新表。
|
||||
- 方案B:三表拆分(推荐用于中长期)
|
||||
- `vip_users`(会员资格)、`vip_levels`(等级/权益配置)、`vip_orders`(开通/续费订单记录)。
|
||||
- 优点:扩展性好,可平滑引入支付渠道、对账、活动定价。
|
||||
- 初期实施成本更高。可采用“增量演进”:先落地方案A,同步预留 `vip_orders` 的接口草案与库表蓝图,待支付接入时启用。
|
||||
|
||||
当前建议:首期采用方案A,满足“开通-审核-生效-到期”的MVP;同时在配置中预留价格与时长,便于后续衔接 `vip_orders`。
|
||||
|
||||
#### 5. 配置与价格策略(严禁硬编码)
|
||||
- 前端价格:已通过 `frontend/common/config.js` 的 `VIP_PRICE_PER_MONTH` 从环境变量/本地存储读取,默认 15 元/月。
|
||||
- 后端价格与时长建议:通过 `system_parameters` 表维护,键建议:
|
||||
- `vip.price`(单位:元/月,number)
|
||||
- `vip.durationDays`(开通/续费时长,默认 30)
|
||||
- 可选:`vip.trialDays`(试用天数,默认 0)
|
||||
- 后端读取优先级:系统参数 > 环境变量(如 `VIP_PRICE`,`VIP_DURATION_DAYS`)> 安全默认值;并通过管理端配置面板维护参数(后续迭代)。
|
||||
|
||||
管理员端价格设置(新增):
|
||||
- 平台统一价,存储于表 `vip_price`(仅一条记录)。
|
||||
- 在管理端“VIP管理”新增“价格设置”入口:读取/修改 `vip_price.price`。
|
||||
- 修改保存后立即生效;前端价格展示以后端返回为准(避免前后端不一致)。
|
||||
|
||||
#### 6. 接口契约(与 openapi.yaml 对齐)
|
||||
- 管理端:
|
||||
- GET `/api/admin/vips`(列表)
|
||||
- POST `/api/admin/vips`(新增)
|
||||
- PUT `/api/admin/vips/{id}`(更新 isVip/status/expireAt/remark)
|
||||
- 审核流程已取消(approve/reject 已移除);统一由用户端一键开通或管理员直接设置。
|
||||
- App(建议新增,未在 openapi.yaml 中登记,待一方开工后再登记):
|
||||
- GET `/api/vip/status`:返回当前用户 VIP 状态与到期时间。
|
||||
- POST `/api/vip/pay`:一键支付并立即开通(临时方案:点击即视为支付成功,直接写入/更新 `vip_users`,置 `status=1,isVip=1,expireAt=now+durationDays`;后续接入真实支付将替换该接口实现)。
|
||||
- 说明:上述 App 接口仅为“建议方案”,在前端或后端开始实现后再将其写入 `/doc/openapi.yaml` 并标注实现状态,避免“仅设计未实现”的文档污染。
|
||||
|
||||
示例返回(状态查询):
|
||||
```json
|
||||
{
|
||||
"isVip": true,
|
||||
"status": 1,
|
||||
"expireAt": "2025-12-31T23:59:59Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. 前后端对接要点
|
||||
- Header:管理端接口需要 `X-User-Id`(用于写入审核人);多租户场景下优先根据用户解析 `shop_id`,或显式传递 `X-Shop-Id`。
|
||||
- 列表过滤:`phone` 模糊匹配 `users.phone`,`status` 精确匹配 `vip_users.status`。
|
||||
- 审核流:通过/驳回接口必须记录 `reviewer_id` 与 `reviewed_at`,便于审计。
|
||||
- App 端状态展示:进入“我的-会员”页时调用 `GET /api/vip/status`,根据返回值展示“已激活/有效期至/立即开通”。
|
||||
|
||||
##### 7.1 数据可见性与留存策略(关键新增)
|
||||
- 规则:
|
||||
- VIP 用户:可查看“所有历史数据”(不做时间裁剪)。
|
||||
- 普通用户:仅显示“最近两个月内”的数据,早于两个月的数据不显示。
|
||||
- 配置项(严禁硬编码):
|
||||
- `vip.dataRetentionDaysForNonVip`(默认 60,单位:天)写入 `system_parameters`;也可通过环境变量 `VIP_NONVIP_RETENTION_DAYS` 覆盖。
|
||||
- 当该值小于等于 0 时,视为“非VIP也不做裁剪”(便于灰度/应急)。
|
||||
- 后端落地建议:
|
||||
- 在列表/查询型接口(如 `/api/orders`, `/api/purchase-orders`, `/api/payments`, `/api/inventories/logs`, `/api/other-transactions` 等)统一增加“非VIP数据时间窗”约束:
|
||||
- 通过当前用户的 VIP 状态与 `expireAt` 判断是否 VIP;非VIP则在 SQL WHERE 中增加 `tx_time/order_time >= NOW() - INTERVAL retentionDays DAY` 等条件。
|
||||
- 采用服务层统一拦截/拼接过滤,避免各控制器重复实现。
|
||||
- 在导出/报表接口同样应用该约束,确保口径一致。
|
||||
- 前端落地建议:
|
||||
- 当非VIP进行跨越两个月以上的日期筛选时,给出轻提示:“普通用户仅显示近两个月数据,开通VIP可查看全部历史”。
|
||||
- 页面不做本地硬裁剪,一切以后端约束为准,前端仅用于用户沟通与引导开通。
|
||||
|
||||
#### 8. 业务流程(Graphviz)
|
||||
```dot
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
node [shape=box, style=rounded];
|
||||
start [label="用户进入VIP页"];
|
||||
pay [label="一键支付开通\n(POST /api/vip/pay)"];
|
||||
active [label="生效: status=1, isVip=1\n设置expireAt=now+durationDays"];
|
||||
expire [label="到期: 定时任务或登录判定\n自动置 isVip=0 / 提示续期"];
|
||||
|
||||
start -> pay -> active -> expire;
|
||||
}
|
||||
```
|
||||
|
||||
#### 9. 实施计划(MVP)
|
||||
1) 数据库脚本对齐(仅通过 MysqlMCP 执行)
|
||||
- 若目标库缺失 `vip_users`:按 `/doc/database_documentation.md` 定义创建,并补充必要索引与外键。
|
||||
- 备注:执行后同步更新 `/doc/database_documentation.md` 中的更新时间与差异说明(保持与线上一致)。
|
||||
2) 后端接口完备
|
||||
- 补齐 `AdminVipController` 的参数校验、店铺隔离、分页 total 统计(可选)。
|
||||
- 新增 App 侧接口:`GET /api/vip/status` 与 `POST /api/vip/pay`(点击即开通临时方案)。当开始实现后,将路径登记至 `/doc/openapi.yaml` 并标注状态。
|
||||
- 配置读取:接入 `system_parameters` 的 `vip.price`/`vip.durationDays`。
|
||||
3) 管理端接入
|
||||
- `VipList.vue` 与 `VipReview.vue` 接口字段对齐;在创建/编辑时透传 `shopId`(或后端根据用户解析)。
|
||||
- 在“VIP管理”中新增“价格设置”对话框:读取/修改 `vip.price`(走系统参数接口或新增专用接口)。
|
||||
4) App 接入
|
||||
- `pages/my/vip.vue` 调用 `GET /api/vip/status`;“立即开通”改为调用 `POST /api/vip/pay`(临时:点击即开通)。
|
||||
5) 定时/登录时到期检查
|
||||
- 每日定时任务或用户登录时,对已到期记录置 `isVip=0` 并提示续期(可由查询接口实时判断并回写)。
|
||||
|
||||
#### 10. 测试用例(核心)
|
||||
- 列表:无过滤/按手机号/按状态返回正确集合;分页稳定。
|
||||
- 新增:缺少 `shopId`/`userId` 报错;完整入参成功写入,`status=0` 待启用。
|
||||
- 更新:仅更新传入字段,`updated_at` 变更。
|
||||
- 审核:通过后 `status=1` 且记录 `reviewer_id/reviewed_at`;驳回后 `status=0`。
|
||||
- 到期:`expireAt < now()` 时,状态查询返回 `isVip=false`(或提示续期),符合设计。
|
||||
- 数据可见性:
|
||||
- 非VIP:订单/支付/库存流水/其他收支等查询接口,返回记录的最早时间不早于“当前时间-60天”(默认值);当调整 `vip.dataRetentionDaysForNonVip` 后行为随之变化。
|
||||
- VIP:在同样条件下可查询到两个月前的历史记录。
|
||||
- 前端在选择超窗的日期范围时出现轻提示,不影响接口正常返回。
|
||||
- 一键支付开通:
|
||||
- 点击开通后接口返回成功,`vip_users` 写入/更新,`status=1,isVip=1,expireAt=now+durationDays`。
|
||||
- 再次点击在有效期内应提示“已是VIP/延长有效期”,并将 `expireAt` 顺延 `durationDays`(若设计为续期)。
|
||||
- 管理端价格设置:
|
||||
- 读取当前价格成功显示;修改后保存成功,重新读取为新值;前端展示同步更新(以接口返回为准)。
|
||||
|
||||
#### 11. 非功能性要求
|
||||
- 审计:记录审核人与审核时间。
|
||||
- 性能:常用过滤列建索引(`shop_id,status`)。
|
||||
- 安全:所有接口在租户维度强制鉴权与隔离;仅店主/平台管理员可审核。
|
||||
|
||||
#### 12. 验收标准
|
||||
- 管理端可增改查 VIP,支持启停与审核通过/驳回。
|
||||
- App 可正确显示会员状态与有效期,支持提交开通申请。
|
||||
- 配置可通过参数调整价格与时长,无任何硬编码常量。
|
||||
- OpenAPI 中 VIP 路径状态标注准确,接口字段与实现一致。
|
||||
- 数据可见性:非VIP仅显示近两个月数据;VIP显示全部历史;可通过系统参数动态调整非VIP留存天数。
|
||||
- 一键支付:点击立即开通可直接成为VIP并设置到期时间;有效期内再次开通实现续期(如有设计)。
|
||||
- 价格设置:管理员可在“VIP管理”中读取/修改VIP价格,保存后前端价格展示与后端一致。
|
||||
|
||||
#### 13. 普通管理员(Admin-Lite)与申请机制(新增需求)
|
||||
|
||||
- 背景:在现有平台管理员(`admins` 表)之外,新增“普通管理员(Normal Admin)”,供用户端的 VIP 用户申请成为普通管理员后,登录一个精简的普通管理端(admin-lite)。
|
||||
- 目标:
|
||||
- 普通管理端仅包含“配件审核”与“我的”两个功能。
|
||||
- 普通管理员使用“用户端账号(邮箱 + 密码)”登录普通管理端。
|
||||
- 申请资格:仅限 VIP(且建议要求处于有效期内)。
|
||||
|
||||
##### 13.1 业务规则
|
||||
- 申请前置:用户必须为 VIP 用户(建议校验 `vip_users.status=1` 且 `expire_at>now()`)。
|
||||
- 审批策略(多方案):
|
||||
- 方案A(推荐)平台审核:提交申请→平台管理员审核→通过后赋予“普通管理员”权限。
|
||||
- 方案B 自动通过:成为 VIP 后点击“申请”即自动获取权限(适合早期冷启动)。
|
||||
- 方案C 店主审核:若后续引入“店主/员工”角色体系,可由店主审批本店普通管理员。
|
||||
- 有效性约束(推荐):普通管理员权限与 VIP 状态绑定;当 VIP 过期则自动失去普通管理员权限(或进入“受限”状态,仅保留查看)。
|
||||
- 范围隔离:普通管理员仅可访问其所属店铺 `shop_id` 范围内的资源(含配件提交与审核)。
|
||||
|
||||
##### 13.2 数据模型(多方案)
|
||||
- 方案A:复用 `users.role`,并新增审计表(快速落地)
|
||||
- `users.role` 可写入 `normal_admin` 作为标识;新增 `normal_admin_audits` 记录申请、审批与撤销流水。
|
||||
- 优点:改动小,落地快。
|
||||
- 风险:`role` 单值扩展性差,后续多角色并存不便。
|
||||
- 方案B:新增 `user_roles`(推荐中长期)
|
||||
- 结构:`id, user_id, role, status, created_at, updated_at`,可并存多个角色;配套 `user_role_audits` 留痕。
|
||||
- 优点:扩展性强,便于细粒度权限。
|
||||
- 成本:需要通用角色判定与拦截器改造。
|
||||
- 方案C:新增 `normal_admins`
|
||||
- 结构:`id, shop_id, user_id, status, expire_at(可选), remark, reviewer_id, reviewed_at, created_at, updated_at`
|
||||
- 优点:语义清晰;可直接与 VIP 绑定。
|
||||
- 成本:专用表+专用逻辑,通用性略弱。
|
||||
|
||||
当前建议:MVP 采用方案A或C(二选一),优先保证上线速度;后续演进到方案B以支撑更多角色与权限。
|
||||
|
||||
配置项(严禁硬编码,写入 `system_parameters` 或全局参数表):
|
||||
- `normalAdmin.autoApprove`:是否自动通过申请(默认 false)。
|
||||
- `normalAdmin.requiredVipActive`:是否要求 VIP 有效期内(默认 true)。
|
||||
|
||||
##### 13.3 接口契约(开始实现任一侧后同步登记至 `/doc/openapi.yaml` 并标注状态)
|
||||
- 用户端(App/小程序):
|
||||
- POST `/api/normal-admin/apply`:提交申请。需要登录且满足 VIP 条件;根据 `normalAdmin.autoApprove` 决定是否即时生效。
|
||||
- GET `/api/normal-admin/application/status`:查询本人申请状态与生效情况。
|
||||
- 平台管理端:
|
||||
- GET `/api/admin/normal-admin/applications`:申请列表(支持状态/关键词/时间范围)。
|
||||
- POST `/api/admin/normal-admin/applications/{id}/approve`:审批通过。
|
||||
- POST `/api/admin/normal-admin/applications/{id}/reject`:审批驳回(需备注)。
|
||||
- 普通管理端鉴权(见 13.4):
|
||||
- 登录沿用用户侧认证:`POST /api/auth/email/login` 或现有登录接口,后端签发 Token 时加入 `scope`/`role` 标识(如 `normal_admin`)。
|
||||
|
||||
示例返回(申请状态):
|
||||
```json
|
||||
{ "status": "approved", "isVip": true, "vipExpireAt": "2025-12-31T23:59:59Z" }
|
||||
```
|
||||
|
||||
##### 13.4 鉴权与拦截(Admin-Lite)
|
||||
- 新增拦截器 `NormalAdminAuthInterceptor`(或等效过滤器):
|
||||
- 校验登录 Token 为用户侧 Token;读取 `role/scope` 含 `normal_admin`。
|
||||
- 若启用 `normalAdmin.requiredVipActive`:同时校验 `vip_users` 有效。
|
||||
- 强制多租户隔离:将 `shop_id` 固化为当前用户所属店铺,杜绝越权访问。
|
||||
- 路由命名建议:`/api/normal-admin/**` 与平台 `/api/admin/**` 区隔;服务层可复用同一业务逻辑。
|
||||
- 兼容方案:若短期沿用现有 `/api/admin/parts/submissions*`,需在后端根据 Token 类型与 `shop_id` 自动收敛到本店数据(不建议长期共用)。
|
||||
|
||||
##### 13.5 普通管理端(admin-lite)前端
|
||||
- 技术栈:Vue3 + Vite + Element Plus,与现管理端一致;新建 `normal-admin/` 工程或在现有 `admin/` 下以独立入口构建。
|
||||
- 模块:
|
||||
- 配件审核:复制 `admin/src/views/parts/Submissions.vue`,仅保留必要字段与操作;HTTP 基址指向 `/api/normal-admin/...`(或复用后端别名);列表限定为本店数据。
|
||||
- 我的:展示账户信息、VIP 状态、普通管理员状态、退出登录;若未获批且为 VIP,显示“申请成为普通管理员”按钮(调用 `/api/normal-admin/apply`)。
|
||||
- 路由示例:
|
||||
- `/` → 重定向 `/parts/submissions`
|
||||
- `/parts/submissions` → 审核页
|
||||
- `/my` → 我的
|
||||
- 配置(环境变量,不得硬编码):
|
||||
- `VITE_APP_TITLE=配件审核(普通管理端)`
|
||||
- `VITE_API_BASE=/api`(与部署网关一致)
|
||||
- `VITE_ENABLE_EXPORT=true/false`(是否开启导出)
|
||||
|
||||
##### 13.6 流程图(Graphviz)
|
||||
```dot
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
node [shape=box, style=rounded];
|
||||
a[label="成为VIP\n(GET /api/vip/status)"];
|
||||
b[label="申请普通管理员\n(POST /api/normal-admin/apply)"];
|
||||
c[label="平台审批/自动通过\n(根据 normalAdmin.autoApprove)"];
|
||||
d[label="获得 normal_admin 权限\n(Token 含 scope)"];
|
||||
e[label="登录普通管理端\n(邮箱+密码)"];
|
||||
f[label="配件审核\n(/api/normal-admin/parts/submissions*)"];
|
||||
|
||||
a -> b -> c -> d -> e -> f;
|
||||
}
|
||||
```
|
||||
|
||||
##### 13.7 验收标准(Admin-Lite)
|
||||
- 登录:使用用户邮箱+密码成功登录普通管理端;非普通管理员登录被拒绝并提示申请。
|
||||
- 资格:非 VIP 点击“申请”提示需先成为 VIP;VIP 点击“申请”按配置自动通过或进入待审;审批通过后刷新权限可访问。
|
||||
- 范围:仅能查看与操作本店 `shop_id` 范围内的配件提交;越权访问被拦截。
|
||||
- 审核:通过/驳回、备注、图片管理、参数编辑等操作与现管理端一致;操作成功刷新列表。
|
||||
- 导出(可选):按配置开放导出功能,文件名与权限口径与现实现一致。
|
||||
- 退化:当 VIP 失效时,若配置为强绑定,应在下一次鉴权时移除 `normal_admin` 访问权限(或降级为只读)。
|
||||
|
||||
##### 13.8 实施计划(补充)
|
||||
1) 后端:
|
||||
- 新增 `NormalAdminAuthInterceptor` 与 `/api/normal-admin/**` 路由;实现申请接口与(可选)平台审批接口;复用配件审核服务,默认按当前用户 `shop_id` 限定范围。
|
||||
- Token 扩展:在用户侧登录成功时,根据数据库角色写入 `role/scope=normal_admin`。
|
||||
- 配置读取:接入 `normalAdmin.autoApprove`、`normalAdmin.requiredVipActive`(来自 `system_parameters` 或全局参数表)。
|
||||
2) 前端(admin-lite):
|
||||
- 建立最小可用工程,复制并精简 `Submissions.vue`;新增 `My.vue`;完善登录页与路由守卫(未授权跳转登录)。
|
||||
- “我的”页集成申请按钮与状态显示;成功后刷新权限并跳转审核页。
|
||||
3) 文档与 OpenAPI:
|
||||
- 任一侧开工后,将上述接口登记至 `/doc/openapi.yaml` 并标注实现状态(❌/✅)。
|
||||
|
||||
##### 13.9 风险与注意事项
|
||||
- 权限收敛:严禁普通管理员访问跨店铺数据;所有查询/导出在服务层统一追加 `shop_id=当前用户.shop_id` 条件。
|
||||
- 身份冲突:若同一用户既是平台管理员又是普通管理员,需优先以平台后台入口登录对应端;两个端的 Token/域名/路由需区分。
|
||||
- 续期与降级:明确 VIP 失效后的权限处理策略与用户提示文案,避免误用。
|
||||
- 审计留痕:申请/审批/撤销必须入审计表;审核操作记录操作人与时间。
|
||||
|
||||
##### 13.10 选型落地(方案A)
|
||||
- 选型结论:
|
||||
- 审批策略采用「方案A(平台审核)」:提交申请→平台管理员审核→通过后赋予“普通管理员”权限。
|
||||
- 数据模型采用「方案A(复用 `users.role` + 审计表)」:当前生效权限以 `users.role` 为准,申请/审批/撤销全量留痕于审计表。
|
||||
|
||||
- 审计表 DDL(提交后通过 MysqlMCP 执行;此处为权威定义,禁止硬编码):
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS normal_admin_audits (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
shop_id BIGINT UNSIGNED NOT NULL,
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
action ENUM('apply','approve','reject','revoke','expire') NOT NULL,
|
||||
remark VARCHAR(255) NULL,
|
||||
operator_admin_id BIGINT UNSIGNED NULL COMMENT '平台管理员ID(apply时可空)',
|
||||
previous_role VARCHAR(32) NULL,
|
||||
new_role VARCHAR(32) NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_naudit_shop_time (shop_id, created_at),
|
||||
KEY idx_naudit_user_time (user_id, created_at),
|
||||
CONSTRAINT fk_naudit_shop FOREIGN KEY (shop_id) REFERENCES shops(id),
|
||||
CONSTRAINT fk_naudit_user FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
CONSTRAINT fk_naudit_admin FOREIGN KEY (operator_admin_id) REFERENCES admins(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='普通管理员申请/审批审计日志';
|
||||
```
|
||||
|
||||
- 状态判定与待办列表(基于审计流,不单独建申请表):
|
||||
- 当前状态 = 按 `user_id` 取最后一条审计记录的 `action`。
|
||||
- 待审批 = 最后一条为 `apply` 且其后没有 `approve/reject`。
|
||||
- 失效处理 = 定时或登录时检测 VIP 过期,若配置 `normalAdmin.requiredVipActive=true`,写入 `expire` 审计并触发降级(见下)。
|
||||
|
||||
- 接口约定细化(开始实现任一侧后再登记至 `/doc/openapi.yaml` 并标注状态):
|
||||
- 用户申请:POST `/api/normal-admin/apply`
|
||||
- 校验:已登录 + VIP 有效(当 `requiredVipActive=true`)。
|
||||
- 行为:写入 `normal_admin_audits(action='apply')`;若 `autoApprove=true` 则转调用审批通过流程。
|
||||
- 审批列表:GET `/api/admin/normal-admin/applications`
|
||||
- 规则:按照审计记录推导“当前为 apply 且未被approve/reject”的记录;支持关键词与时间过滤;返回 `{ list, total }`。
|
||||
- 审批通过:POST `/api/admin/normal-admin/applications/{userId}/approve`
|
||||
- 行为:读取当前 `users.role` 记为 `previous_role`;更新 `users.role='normal_admin'`;写入审计 `approve`(含 `previous_role/new_role`)。
|
||||
- 审批驳回:POST `/api/admin/normal-admin/applications/{userId}/reject`
|
||||
- 行为:写入审计 `reject`(需 remark)。
|
||||
- 撤销权限:POST `/api/admin/normal-admin/users/{userId}/revoke`
|
||||
- 行为:将 `users.role` 回滚到最近一次 `approve` 的 `previous_role`(若不存在则设为 `'staff'` 或根据 `is_owner` 退回 `'owner'`);写入审计 `revoke`。
|
||||
|
||||
- 鉴权与Token(Admin-Lite):
|
||||
- 登录沿用用户端登录接口(邮箱/密码),签发 Token 时附带 `role` 与 `shopId`;Admin-Lite 入口要求 `role='normal_admin'`。
|
||||
- 在拦截器 `NormalAdminAuthInterceptor` 中:
|
||||
- 校验 `role='normal_admin'`;若 `requiredVipActive=true`,同时校验当前用户 VIP 有效。
|
||||
- 将请求上下文中的 `shop_id` 固化为当前用户 `shop_id`,所有查询自动拼接该条件。
|
||||
|
||||
- 失效与回滚策略:
|
||||
- 当 VIP 过期(检测点:每日任务/登录/接口访问拦截),若启用强绑定则触发降级:
|
||||
- 将 `users.role` 由 `normal_admin` 回滚至上次 `approve` 的 `previous_role`(若无记录:`is_owner=1` 回滚到 `'owner'`,否则 `'staff'`)。
|
||||
- 写入 `expire` 审计(remark 记录触发缘由)。
|
||||
|
||||
- 风险与缓解(方案A局限):
|
||||
- `users.role` 为单值字段,无法并存多角色;短期仅用于识别 Admin-Lite 访问权限,不影响用户侧功能(用户侧权限建议依赖 `is_owner` 与接口授权)。
|
||||
- 后续若需多角色并存,按 13.2 的方案B 迁移至 `user_roles`,并在迁移期间保留写入 `users.role` 的兼容逻辑。
|
||||
|
||||
- 测试要点补充:
|
||||
- 申请→审批通过:审计有 `apply`、`approve` 两条,`users.role` 变为 `normal_admin`,Admin-Lite 可登录访问。
|
||||
- 申请→驳回:审计有 `apply`、`reject`,`users.role` 不变,Admin-Lite 拒绝访问。
|
||||
- 撤销:`users.role` 回滚为 `previous_role`,新增 `revoke` 审计;再次访问被拒绝。
|
||||
- VIP 过期:若强绑定开启,触发降级并写入 `expire`,Admin-Lite 访问被拒绝;关闭强绑定时仅告警不降级。
|
||||
|
||||
—— 本文档为当前功能实现说明与落地细节,不含历史变更记录;如有与数据模型不匹配之处,以 `/doc/database_documentation.md` 为准并及时校准。
|
||||
Reference in New Issue
Block a user