Files
huang 5a90caa619
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m39s
feat(shop-role): 实现店铺角色继承功能和权限检查优化
- 新增店铺角色管理 API 和数据模型
- 实现角色继承和权限检查逻辑
- 添加流程测试框架和集成测试
- 更新权限服务和账号管理逻辑
- 添加数据库迁移脚本
- 归档 OpenSpec 变更文档

Ultraworked with Sisyphus
2026-02-03 10:06:13 +08:00

9.9 KiB
Raw Permalink Blame History

shop-role-management Specification

Purpose

提供店铺级角色管理能力,允许平台为代理店铺设置默认角色,该店铺下所有账号自动继承,简化 MVP 阶段的批量角色分配操作。

Requirements

Requirement: 分配店铺角色

系统 SHALL 提供接口允许平台用户或店铺管理员为店铺分配角色。

接口: POST /api/admin/shops/:shop_id/roles

请求体:

{
  "role_ids": [5]  // 角色 ID 列表,传空数组表示清空所有角色
}

响应体:

{
  "code": 0,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "shop_id": 10,
      "role_id": 5,
      "status": 1,
      "created_at": "2026-02-02T10:00:00Z"
    }
  ],
  "timestamp": "2026-02-02T10:00:00Z"
}

Scenario: 成功分配单个角色

  • WHEN 平台用户调用 POST /api/admin/shops/10/roles 请求体为 {"role_ids": [5]}
  • AND 角色 ID 5 存在且为客户角色RoleType=2
  • AND 店铺 ID 10 存在
  • THEN 系统创建店铺-角色关联记录
  • AND 返回 HTTP 200 和关联记录
  • AND 清理该店铺下所有账号的权限缓存

Scenario: 清空店铺所有角色

  • WHEN 平台用户调用 POST /api/admin/shops/10/roles 请求体为 {"role_ids": []}
  • THEN 系统删除该店铺的所有角色关联
  • AND 返回 HTTP 200 和空数组
  • AND 清理该店铺下所有账号的权限缓存

Scenario: 替换现有角色

  • WHEN 店铺已分配角色 ID 5
  • AND 平台用户调用 POST /api/admin/shops/10/roles 请求体为 {"role_ids": [7]}
  • THEN 系统删除原有角色 ID 5 的关联
  • AND 创建新的角色 ID 7 的关联
  • AND 返回 HTTP 200 和新关联记录

Scenario: 角色类型校验失败

  • WHEN 平台用户调用 POST /api/admin/shops/10/roles 请求体为 {"role_ids": [3]}
  • AND 角色 ID 3 是平台角色RoleType=1
  • THEN 返回 HTTP 400 错误码 errors.CodeInvalidParam
  • AND 错误消息为"店铺只能分配客户角色"
  • AND 不创建任何关联记录

Scenario: 店铺不存在

  • WHEN 平台用户调用 POST /api/admin/shops/999/roles
  • AND 店铺 ID 999 不存在
  • THEN 返回 HTTP 404 错误码 errors.CodeNotFound
  • AND 错误消息为"店铺不存在"

Scenario: 权限不足

  • WHEN 代理用户调用 POST /api/admin/shops/20/roles
  • AND 店铺 ID 20 不在该代理的管理范围内(不是自己店铺或下级店铺)
  • THEN 返回 HTTP 403 错误码 errors.CodeForbidden
  • AND 错误消息为"无权限操作该资源或资源不存在"

Requirement: 查询店铺角色

系统 SHALL 提供接口查询店铺已分配的角色列表。

接口: GET /api/admin/shops/:shop_id/roles

响应体:

{
  "code": 0,
  "msg": "success",
  "data": {
    "shop_id": 10,
    "roles": [
      {
        "shop_id": 10,
        "role_id": 5,
        "role_name": "代理店长",
        "role_desc": "代理店铺管理员",
        "status": 1
      }
    ]
  },
  "timestamp": "2026-02-02T10:00:00Z"
}

Scenario: 查询已分配角色

  • WHEN 平台用户调用 GET /api/admin/shops/10/roles
  • AND 店铺 ID 10 已分配角色 ID 5
  • THEN 返回 HTTP 200 和角色详情列表
  • AND 包含角色名称、描述等信息

Scenario: 查询未分配角色的店铺

  • WHEN 平台用户调用 GET /api/admin/shops/10/roles
  • AND 店铺 ID 10 未分配任何角色
  • THEN 返回 HTTP 200
  • AND roles 字段为空数组

Scenario: 店铺不存在

  • WHEN 平台用户调用 GET /api/admin/shops/999/roles
  • AND 店铺 ID 999 不存在
  • THEN 返回 HTTP 404 错误码 errors.CodeNotFound
  • AND 错误消息为"店铺不存在"

Scenario: 权限不足

  • WHEN 代理用户调用 GET /api/admin/shops/20/roles
  • AND 店铺 ID 20 不在该代理的管理范围内
  • THEN 返回 HTTP 403 错误码 errors.CodeForbidden
  • AND 错误消息为"无权限操作该资源或资源不存在"

Requirement: 删除店铺角色

系统 SHALL 提供接口删除店铺的特定角色关联。

接口: DELETE /api/admin/shops/:shop_id/roles/:role_id

响应体:

{
  "code": 0,
  "msg": "success",
  "data": null,
  "timestamp": "2026-02-02T10:00:00Z"
}

Scenario: 成功删除角色

  • WHEN 平台用户调用 DELETE /api/admin/shops/10/roles/5
  • AND 店铺 ID 10 存在
  • AND 店铺已分配角色 ID 5
  • THEN 系统删除该关联记录
  • AND 返回 HTTP 200
  • AND 清理该店铺下所有账号的权限缓存

