Files
PartsInquiry/doc/vip_development.md
2025-09-27 22:57:59 +08:00

23 KiB
Raw Blame History

VIP 功能开发文档

1. 范围与目标

  • 目标为“配件查询”应用提供面向店铺租户的会员体系VIP支持会员开通、审核、生效与到期管理并为后续增值能力如数据永久存储、高级报表、优先审核等提供统一的权限判定入口。
  • 范围:后端服务与管理端优先落地;移动端展示会员状态与开通入口,后续再接入支付与自动续期。

2. 现状与差距

  • 前端 Appfrontend/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.jsVIP_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 并标注实现状态,避免“仅设计未实现”的文档污染。

示例返回(状态查询):

{
  "isVip": true,
  "status": 1,
  "expireAt": "2025-12-31T23:59:59Z"
}

7. 前后端对接要点

  • Header管理端接口需要 X-User-Id(用于写入审核人);多租户场景下优先根据用户解析 shop_id,或显式传递 X-Shop-Id
  • 列表过滤:phone 模糊匹配 users.phonestatus 精确匹配 vip_users.status
  • 审核流:通过/驳回接口必须记录 reviewer_idreviewed_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

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/statusPOST /api/vip/pay(点击即开通临时方案)。当开始实现后,将路径登记至 /doc/openapi.yaml 并标注状态。
    • 配置读取:接入 system_parametersvip.price/vip.durationDays
  3. 管理端接入
    • VipList.vueVipReview.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=1expire_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)。

示例返回(申请状态):

{ "status": "approved", "isVip": true, "vipExpireAt": "2025-12-31T23:59:59Z" }
13.4 鉴权与拦截Admin-Lite
  • 新增拦截器 NormalAdminAuthInterceptor(或等效过滤器):
    • 校验登录 Token 为用户侧 Token读取 role/scopenormal_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
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 点击“申请”提示需先成为 VIPVIP 点击“申请”按配置自动通过或进入待审;审批通过后刷新权限可访问。
  • 范围:仅能查看与操作本店 shop_id 范围内的配件提交;越权访问被拦截。
  • 审核:通过/驳回、备注、图片管理、参数编辑等操作与现管理端一致;操作成功刷新列表。
  • 导出(可选):按配置开放导出功能,文件名与权限口径与现实现一致。
  • 退化:当 VIP 失效时,若配置为强绑定,应在下一次鉴权时移除 normal_admin 访问权限(或降级为只读)。
13.8 实施计划(补充)
  1. 后端:
  • 新增 NormalAdminAuthInterceptor/api/normal-admin/** 路由;实现申请接口与(可选)平台审批接口;复用配件审核服务,默认按当前用户 shop_id 限定范围。
  • Token 扩展:在用户侧登录成功时,根据数据库角色写入 role/scope=normal_admin
  • 配置读取:接入 normalAdmin.autoApprovenormalAdmin.requiredVipActive(来自 system_parameters 或全局参数表)。
  1. 前端admin-lite
  • 建立最小可用工程,复制并精简 Submissions.vue;新增 My.vue;完善登录页与路由守卫(未授权跳转登录)。
  • “我的”页集成申请按钮与状态显示;成功后刷新权限并跳转审核页。
  1. 文档与 OpenAPI
  • 任一侧开工后,将上述接口登记至 /doc/openapi.yaml 并标注实现状态(/)。
13.9 风险与注意事项
  • 权限收敛:严禁普通管理员访问跨店铺数据;所有查询/导出在服务层统一追加 shop_id=当前用户.shop_id 条件。
  • 身份冲突:若同一用户既是平台管理员又是普通管理员,需优先以平台后台入口登录对应端;两个端的 Token/域名/路由需区分。
  • 续期与降级:明确 VIP 失效后的权限处理策略与用户提示文案,避免误用。
  • 审计留痕:申请/审批/撤销必须入审计表;审核操作记录操作人与时间。
13.10 选型落地方案A
  • 选型结论:

    • 审批策略采用「方案A平台审核提交申请→平台管理员审核→通过后赋予“普通管理员”权限。
    • 数据模型采用「方案A复用 users.role + 审计表)」:当前生效权限以 users.role 为准,申请/审批/撤销全量留痕于审计表。
  • 审计表 DDL提交后通过 MysqlMCP 执行;此处为权威定义,禁止硬编码):

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 '平台管理员IDapply时可空',
  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 回滚到最近一次 approveprevious_role(若不存在则设为 'staff' 或根据 is_owner 退回 'owner');写入审计 revoke
  • 鉴权与TokenAdmin-Lite

    • 登录沿用用户端登录接口(邮箱/密码),签发 Token 时附带 roleshopIdAdmin-Lite 入口要求 role='normal_admin'
    • 在拦截器 NormalAdminAuthInterceptor 中:
      • 校验 role='normal_admin';若 requiredVipActive=true,同时校验当前用户 VIP 有效。
      • 将请求上下文中的 shop_id 固化为当前用户 shop_id,所有查询自动拼接该条件。
  • 失效与回滚策略:

    • 当 VIP 过期(检测点:每日任务/登录/接口访问拦截),若启用强绑定则触发降级:
      • users.rolenormal_admin 回滚至上次 approveprevious_role(若无记录:is_owner=1 回滚到 'owner',否则 'staff')。
      • 写入 expire 审计remark 记录触发缘由)。
  • 风险与缓解方案A局限

    • users.role 为单值字段,无法并存多角色;短期仅用于识别 Admin-Lite 访问权限,不影响用户侧功能(用户侧权限建议依赖 is_owner 与接口授权)。
    • 后续若需多角色并存,按 13.2 的方案B 迁移至 user_roles,并在迁移期间保留写入 users.role 的兼容逻辑。
  • 测试要点补充:

    • 申请→审批通过:审计有 applyapprove 两条,users.role 变为 normal_adminAdmin-Lite 可登录访问。
    • 申请→驳回:审计有 applyrejectusers.role 不变Admin-Lite 拒绝访问。
    • 撤销:users.role 回滚为 previous_role,新增 revoke 审计;再次访问被拒绝。
    • VIP 过期:若强绑定开启,触发降级并写入 expireAdmin-Lite 访问被拒绝;关闭强绑定时仅告警不降级。

—— 本文档为当前功能实现说明与落地细节,不含历史变更记录;如有与数据模型不匹配之处,以 /doc/database_documentation.md 为准并及时校准。