Scenario: 删除不存在的角色关联

  • WHEN 平台用户调用 DELETE /api/admin/shops/10/roles/5
  • AND 店铺 ID 10 未分配角色 ID 5
  • THEN 返回 HTTP 200幂等操作
  • AND 不执行任何数据库操作

Scenario: 店铺不存在

  • WHEN 平台用户调用 DELETE /api/admin/shops/999/roles/5
  • AND 店铺 ID 999 不存在
  • THEN 返回 HTTP 404 错误码 errors.CodeNotFound
  • AND 错误消息为"店铺不存在"

Scenario: 权限不足

  • WHEN 代理用户调用 DELETE /api/admin/shops/20/roles/5
  • AND 店铺 ID 20 不在该代理的管理范围内
  • THEN 返回 HTTP 403 错误码 errors.CodeForbidden
  • AND 错误消息为"无权限操作该资源或资源不存在"

Requirement: 数据库表结构

系统 SHALL 创建 tb_shop_role 表存储店铺-角色关联关系。

表结构:

CREATE TABLE tb_shop_role (
    id SERIAL PRIMARY KEY,
    shop_id INT NOT NULL,
    role_id INT NOT NULL,
    status INT NOT NULL DEFAULT 1,  -- 0=禁用 1=启用
    creator INT NOT NULL,
    updater INT NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    deleted_at TIMESTAMP,
    UNIQUE (shop_id, role_id) WHERE deleted_at IS NULL
);

索引:

  • idx_shop_role_shop_id - 查询店铺角色(高频)
  • idx_shop_role_role_id - 查询角色被哪些店铺使用(低频)
  • idx_shop_role_deleted_at - 软删除过滤

Scenario: 唯一性约束

  • WHEN 尝试为同一店铺分配同一角色两次
  • THEN 数据库返回唯一性约束冲突错误
  • AND 系统捕获错误并返回友好错误消息

Scenario: 软删除机制

  • WHEN 删除店铺角色关联
  • THEN 系统设置 deleted_at 字段为当前时间
  • AND 后续查询自动过滤 deleted_at IS NOT NULL 的记录

Requirement: 缓存失效策略

系统 SHALL 在店铺角色变更时清理相关账号的权限缓存。

Scenario: 分配角色时清理缓存

  • WHEN 为店铺 ID 10 分配角色
  • THEN 系统查询该店铺下所有账号 ID 列表
  • AND 遍历删除每个账号的权限缓存键 user:permissions:{account_id}
  • AND 下次权限检查时,账号会重新查询并继承新角色

Scenario: 删除角色时清理缓存

  • WHEN 删除店铺 ID 10 的角色关联
  • THEN 系统查询该店铺下所有账号 ID 列表
  • AND 遍历删除每个账号的权限缓存键
  • AND 下次权限检查时,账号将无角色(如果无账号级角色)

Scenario: 账号有自己角色时不受影响

  • WHEN 店铺角色变更
  • AND 某账号有自己的账号级角色
  • THEN 该账号的权限缓存被清理
  • AND 下次权限检查时,继续使用账号级角色(不继承店铺角色)

Requirement: 权限控制

店铺角色管理接口 SHALL 实施权限控制,只有有权限的用户才能操作。

权限规则:

  • 超级管理员UserType=1可操作所有店铺
  • 平台用户UserType=2可操作所有店铺
  • 代理用户UserType=3只能操作自己店铺及下级店铺
  • 企业用户UserType=4无权限操作店铺角色

Scenario: 超级管理员操作任意店铺

  • WHEN 超级管理员调用店铺角色管理接口
  • THEN 跳过权限检查
  • AND 允许操作任意店铺

Scenario: 平台用户操作任意店铺

  • WHEN 平台用户调用店铺角色管理接口
  • THEN 允许操作任意店铺

Scenario: 代理用户操作下级店铺

  • WHEN 代理用户shop_id=10调用店铺角色管理接口
  • AND 目标店铺 ID 15 是店铺 10 的下级店铺
  • THEN 调用 middleware.CanManageShop(ctx, 15, shopStore)
  • AND 返回 nil有权限
  • AND 允许操作

Scenario: 代理用户操作无关店铺

  • WHEN 代理用户shop_id=10调用店铺角色管理接口
  • AND 目标店铺 ID 20 不是店铺 10 的下级店铺
  • THEN 调用 middleware.CanManageShop(ctx, 20, shopStore)
  • AND 返回 error无权限
  • AND 拒绝操作

Scenario: 企业用户尝试操作店铺角色

  • WHEN 企业用户调用店铺角色管理接口
  • THEN 返回 HTTP 403 错误码 errors.CodeForbidden
  • AND 错误消息为"无权限操作该资源或资源不存在"

Requirement: 业务规则校验

店铺角色分配 SHALL 执行业务规则校验,确保数据一致性。

Scenario: 角色存在性校验

  • WHEN 分配店铺角色时指定角色 ID 列表
  • THEN 系统查询所有角色是否存在
  • AND 如果部分角色不存在,返回错误"部分角色不存在"
  • AND 不创建任何关联记录(原子操作)

Scenario: 角色状态校验

  • WHEN 分配店铺角色时指定角色 ID
  • AND 该角色的 status 字段为 0禁用
  • THEN 返回错误"角色已禁用"
  • AND 不创建关联记录

Scenario: 角色类型校验

  • WHEN 分配店铺角色时指定角色 ID
  • AND 该角色的 role_type 字段为 1平台角色
  • THEN 返回错误"店铺只能分配客户角色"
  • AND 不创建关联记录

Scenario: 店铺存在性校验

  • WHEN 分配店铺角色时指定店铺 ID
  • AND 该店铺不存在或已软删除
  • THEN 返回错误"店铺不存在"
  • AND 不执行任何操作