|
|
@@ -0,0 +1,1319 @@
|
|
|
+# 统一权限管理系统(Permission System Server)
|
|
|
+
|
|
|
+集中式多产品权限管理平台后端服务。为多个产品提供统一的用户认证、权限管理、角色管理能力,产品通过 HTTP API 或 gRPC 接入。
|
|
|
+
|
|
|
+## 核心特性
|
|
|
+
|
|
|
+- **多产品隔离** — 权限、角色、成员按产品(`productCode`)隔离,一个账号可跨产品通用
|
|
|
+- **自动权限同步** — 产品启动时通过 API 自动上报权限列表,系统自动新增/更新/禁用
|
|
|
+- **灵活的权限模型** — 角色权限 + 用户级 ALLOW/DENY 覆盖,细粒度控制
|
|
|
+- **研发部门自动授权** — 研发部门(`deptType=DEV`)的成员加入产品后自动拥有全部权限
|
|
|
+- **多维度操作权限管控** — 集中式访问控制,覆盖超管/产品管理员/成员类型/部门层级/权限级别五个维度
|
|
|
+- **用户信息缓存** — `UserDetailsLoader` 一次性加载用户完整信息,Redis 缓存 + TTL + 主动失效
|
|
|
+- **双协议** — 同时提供 HTTP REST API(管理 UI)和 gRPC(产品后端高性能调用)
|
|
|
+- **JWT 本地验证** — 登录获取 JWT,产品后端可本地验证,无需每次请求回调权限系统
|
|
|
+- **Token 类型安全** — access/refresh token 通过 `tokenType` 字段严格区分,防止 token 混用
|
|
|
+- **四层安全纵深** — JWT 中间件 → 用户状态实时检查 → 冻结账号拦截 → Logic 层操作权限控制
|
|
|
+
|
|
|
+## 系统架构
|
|
|
+
|
|
|
+```mermaid
|
|
|
+graph TB
|
|
|
+ subgraph 权限系统
|
|
|
+ HTTP[HTTP API :10001]
|
|
|
+ GRPC[gRPC Server :10002]
|
|
|
+ DB[(MySQL)]
|
|
|
+ CACHE[(Redis Cache)]
|
|
|
+ HTTP --> DB
|
|
|
+ HTTP --> CACHE
|
|
|
+ GRPC --> DB
|
|
|
+ GRPC --> CACHE
|
|
|
+ end
|
|
|
+
|
|
|
+ UI[管理后台 UI] -->|REST API| HTTP
|
|
|
+ ProductA[产品 A 后端] -->|gRPC| GRPC
|
|
|
+ ProductB[产品 B 后端] -->|gRPC| GRPC
|
|
|
+ ProductC[产品 C 后端] -->|HTTP| HTTP
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 核心概念与实体关系
|
|
|
+
|
|
|
+### 实体关系总览
|
|
|
+
|
|
|
+```mermaid
|
|
|
+erDiagram
|
|
|
+ sys_product ||--o{ sys_perm : "productCode → 产品拥有权限"
|
|
|
+ sys_product ||--o{ sys_role : "productCode → 产品拥有角色"
|
|
|
+ sys_product ||--o{ sys_product_member : "productCode → 产品拥有成员"
|
|
|
+ sys_user ||--o{ sys_product_member : "userId → 用户加入产品"
|
|
|
+ sys_user ||--o{ sys_user_role : "userId → 用户绑定角色"
|
|
|
+ sys_user ||--o{ sys_user_perm : "userId → 用户自定义权限"
|
|
|
+ sys_user }o--o| sys_dept : "deptId → 用户所属部门"
|
|
|
+ sys_dept }o--o| sys_dept : "parentId → 父子部门"
|
|
|
+ sys_role ||--o{ sys_role_perm : "roleId → 角色绑定权限"
|
|
|
+ sys_role ||--o{ sys_user_role : "roleId → 角色分配给用户"
|
|
|
+ sys_perm ||--o{ sys_role_perm : "permId → 权限绑定到角色"
|
|
|
+ sys_perm ||--o{ sys_user_perm : "permId → 权限直接授予用户"
|
|
|
+
|
|
|
+ sys_product {
|
|
|
+ bigint id PK
|
|
|
+ varchar code UK "产品编码"
|
|
|
+ varchar name "产品名称"
|
|
|
+ varchar appKey UK "接入密钥"
|
|
|
+ varchar appSecret "签名密钥"
|
|
|
+ tinyint status "1启用 2禁用"
|
|
|
+ }
|
|
|
+ sys_user {
|
|
|
+ bigint id PK
|
|
|
+ varchar username UK "登录名"
|
|
|
+ varchar password "bcrypt密码"
|
|
|
+ varchar nickname "昵称"
|
|
|
+ bigint deptId FK "部门ID"
|
|
|
+ tinyint isSuperAdmin "1是 2否"
|
|
|
+ tinyint mustChangePassword "1是 2否"
|
|
|
+ tinyint status "1正常 2冻结"
|
|
|
+ }
|
|
|
+ sys_dept {
|
|
|
+ bigint id PK
|
|
|
+ bigint parentId "父部门ID"
|
|
|
+ varchar name "部门名称"
|
|
|
+ varchar path "层级路径"
|
|
|
+ varchar deptType "NORMAL普通 DEV研发"
|
|
|
+ int sort "排序值"
|
|
|
+ tinyint status "1启用 2禁用"
|
|
|
+ }
|
|
|
+ sys_perm {
|
|
|
+ bigint id PK
|
|
|
+ varchar productCode "所属产品"
|
|
|
+ varchar name "权限名"
|
|
|
+ varchar code UK "权限code"
|
|
|
+ tinyint status "1启用 2禁用"
|
|
|
+ }
|
|
|
+ sys_role {
|
|
|
+ bigint id PK
|
|
|
+ varchar productCode "所属产品"
|
|
|
+ varchar name UK "角色名"
|
|
|
+ int permsLevel "权限等级"
|
|
|
+ tinyint status "1启用 2禁用"
|
|
|
+ }
|
|
|
+ sys_product_member {
|
|
|
+ bigint id PK
|
|
|
+ varchar productCode "产品编码"
|
|
|
+ bigint userId FK "用户ID"
|
|
|
+ varchar memberType "成员类型"
|
|
|
+ tinyint status "1启用 2禁用"
|
|
|
+ }
|
|
|
+ sys_role_perm {
|
|
|
+ bigint id PK
|
|
|
+ bigint roleId FK
|
|
|
+ bigint permId FK
|
|
|
+ }
|
|
|
+ sys_user_role {
|
|
|
+ bigint id PK
|
|
|
+ bigint userId FK
|
|
|
+ bigint roleId FK
|
|
|
+ }
|
|
|
+ sys_user_perm {
|
|
|
+ bigint id PK
|
|
|
+ bigint userId FK
|
|
|
+ bigint permId FK
|
|
|
+ varchar effect "ALLOW或DENY"
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+### 五大核心实体
|
|
|
+
|
|
|
+| 实体 | 说明 | 隔离范围 |
|
|
|
+|------|------|----------|
|
|
|
+| **产品 (Product)** | 接入权限系统的业务系统(如 CRM、OA),每个产品有独立的 `appKey`/`appSecret` | 全局 |
|
|
|
+| **部门 (Dept)** | 组织架构树形结构,支持无限层级嵌套。`deptType=DEV` 的研发部门有特殊权限逻辑 | 全局 |
|
|
|
+| **用户 (User)** | 全局账号,通过 `deptId` 归属部门,通过成员关系加入不同产品 | 全局 |
|
|
|
+| **角色 (Role)** | 产品级别的权限集合,同一角色名在不同产品中互不干扰 | 产品级 |
|
|
|
+| **权限 (Perm)** | 产品级别的最小权限单元,由产品后端通过 `SyncPerms` 自动上报 | 产品级 |
|
|
|
+
|
|
|
+### 实体间关系详解
|
|
|
+
|
|
|
+```
|
|
|
+全局组织架构 产品权限体系(按产品隔离)
|
|
|
+┌─────────────────┐ ┌────────────────────────────────────┐
|
|
|
+│ 部门 (Dept) │ │ 产品 (Product) │
|
|
|
+│ ├── 研发部(DEV) │ │ ┌─────────┐ ┌──────────┐ │
|
|
|
+│ ├── 市场部 │ │ │ 权限列表 │ │ 角色列表 │ │
|
|
|
+│ └── 财务部 │ │ │ Perm │ │ Role │ │
|
|
|
+└───────┬─────────┘ │ └────┬────┘ └────┬─────┘ │
|
|
|
+ │ deptId │ │ │ │
|
|
|
+┌───────┴──────┐ │ ┌────┴────────────┴────┐ │
|
|
|
+│ 用户 (User) │─────────────│ │ 角色-权限绑定 RolePerm │ │
|
|
|
+│ │ 成员关系 │ └─────────────────────┘ │
|
|
|
+│ │──────────────│ │
|
|
|
+│ │ │ ┌───────────────────────┐ │
|
|
|
+│ │──────────────│ │ 用户-角色 UserRole │ │
|
|
|
+│ │ │ └───────────────────────┘ │
|
|
|
+│ │──────────────│ ┌───────────────────────┐ │
|
|
|
+│ │ │ │ 用户-权限覆盖 UserPerm │ │
|
|
|
+└──────────────┘ │ │ ALLOW / DENY │ │
|
|
|
+ │ └───────────────────────┘ │
|
|
|
+ └────────────────────────────────────┘
|
|
|
+```
|
|
|
+
|
|
|
+**关键关系**:
|
|
|
+
|
|
|
+1. **用户 → 产品**:通过 `sys_product_member` 建立,一个用户可以是多个产品的成员
|
|
|
+2. **用户 → 部门**:通过 `sys_user.deptId` 关联,一个用户只属于一个部门
|
|
|
+3. **角色 → 权限**:通过 `sys_role_perm` 绑定,一个角色可绑定多个权限
|
|
|
+4. **用户 → 角色**:通过 `sys_user_role` 分配,一个用户可拥有多个角色
|
|
|
+5. **用户 → 权限覆盖**:通过 `sys_user_perm` 直接授予/拒绝,优先级高于角色权限
|
|
|
+
|
|
|
+### 成员类型与权限层级
|
|
|
+
|
|
|
+| 优先级 | 类型 | 权限范围 | 来源 |
|
|
|
+|--------|------|----------|------|
|
|
|
+| 1 | `SUPER_ADMIN` | **所有产品**的全部权限 | `sys_user.isSuperAdmin = 1` |
|
|
|
+| 2 | `DEVELOPER` | **该产品**全部权限 | `sys_product_member.memberType` |
|
|
|
+| 3 | `ADMIN` | **该产品**全部权限 | `sys_product_member.memberType` |
|
|
|
+| 4 | 研发部门成员 | **该产品**全部权限 | `sys_dept.deptType = DEV` + 是产品成员 |
|
|
|
+| 5 | `MEMBER` | 角色权限 ∪ ALLOW - DENY | 角色绑定 + 用户级覆盖计算 |
|
|
|
+
|
|
|
+### 权限计算流程
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ START[获取用户权限] --> IS_SUPER{是超级管理员?}
|
|
|
+ IS_SUPER -->|是| ALL_PERMS[返回该产品全部权限码]
|
|
|
+ IS_SUPER -->|否| FIND_MEMBER[查询 sys_product_member]
|
|
|
+ FIND_MEMBER --> HAS_MEMBER{是产品成员?}
|
|
|
+ HAS_MEMBER -->|否| EMPTY[返回空权限]
|
|
|
+ HAS_MEMBER -->|是| CHECK_TYPE{成员类型?}
|
|
|
+ CHECK_TYPE -->|DEVELOPER / ADMIN| ALL_PERMS
|
|
|
+ CHECK_TYPE -->|MEMBER| CHECK_DEPT{所属部门是研发部门?}
|
|
|
+ CHECK_DEPT -->|是| ALL_PERMS
|
|
|
+ CHECK_DEPT -->|否| CALC[计算权限]
|
|
|
+ CALC --> ROLE_PERMS[查询用户角色绑定的权限 ID]
|
|
|
+ ROLE_PERMS --> USER_ALLOW[查询用户级 ALLOW 权限 ID]
|
|
|
+ USER_ALLOW --> USER_DENY[查询用户级 DENY 权限 ID]
|
|
|
+ USER_DENY --> MERGE["合并: (角色权限 ∪ ALLOW) - DENY"]
|
|
|
+ MERGE --> FILTER[过滤已禁用的权限]
|
|
|
+ FILTER --> CODES[转换为权限 code 列表返回]
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 实际业务配置指南
|
|
|
+
|
|
|
+以下以一个典型的中小公司为例,说明如何配置和使用权限系统。
|
|
|
+
|
|
|
+### 场景假设
|
|
|
+
|
|
|
+公司有**研发部**(30 人)、**市场部**(10 人)、**运营部**(15 人),负责维护 3 个产品:**CRM 系统**、**OA 系统**、**电商后台**。研发人员需要在产品间流动。
|
|
|
+
|
|
|
+### 第一步:搭建组织架构
|
|
|
+
|
|
|
+```mermaid
|
|
|
+graph TD
|
|
|
+ ROOT[公司] --> DEV[研发部 deptType=DEV]
|
|
|
+ ROOT --> MKT[市场部 deptType=NORMAL]
|
|
|
+ ROOT --> OPS[运营部 deptType=NORMAL]
|
|
|
+ DEV --> FE[前端组]
|
|
|
+ DEV --> BE[后端组]
|
|
|
+ DEV --> QA[测试组]
|
|
|
+```
|
|
|
+
|
|
|
+```bash
|
|
|
+# 创建顶级部门
|
|
|
+POST /api/dept/create {"parentId": 0, "name": "研发部", "deptType": "DEV", "sort": 1}
|
|
|
+# 返回 id=1
|
|
|
+
|
|
|
+POST /api/dept/create {"parentId": 0, "name": "市场部", "sort": 2}
|
|
|
+# deptType 不传默认为 NORMAL,返回 id=2
|
|
|
+
|
|
|
+POST /api/dept/create {"parentId": 0, "name": "运营部", "sort": 3}
|
|
|
+# 返回 id=3
|
|
|
+
|
|
|
+# 创建研发部子部门
|
|
|
+POST /api/dept/create {"parentId": 1, "name": "前端组", "deptType": "DEV", "sort": 1}
|
|
|
+POST /api/dept/create {"parentId": 1, "name": "后端组", "deptType": "DEV", "sort": 2}
|
|
|
+POST /api/dept/create {"parentId": 1, "name": "测试组", "deptType": "DEV", "sort": 3}
|
|
|
+```
|
|
|
+
|
|
|
+> **研发部门的特殊能力**:`deptType=DEV` 的部门中的成员,只要被添加到某个产品下,就自动拥有该产品的全部权限。当从产品成员中移除时,权限立即收回。
|
|
|
+
|
|
|
+### 第二步:注册产品
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/product/create {"code": "crm", "name": "CRM 系统"}
|
|
|
+# 返回 appKey, appSecret, adminUser, adminPassword
|
|
|
+
|
|
|
+POST /api/product/create {"code": "oa", "name": "OA 系统"}
|
|
|
+POST /api/product/create {"code": "mall", "name": "电商后台"}
|
|
|
+```
|
|
|
+
|
|
|
+每个产品创建后会自动生成一个初始管理员账号(如 `admin_crm`),该账号是该产品的 `SUPER_ADMIN` 级别成员。
|
|
|
+
|
|
|
+### 第三步:产品上报权限
|
|
|
+
|
|
|
+各产品后端在启动时自动调用 `POST /api/perm/sync` 上报权限列表。以 CRM 为例:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "appKey": "...", "appSecret": "...",
|
|
|
+ "perms": [
|
|
|
+ {"code": "customer:list", "name": "查看客户列表"},
|
|
|
+ {"code": "customer:create", "name": "创建客户"},
|
|
|
+ {"code": "customer:update", "name": "编辑客户"},
|
|
|
+ {"code": "customer:delete", "name": "删除客户"},
|
|
|
+ {"code": "order:list", "name": "查看订单"},
|
|
|
+ {"code": "order:create", "name": "创建订单"},
|
|
|
+ {"code": "report:export", "name": "导出报表"}
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+权限由产品代码自动管理,管理员无需手动创建。
|
|
|
+
|
|
|
+### 第四步:创建角色并绑定权限
|
|
|
+
|
|
|
+```bash
|
|
|
+# CRM 产品下创建角色
|
|
|
+POST /api/role/create {"productCode": "crm", "name": "销售经理", "permsLevel": 10}
|
|
|
+POST /api/role/create {"productCode": "crm", "name": "普通销售", "permsLevel": 20}
|
|
|
+POST /api/role/create {"productCode": "crm", "name": "客服", "permsLevel": 30}
|
|
|
+
|
|
|
+# 为角色绑定权限
|
|
|
+POST /api/role/bindPerms {"roleId": 1, "permIds": [1,2,3,4,5,6,7]} # 销售经理:全部
|
|
|
+POST /api/role/bindPerms {"roleId": 2, "permIds": [1,2,5,6]} # 普通销售:查看+创建
|
|
|
+POST /api/role/bindPerms {"roleId": 3, "permIds": [1,5]} # 客服:仅查看
|
|
|
+```
|
|
|
+
|
|
|
+### 第五步:创建用户并配置权限
|
|
|
+
|
|
|
+**场景 A:研发人员张三** — 属于研发部,需要参与 CRM 和 OA 的开发
|
|
|
+
|
|
|
+```bash
|
|
|
+# 创建用户,归属研发部
|
|
|
+POST /api/user/create {"username": "zhangsan", "password": "123456", "nickname": "张三", "deptId": 1}
|
|
|
+# 返回 userId=10
|
|
|
+
|
|
|
+# 将张三添加为 CRM 和 OA 的成员(MEMBER 类型即可)
|
|
|
+POST /api/member/add {"productCode": "crm", "userId": 10, "memberType": "MEMBER"}
|
|
|
+POST /api/member/add {"productCode": "oa", "userId": 10, "memberType": "MEMBER"}
|
|
|
+
|
|
|
+# 因为张三属于研发部(DEV),他登录 CRM 或 OA 时自动拥有全部权限
|
|
|
+# 无需为他分配角色!
|
|
|
+```
|
|
|
+
|
|
|
+**场景 B:一段时间后张三不再负责 OA** — 只需移除成员关系
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/member/remove {"id": <张三在OA中的成员记录ID>}
|
|
|
+# OA 的权限立即收回,CRM 的权限不受影响
|
|
|
+```
|
|
|
+
|
|
|
+**场景 C:市场部李四** — 需要使用 CRM,分配销售角色
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/user/create {"username": "lisi", "password": "123456", "nickname": "李四", "deptId": 2}
|
|
|
+# 返回 userId=11
|
|
|
+
|
|
|
+POST /api/member/add {"productCode": "crm", "userId": 11, "memberType": "MEMBER"}
|
|
|
+POST /api/user/bindRoles {"userId": 11, "roleIds": [2]} # 分配"普通销售"角色
|
|
|
+```
|
|
|
+
|
|
|
+李四登录 CRM 后拥有的权限 = 普通销售角色的权限 = `[customer:list, customer:create, order:list, order:create]`。
|
|
|
+
|
|
|
+**场景 D:给李四额外权限** — 在角色基础上微调
|
|
|
+
|
|
|
+```bash
|
|
|
+# 额外给李四"导出报表"权限,但禁止他"创建订单"
|
|
|
+POST /api/user/setPerms {
|
|
|
+ "userId": 11,
|
|
|
+ "perms": [
|
|
|
+ {"permId": 7, "effect": "ALLOW"},
|
|
|
+ {"permId": 6, "effect": "DENY"}
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+最终李四的权限 = 角色 `[1,2,5,6]` ∪ ALLOW `[7]` - DENY `[6]` = `[customer:list, customer:create, order:list, report:export]`
|
|
|
+
|
|
|
+**场景 E:外部临时协作人员** — 不属于任何部门
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/user/create {"username": "temp_wang", "password": "123456", "nickname": "外包王五"}
|
|
|
+# deptId 不传,不归属任何部门
|
|
|
+
|
|
|
+POST /api/member/add {"productCode": "crm", "userId": 12, "memberType": "MEMBER"}
|
|
|
+POST /api/user/bindRoles {"userId": 12, "roleIds": [3]} # 分配"客服"角色(只读)
|
|
|
+```
|
|
|
+
|
|
|
+### 权限配置决策树
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ Q1{该用户是研发人员?}
|
|
|
+ Q1 -->|是| A1[归属研发部门 deptType=DEV]
|
|
|
+ A1 --> A2["添加为产品成员(MEMBER)即可<br>自动拥有全部权限"]
|
|
|
+ Q1 -->|否| Q2{该用户是产品管理员?}
|
|
|
+ Q2 -->|是| A3["添加为产品成员(ADMIN)<br>自动拥有全部权限"]
|
|
|
+ Q2 -->|否| Q3{需要精细权限控制?}
|
|
|
+ Q3 -->|是| A4["添加为产品成员(MEMBER)"]
|
|
|
+ A4 --> A5[分配角色 + 可选 ALLOW/DENY 覆盖]
|
|
|
+ Q3 -->|否| A6[不添加为成员 = 无权限]
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 操作权限控制(Access Control)
|
|
|
+
|
|
|
+系统内置一套集中式操作权限管控机制,对所有管理类接口实施多维度的访问控制,防止越权操作。
|
|
|
+
|
|
|
+### 安全架构:四层纵深防护
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart LR
|
|
|
+ REQ[HTTP 请求] --> L1["① JWT 中间件<br>解析 token / 验证签名"]
|
|
|
+ L1 --> L2["② UserDetailsLoader<br>加载用户实时状态"]
|
|
|
+ L2 --> L3["③ 冻结检查<br>Status != 1 → 403"]
|
|
|
+ L3 --> L4["④ Logic 层<br>操作权限控制"]
|
|
|
+ L4 --> BIZ[业务逻辑]
|
|
|
+```
|
|
|
+
|
|
|
+| 层级 | 组件 | 职责 |
|
|
|
+|------|------|------|
|
|
|
+| 第一层 | JWT 中间件 | 解析 access token、验证签名、校验 `tokenType` |
|
|
|
+| 第二层 | UserDetailsLoader | 从 Redis 缓存或 DB 加载用户完整信息(含部门、角色、权限) |
|
|
|
+| 第三层 | 用户状态检查 | 冻结账号(`status ≠ 1`)直接返回 403 |
|
|
|
+| 第四层 | access.go | 按接口类型检查超管/产品管理员/部门层级/权限级别 |
|
|
|
+
|
|
|
+### 接口操作权限矩阵
|
|
|
+
|
|
|
+| 接口 | 权限要求 | 额外检查 |
|
|
|
+|------|----------|----------|
|
|
|
+| **产品管理** |||
|
|
|
+| 创建产品 | 仅超级管理员 | — |
|
|
|
+| 更新产品 | 仅超级管理员 | — |
|
|
|
+| **部门管理** |||
|
|
|
+| 创建部门 | 仅超级管理员 | — |
|
|
|
+| 更新部门 | 仅超级管理员 | — |
|
|
|
+| 删除部门 | 仅超级管理员 | 有子部门时拒绝 |
|
|
|
+| **角色管理** |||
|
|
|
+| 创建角色 | 超管 或 产品管理员 | — |
|
|
|
+| 更新角色 | 超管 或 产品管理员 | — |
|
|
|
+| 删除角色 | 超管 或 产品管理员 | 级联删除关联数据 |
|
|
|
+| 绑定角色权限 | 超管 或 产品管理员 | — |
|
|
|
+| **用户管理** |||
|
|
|
+| 创建用户 | 超管 或 产品管理员 | — |
|
|
|
+| 更新用户信息 | 仅本人 或 超管 | — |
|
|
|
+| 冻结/解冻用户 | 通过 `CheckManageAccess` | 不可冻结自己和超管 |
|
|
|
+| 绑定角色 | 通过 `CheckManageAccess` | — |
|
|
|
+| 设置权限覆盖 | 通过 `CheckManageAccess` | — |
|
|
|
+| **成员管理** |||
|
|
|
+| 添加成员 | 通过 `CheckManageAccess` + `CheckMemberTypeAssignment` | 不可分配同级或更高类型 |
|
|
|
+| 更新成员 | 通过 `CheckManageAccess` + `CheckMemberTypeAssignment` | — |
|
|
|
+| 移除成员 | 通过 `CheckManageAccess` | 级联清理角色/权限绑定 |
|
|
|
+| **查询类接口** |||
|
|
|
+| 产品/部门/角色/用户/成员列表与详情 | 已登录即可 | — |
|
|
|
+| 用户信息 (userInfo) | 已登录即可 | 返回当前登录用户自己的信息 |
|
|
|
+| **公开接口** |||
|
|
|
+| 登录 / 刷新令牌 / 同步权限 | 无需鉴权 | 同步权限通过 appKey/appSecret 认证 |
|
|
|
+
|
|
|
+### CheckManageAccess — 用户管理权限检查
|
|
|
+
|
|
|
+`CheckManageAccess` 是管理类操作的核心权限检查函数,执行多维度判定:
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart TD
|
|
|
+ START[CheckManageAccess] --> SA{是超级管理员?}
|
|
|
+ SA -->|是| PASS[✅ 通过]
|
|
|
+ SA -->|否| SELF{操作自己?}
|
|
|
+ SELF -->|是| PASS
|
|
|
+ SELF -->|否| DEPT[部门层级检查]
|
|
|
+ DEPT --> IS_ADMIN{操作者是 ADMIN?}
|
|
|
+ IS_ADMIN -->|是| LEVEL[权限级别检查]
|
|
|
+ IS_ADMIN -->|否| HAS_DEPT{操作者有部门?}
|
|
|
+ HAS_DEPT -->|是| TARGET_DEPT{目标在操作者<br>本部门或子部门?}
|
|
|
+ HAS_DEPT -->|否| DENY1[❌ 未归属部门]
|
|
|
+ TARGET_DEPT -->|是| LEVEL
|
|
|
+ TARGET_DEPT -->|否| DENY2[❌ 跨部门拒绝]
|
|
|
+ LEVEL --> CMP_TYPE{比较 memberType 优先级}
|
|
|
+ CMP_TYPE -->|操作者更高| PASS
|
|
|
+ CMP_TYPE -->|目标更高| DENY3[❌ 权限不足]
|
|
|
+ CMP_TYPE -->|同级| CMP_PERM{比较 permsLevel}
|
|
|
+ CMP_PERM -->|操作者更小| PASS
|
|
|
+ CMP_PERM -->|相等或更大| DENY4[❌ 同级或更低]
|
|
|
+```
|
|
|
+
|
|
|
+**检查维度说明**:
|
|
|
+
|
|
|
+1. **超管豁免**:`SUPER_ADMIN` 不受任何限制
|
|
|
+2. **自我豁免**:操作自己的记录总是允许
|
|
|
+3. **部门层级**:操作者只能管理本部门及下级子部门的用户(`ADMIN` 和超管豁免此检查)
|
|
|
+4. **权限级别**:先比 `memberType` 优先级,同级再比 `permsLevel` 数值
|
|
|
+ - `memberType` 优先级:`SUPER_ADMIN(0) > ADMIN(1) > DEVELOPER(2) > MEMBER(3)`
|
|
|
+ - `permsLevel`:数值越小权限越高(如 10 > 20),无角色默认为 `MaxInt64`(最低)
|
|
|
+
|
|
|
+### UserDetailsLoader — 用户信息缓存
|
|
|
+
|
|
|
+`UserDetailsLoader` 是一个集中式的用户信息加载与缓存组件,为中间件、登录、用户信息查询等多个场景提供统一的数据来源。
|
|
|
+
|
|
|
+**加载的完整数据**:
|
|
|
+
|
|
|
+| 数据来源 | 加载的字段 |
|
|
|
+|----------|-----------|
|
|
|
+| `sys_user` | userId, username, nickname, avatar, email, phone, remark, isSuperAdmin, mustChangePassword, status |
|
|
|
+| `sys_dept` | deptId, deptName, deptPath, deptType |
|
|
|
+| `sys_product` | productCode, productName |
|
|
|
+| `sys_product_member` | memberType |
|
|
|
+| `sys_role` (当前产品) | roles[], minPermsLevel |
|
|
|
+| 计算后的权限 | perms[] (权限 code 集合) |
|
|
|
+
|
|
|
+**缓存策略**:
|
|
|
+
|
|
|
+- **存储**:Redis JSON,key 格式 `{prefix}:ud:{userId}:{productCode}`
|
|
|
+- **TTL**:300 秒(5 分钟)自然过期
|
|
|
+- **主动失效**:所有写操作均触发对应的缓存清除
|
|
|
+
|
|
|
+| 操作 | 失效方法 | 失效范围 |
|
|
|
+|------|----------|----------|
|
|
|
+| 更新用户信息 / 冻结解冻 / 修改密码 | `Clean(userId)` | 该用户所有产品缓存 |
|
|
|
+| 设置用户权限覆盖 / 添加成员 / 更新成员 | `Del(userId, productCode)` | 该用户在指定产品的缓存 |
|
|
|
+| 更新角色 / 删除角色 / 绑定角色权限 | `BatchDel(userIds, productCode)` | 受影响用户在指定产品的缓存 |
|
|
|
+| 更新产品 / 同步权限 | `CleanByProduct(productCode)` | 该产品下所有用户的缓存 |
|
|
|
+| 更新部门 | `Clean(uid)` × N | 该部门下所有用户的缓存 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 快速开始
|
|
|
+
|
|
|
+### 环境要求
|
|
|
+
|
|
|
+- Go 1.21+
|
|
|
+- MySQL 8.0+
|
|
|
+- Redis 6.0+
|
|
|
+- goctl 1.10+
|
|
|
+
|
|
|
+### 部署步骤
|
|
|
+
|
|
|
+```bash
|
|
|
+# 1. 创建数据库并导入表结构
|
|
|
+mysql -u root -p -e "CREATE DATABASE perms_system DEFAULT CHARACTER SET utf8mb4;"
|
|
|
+mysql -u root -p perms_system < perm.sql
|
|
|
+
|
|
|
+# 2. 修改配置(数据库、Redis、JWT 密钥等)
|
|
|
+vim etc/perm-api.yaml
|
|
|
+
|
|
|
+# 3. 安装依赖并启动
|
|
|
+go mod tidy
|
|
|
+go run perm.go
|
|
|
+
|
|
|
+# 服务启动后:
|
|
|
+# - HTTP API: http://localhost:10001
|
|
|
+# - gRPC: localhost:10002
|
|
|
+```
|
|
|
+
|
|
|
+### 初始化超级管理员
|
|
|
+
|
|
|
+首次部署后,需手动在数据库中插入超级管理员账号:
|
|
|
+
|
|
|
+```sql
|
|
|
+INSERT INTO sys_user (username, password, nickname, isSuperAdmin, status, createTime, updateTime)
|
|
|
+VALUES ('superadmin', '$2a$10$这里替换为bcrypt加密后的密码', '超级管理员', 1, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
|
|
|
+```
|
|
|
+
|
|
|
+可使用任意 bcrypt 工具生成密码哈希,或在 Go 中执行:
|
|
|
+
|
|
|
+```go
|
|
|
+hash, _ := bcrypt.GenerateFromPassword([]byte("your-password"), bcrypt.DefaultCost)
|
|
|
+fmt.Println(string(hash))
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 产品接入指南
|
|
|
+
|
|
|
+本节以 "CRM 系统"(编码 `crm`)为例,完整演示接入流程。
|
|
|
+
|
|
|
+### 接入全景图
|
|
|
+
|
|
|
+```mermaid
|
|
|
+sequenceDiagram
|
|
|
+ participant SA as 超级管理员
|
|
|
+ participant PS as 权限系统
|
|
|
+ participant CRM as CRM 后端
|
|
|
+ participant U as 终端用户
|
|
|
+
|
|
|
+ rect rgb(230, 245, 255)
|
|
|
+ Note over SA, PS: 阶段一:注册产品
|
|
|
+ SA ->> PS: POST /api/product/create
|
|
|
+ PS -->> SA: appKey + appSecret + 初始管理员
|
|
|
+ end
|
|
|
+
|
|
|
+ rect rgb(230, 255, 230)
|
|
|
+ Note over CRM, PS: 阶段二:CRM 启动 → 同步权限
|
|
|
+ CRM ->> PS: POST /api/perm/sync (appKey + appSecret + 权限列表)
|
|
|
+ PS -->> CRM: {added, updated, disabled}
|
|
|
+ end
|
|
|
+
|
|
|
+ rect rgb(255, 245, 230)
|
|
|
+ Note over SA, PS: 阶段三:管理员配置角色与用户
|
|
|
+ SA ->> PS: 创建角色、绑定权限、创建用户、分配角色
|
|
|
+ end
|
|
|
+
|
|
|
+ rect rgb(245, 230, 255)
|
|
|
+ Note over U, CRM: 阶段四:用户登录与鉴权
|
|
|
+ U ->> CRM: 提交用户名密码
|
|
|
+ CRM ->> PS: POST /api/auth/login 或 gRPC Login
|
|
|
+ PS -->> CRM: accessToken + refreshToken + perms[]
|
|
|
+ CRM -->> U: 返回 token
|
|
|
+ U ->> CRM: 业务请求 + Bearer token
|
|
|
+ CRM ->> CRM: 本地 JWT 解析,检查 perms[]
|
|
|
+ CRM -->> U: 返回业务数据
|
|
|
+ end
|
|
|
+```
|
|
|
+
|
|
|
+### 阶段一:注册产品
|
|
|
+
|
|
|
+超级管理员登录后创建产品:
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/product/create
|
|
|
+{"code": "crm", "name": "CRM 系统", "remark": "客户关系管理"}
|
|
|
+```
|
|
|
+
|
|
|
+响应中包含 `appKey`、`appSecret`、`adminUser`、`adminPassword`,**请立即保存,后续不再展示**。
|
|
|
+
|
|
|
+### 阶段二:产品启动时同步权限
|
|
|
+
|
|
|
+CRM 后端在启动阶段上报全部权限列表。**此接口无需 JWT,通过 appKey + appSecret 认证**。
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/perm/sync
|
|
|
+{
|
|
|
+ "appKey": "a1b2c3d4e5f6...",
|
|
|
+ "appSecret": "x9y8z7w6v5u4...",
|
|
|
+ "perms": [
|
|
|
+ {"code": "customer:list", "name": "查看客户列表"},
|
|
|
+ {"code": "customer:create", "name": "创建客户"},
|
|
|
+ {"code": "customer:update", "name": "编辑客户"},
|
|
|
+ {"code": "customer:delete", "name": "删除客户"},
|
|
|
+ {"code": "order:list", "name": "查看订单列表"},
|
|
|
+ {"code": "order:create", "name": "创建订单"},
|
|
|
+ {"code": "report:export", "name": "导出报表"}
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**Go 代码示例(放在 main 启动流程中):**
|
|
|
+
|
|
|
+```go
|
|
|
+var permsList = []map[string]string{
|
|
|
+ {"code": "customer:list", "name": "查看客户列表"},
|
|
|
+ {"code": "customer:create", "name": "创建客户"},
|
|
|
+ // ...
|
|
|
+}
|
|
|
+
|
|
|
+func syncPermsOnStartup(permSystemURL, appKey, appSecret string) {
|
|
|
+ body, _ := json.Marshal(map[string]interface{}{
|
|
|
+ "appKey": appKey, "appSecret": appSecret, "perms": permsList,
|
|
|
+ })
|
|
|
+ resp, err := http.Post(permSystemURL+"/api/perm/sync", "application/json", bytes.NewReader(body))
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("同步权限失败: %v", err)
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+ log.Println("权限同步完成")
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 阶段三:管理员配置角色与用户
|
|
|
+
|
|
|
+详见上文「实际业务配置指南」。
|
|
|
+
|
|
|
+### 阶段四:用户登录与鉴权
|
|
|
+
|
|
|
+#### 1. 用户登录
|
|
|
+
|
|
|
+**方式 A:HTTP API**
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/auth/login
|
|
|
+{"username": "zhangsan", "password": "123456", "productCode": "crm"}
|
|
|
+```
|
|
|
+
|
|
|
+**方式 B:gRPC SDK(推荐 Go 项目)**
|
|
|
+
|
|
|
+```go
|
|
|
+import "perms-system-server/permclient"
|
|
|
+
|
|
|
+client, err := permclient.NewPermClient("perm-system:10002")
|
|
|
+resp, err := client.Login(ctx, "crm", "zhangsan", "123456")
|
|
|
+// resp.AccessToken, resp.RefreshToken, resp.Perms
|
|
|
+```
|
|
|
+
|
|
|
+#### 2. 本地 JWT 验证(推荐)
|
|
|
+
|
|
|
+产品后端配置与权限系统相同的 `AccessSecret`,本地解析 JWT 即可获取用户信息和权限列表,无需每次回调。
|
|
|
+
|
|
|
+```go
|
|
|
+type Claims struct {
|
|
|
+ TokenType string `json:"tokenType"`
|
|
|
+ UserId int64 `json:"userId"`
|
|
|
+ Username string `json:"username"`
|
|
|
+ ProductCode string `json:"productCode"`
|
|
|
+ MemberType string `json:"memberType"`
|
|
|
+ Perms []string `json:"perms"`
|
|
|
+ jwt.RegisteredClaims
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+> `tokenType` 字段区分 `"access"` 和 `"refresh"`,验证时应检查 `tokenType == "access"`。
|
|
|
+
|
|
|
+#### 3. 业务接口中检查权限
|
|
|
+
|
|
|
+```go
|
|
|
+func CreateOrderHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
+ claims := middleware.GetClaims(r.Context())
|
|
|
+ if !hasPermission(claims.Perms, "order:create") {
|
|
|
+ // 返回 403
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 执行业务逻辑...
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 4. Token 续期
|
|
|
+
|
|
|
+```bash
|
|
|
+POST /api/auth/refreshToken
|
|
|
+Authorization: Bearer eyJhbGciOi... # refreshToken 放在 header 中
|
|
|
+Content-Type: application/json
|
|
|
+
|
|
|
+{"productCode": "crm"}
|
|
|
+```
|
|
|
+
|
|
|
+> `refreshToken` 有效期默认 7 天,刷新时返回原始 refreshToken(不重新签发),过期后必须重新登录。这确保了 token 有固定的生命周期。
|
|
|
+
|
|
|
+### 接入检查清单
|
|
|
+
|
|
|
+- [ ] 已获取产品的 `appKey` 和 `appSecret`
|
|
|
+- [ ] 产品启动时调用 `/api/perm/sync` 同步了全部权限
|
|
|
+- [ ] 后端配置了与权限系统相同的 `AccessSecret`
|
|
|
+- [ ] 登录接口正确调用权限系统并返回 token
|
|
|
+- [ ] JWT 鉴权中间件已加入受保护路由,并验证 `tokenType == "access"`
|
|
|
+- [ ] 业务接口中根据 `claims.Perms` 做了权限校验
|
|
|
+- [ ] 前端在 `accessToken` 过期时能自动调用 `refreshToken` 续期
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## HTTP API 接口文档
|
|
|
+
|
|
|
+所有接口基础路径为 `/api`,请求方式统一为 **POST**,参数通过 **JSON Body** 传递。需鉴权接口须携带 `Authorization: Bearer {accessToken}`。
|
|
|
+
|
|
|
+### 统一响应格式
|
|
|
+
|
|
|
+所有 HTTP 接口均返回统一的 JSON 结构:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "code": 0,
|
|
|
+ "msg": "ok",
|
|
|
+ "data": { ... }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| `code` | int | 业务状态码,`0` 表示成功,非零表示失败 |
|
|
|
+| `msg` | string | 状态描述 |
|
|
|
+| `data` | object/null | 业务数据,失败时无此字段 |
|
|
|
+
|
|
|
+### 错误码一览
|
|
|
+
|
|
|
+| Code | 语义 | 典型场景 |
|
|
|
+|------|------|----------|
|
|
|
+| `0` | 成功 | 所有正常响应 |
|
|
|
+| `400` | 请求不合法 | 参数缺失/格式错误、原密码错误、存在子部门无法删除等 |
|
|
|
+| `401` | 未认证 | 未登录、token 无效/过期/类型错误、用户名密码错误 |
|
|
|
+| `403` | 无权限 | 账号已冻结、产品已禁用、非超管操作产品/部门、非管理员操作角色/用户、跨部门/越级管理 |
|
|
|
+| `404` | 资源不存在 | 用户/产品/角色/部门/成员不存在 |
|
|
|
+| `409` | 资源冲突 | 用户名已存在、产品编码已存在、角色名重复、成员重复添加 |
|
|
|
+| `500` | 系统错误 | 数据库异常等未预期的内部错误(不暴露具体信息) |
|
|
|
+
|
|
|
+### 公开接口(无需鉴权)
|
|
|
+
|
|
|
+#### POST /api/auth/login — 用户登录
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| username | string | 是 | 登录名 |
|
|
|
+| password | string | 是 | 密码 |
|
|
|
+| productCode | string | 否 | 产品编码,传入则返回该产品的权限列表 |
|
|
|
+
|
|
|
+**响应 data:**
|
|
|
+
|
|
|
+| 字段 | 类型 | 说明 |
|
|
|
+|------|------|------|
|
|
|
+| accessToken | string | 访问令牌 |
|
|
|
+| refreshToken | string | 刷新令牌 |
|
|
|
+| expires | int64 | accessToken 过期时间(Unix 时间戳,秒) |
|
|
|
+| userInfo | object | 用户信息(含 perms 权限码数组) |
|
|
|
+
|
|
|
+#### POST /api/auth/refreshToken — 刷新令牌
|
|
|
+
|
|
|
+通过 `Authorization: Bearer {refreshToken}` 请求头传入 refresh token。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| Authorization | header | 是 | `Bearer {refreshToken}` |
|
|
|
+| productCode | string | 否 | 切换产品上下文时传入(Body) |
|
|
|
+
|
|
|
+**响应 data:** 与登录接口相同。注意返回的 `refreshToken` 是原始值(不重新签发),refresh token 有固定有效期,过期后需重新登录。
|
|
|
+
|
|
|
+#### POST /api/perm/sync — 同步产品权限
|
|
|
+
|
|
|
+通过 appKey/appSecret 认证,产品后端上报全量权限列表。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| appKey | string | 是 | 产品接入密钥 |
|
|
|
+| appSecret | string | 是 | 产品签名密钥 |
|
|
|
+| perms | array | 是 | 权限列表 |
|
|
|
+| perms[].code | string | 是 | 权限码(如 `user:create`) |
|
|
|
+| perms[].name | string | 是 | 权限名 |
|
|
|
+| perms[].remark | string | 否 | 备注 |
|
|
|
+
|
|
|
+**响应 data:** `{"added": 3, "updated": 1, "disabled": 0}`
|
|
|
+
|
|
|
+### 认证接口(需鉴权)
|
|
|
+
|
|
|
+#### POST /api/auth/userInfo — 获取当前用户信息
|
|
|
+
|
|
|
+无请求参数。**响应 data:** `UserInfo` 对象。
|
|
|
+
|
|
|
+#### POST /api/auth/changePassword — 修改密码
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| oldPassword | string | 是 | 原密码 |
|
|
|
+| newPassword | string | 是 | 新密码(6-72 字符,不能与旧密码相同) |
|
|
|
+
|
|
|
+### 产品管理(仅超级管理员)
|
|
|
+
|
|
|
+#### POST /api/product/create — 创建产品
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| code | string | 是 | 产品编码(全局唯一) |
|
|
|
+| name | string | 是 | 产品名称 |
|
|
|
+| remark | string | 否 | 备注 |
|
|
|
+
|
|
|
+**响应 data:** `{"id", "code", "appKey", "appSecret", "adminUser", "adminPassword"}`
|
|
|
+
|
|
|
+#### POST /api/product/update — 更新产品
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 产品 ID |
|
|
|
+| name | string | 是 | 产品名称 |
|
|
|
+| remark | string | 否 | 备注 |
|
|
|
+| status | int64 | 否 | 1=启用 2=禁用 |
|
|
|
+
|
|
|
+#### POST /api/product/list — 产品列表
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| page | int64 | 否 | 页码,默认 1 |
|
|
|
+| pageSize | int64 | 否 | 每页条数,默认 20,上限 100 |
|
|
|
+
|
|
|
+**响应 data:** `{"total": N, "list": [ProductItem...]}`
|
|
|
+
|
|
|
+#### POST /api/product/detail — 产品详情
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 产品 ID |
|
|
|
+
|
|
|
+### 部门管理(仅超级管理员)
|
|
|
+
|
|
|
+#### POST /api/dept/create — 创建部门
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| parentId | int64 | 是 | 父部门 ID,0 表示顶级部门 |
|
|
|
+| name | string | 是 | 部门名称 |
|
|
|
+| sort | int64 | 否 | 排序值 |
|
|
|
+| deptType | string | 否 | `NORMAL`(默认)或 `DEV`(研发部门) |
|
|
|
+| remark | string | 否 | 备注 |
|
|
|
+
|
|
|
+**响应 data:** `{"id": 1}`
|
|
|
+
|
|
|
+#### POST /api/dept/update — 更新部门
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 部门 ID |
|
|
|
+| name | string | 是 | 名称 |
|
|
|
+| sort | int64 | 否 | 排序值 |
|
|
|
+| deptType | string | 否 | `NORMAL` 或 `DEV` |
|
|
|
+| remark | string | 否 | 备注 |
|
|
|
+| status | int64 | 否 | 状态 |
|
|
|
+
|
|
|
+#### POST /api/dept/delete — 删除部门
|
|
|
+
|
|
|
+存在子部门时无法删除。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 部门 ID |
|
|
|
+
|
|
|
+#### POST /api/dept/tree — 部门树
|
|
|
+
|
|
|
+无请求参数。返回完整的部门树形结构(含 `children` 嵌套和 `deptType`)。
|
|
|
+
|
|
|
+### 权限管理
|
|
|
+
|
|
|
+#### POST /api/perm/list — 权限列表
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| productCode | string | 是 | 产品编码 |
|
|
|
+| page | int64 | 否 | 页码 |
|
|
|
+| pageSize | int64 | 否 | 每页条数 |
|
|
|
+
|
|
|
+**响应 data:** `{"total": N, "list": [PermItem...]}`
|
|
|
+
|
|
|
+### 角色管理(超级管理员 或 产品管理员)
|
|
|
+
|
|
|
+#### POST /api/role/create — 创建角色
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| productCode | string | 是 | 所属产品编码 |
|
|
|
+| name | string | 是 | 角色名(产品内唯一) |
|
|
|
+| remark | string | 否 | 备注 |
|
|
|
+| permsLevel | int64 | 是 | 权限等级 |
|
|
|
+
|
|
|
+**响应 data:** `{"id": 1}`
|
|
|
+
|
|
|
+#### POST /api/role/update — 更新角色
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 角色 ID |
|
|
|
+| name | string | 是 | 角色名 |
|
|
|
+| remark | string | 否 | 备注 |
|
|
|
+| permsLevel | int64 | 是 | 权限等级 |
|
|
|
+| status | int64 | 否 | 状态 |
|
|
|
+
|
|
|
+#### POST /api/role/delete — 删除角色
|
|
|
+
|
|
|
+级联删除角色关联的权限绑定和用户绑定。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 角色 ID |
|
|
|
+
|
|
|
+#### POST /api/role/list — 角色列表
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| productCode | string | 是 | 产品编码 |
|
|
|
+| page | int64 | 否 | 页码 |
|
|
|
+| pageSize | int64 | 否 | 每页条数 |
|
|
|
+
|
|
|
+#### POST /api/role/detail — 角色详情
|
|
|
+
|
|
|
+返回角色信息及绑定的权限 ID 列表(`permIds`)。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 角色 ID |
|
|
|
+
|
|
|
+#### POST /api/role/bindPerms — 绑定角色权限
|
|
|
+
|
|
|
+全量替换该角色的权限。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| roleId | int64 | 是 | 角色 ID |
|
|
|
+| permIds | []int64 | 是 | 权限 ID 列表(空数组清空绑定) |
|
|
|
+
|
|
|
+### 用户管理
|
|
|
+
|
|
|
+#### POST /api/user/create — 创建用户(超级管理员 或 产品管理员)
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| username | string | 是 | 登录名(唯一) |
|
|
|
+| password | string | 是 | 密码(6-72 字符) |
|
|
|
+| nickname | string | 否 | 昵称 |
|
|
|
+| email | string | 否 | 邮箱(需合法格式) |
|
|
|
+| phone | string | 否 | 手机号(7-15 位数字,可含 `+` 前缀) |
|
|
|
+| remark | string | 否 | 备注 |
|
|
|
+| deptId | int64 | 否 | 部门 ID |
|
|
|
+
|
|
|
+**响应 data:** `{"id": 1}`
|
|
|
+
|
|
|
+#### POST /api/user/update — 更新用户(仅本人 或 超级管理员)
|
|
|
+
|
|
|
+支持字段清空:传 `""` 清空字符串字段,传 `0` 清空 deptId,不传字段则不更新。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 用户 ID |
|
|
|
+| nickname | *string | 否 | 昵称 |
|
|
|
+| email | *string | 否 | 邮箱 |
|
|
|
+| phone | *string | 否 | 手机号 |
|
|
|
+| remark | *string | 否 | 备注 |
|
|
|
+| deptId | *int64 | 否 | 部门 ID(传 0 取消部门) |
|
|
|
+| status | int64 | 否 | 状态 |
|
|
|
+
|
|
|
+#### POST /api/user/list — 用户列表
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| productCode | string | 否 | 产品编码(传入则附带成员类型) |
|
|
|
+| page | int64 | 否 | 页码 |
|
|
|
+| pageSize | int64 | 否 | 每页条数 |
|
|
|
+
|
|
|
+#### POST /api/user/detail — 用户详情
|
|
|
+
|
|
|
+返回用户信息及绑定的角色 ID 列表(`roleIds`)。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 用户 ID |
|
|
|
+
|
|
|
+#### POST /api/user/bindRoles — 绑定用户角色(需管理权限)
|
|
|
+
|
|
|
+需通过 `CheckManageAccess` 权限检查。全量替换该用户的角色。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| userId | int64 | 是 | 用户 ID |
|
|
|
+| roleIds | []int64 | 是 | 角色 ID 列表 |
|
|
|
+
|
|
|
+#### POST /api/user/setPerms — 设置用户权限覆盖(需管理权限)
|
|
|
+
|
|
|
+需通过 `CheckManageAccess` 权限检查。全量替换用户级别的 ALLOW/DENY 权限覆盖。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| userId | int64 | 是 | 用户 ID |
|
|
|
+| perms | array | 是 | 权限覆盖列表 |
|
|
|
+| perms[].permId | int64 | 是 | 权限 ID |
|
|
|
+| perms[].effect | string | 是 | `ALLOW` 或 `DENY` |
|
|
|
+
|
|
|
+#### POST /api/user/updateStatus — 更新用户状态(需管理权限)
|
|
|
+
|
|
|
+需通过 `CheckManageAccess` 权限检查。不允许冻结自己和超级管理员。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 用户 ID |
|
|
|
+| status | int64 | 是 | 1=正常 2=冻结 |
|
|
|
+
|
|
|
+### 产品成员管理(需管理权限)
|
|
|
+
|
|
|
+#### POST /api/member/add — 添加产品成员
|
|
|
+
|
|
|
+需通过 `CheckManageAccess` + `CheckMemberTypeAssignment` 权限检查。不可分配与自己同级或更高级的成员类型。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| productCode | string | 是 | 产品编码 |
|
|
|
+| userId | int64 | 是 | 用户 ID |
|
|
|
+| memberType | string | 是 | `ADMIN` / `DEVELOPER` / `MEMBER` |
|
|
|
+
|
|
|
+**响应 data:** `{"id": 1}`
|
|
|
+
|
|
|
+#### POST /api/member/update — 更新成员
|
|
|
+
|
|
|
+需通过 `CheckManageAccess` + `CheckMemberTypeAssignment` 权限检查。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 成员记录 ID |
|
|
|
+| memberType | string | 是 | 成员类型 |
|
|
|
+| status | int64 | 否 | 状态 |
|
|
|
+
|
|
|
+#### POST /api/member/remove — 移除成员
|
|
|
+
|
|
|
+需通过 `CheckManageAccess` 权限检查。级联清理该成员在该产品下的角色绑定和权限覆盖。
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| id | int64 | 是 | 成员记录 ID |
|
|
|
+
|
|
|
+#### POST /api/member/list — 成员列表
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| productCode | string | 是 | 产品编码 |
|
|
|
+| page | int64 | 否 | 页码 |
|
|
|
+| pageSize | int64 | 否 | 每页条数 |
|
|
|
+
|
|
|
+**响应 data:** `{"total": N, "list": [MemberItem...]}`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## gRPC 接口文档
|
|
|
+
|
|
|
+gRPC 服务定义见 `pb/perm.proto`,默认监听 `:10002`。
|
|
|
+
|
|
|
+| 方法 | 说明 | 使用场景 |
|
|
|
+|------|------|----------|
|
|
|
+| `SyncPermissions` | 同步产品权限列表 | 产品启动时调用 |
|
|
|
+| `Login` | 用户登录 | 产品后端代理用户登录 |
|
|
|
+| `RefreshToken` | 刷新令牌 | accessToken 过期续期 |
|
|
|
+| `VerifyToken` | 验证令牌 | 产品后端验证用户 token(可选,推荐本地 JWT 验证) |
|
|
|
+| `GetUserPerms` | 获取用户权限 | 实时查询用户最新权限 |
|
|
|
+
|
|
|
+所有 gRPC 错误使用标准 `status.Error(codes.Xxx, msg)` 格式。
|
|
|
+
|
|
|
+Go 项目可直接引用 `permclient` 包:
|
|
|
+
|
|
|
+```go
|
|
|
+import "perms-system-server/permclient"
|
|
|
+
|
|
|
+client, err := permclient.NewPermClient("perm-system:10002")
|
|
|
+
|
|
|
+resp, err := client.SyncPermissions(ctx, &pb.SyncPermissionsReq{...})
|
|
|
+resp, err := client.Login(ctx, &pb.LoginReq{...})
|
|
|
+resp, err := client.VerifyToken(ctx, &pb.VerifyTokenReq{AccessToken: token})
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 项目结构
|
|
|
+
|
|
|
+```
|
|
|
+server/
|
|
|
+├── perm.go # 入口(同时启动 HTTP + gRPC)
|
|
|
+├── perm.api # HTTP API 定义(go-zero 标准)
|
|
|
+├── perm.sql # 数据库 DDL
|
|
|
+├── gen-api.sh # API 代码生成脚本
|
|
|
+├── gen-model.sh # Model 代码生成脚本
|
|
|
+├── run-test.sh # 测试运行脚本
|
|
|
+├── test-design.md # 测试用例设计文档(433 TC)
|
|
|
+├── test-report.md # 测试报告
|
|
|
+├── etc/
|
|
|
+│ └── perm-api.yaml # 服务配置
|
|
|
+├── pb/
|
|
|
+│ ├── perm.proto # gRPC 接口定义
|
|
|
+│ ├── perm.pb.go # protoc 生成
|
|
|
+│ └── perm_grpc.pb.go # protoc 生成
|
|
|
+├── permclient/
|
|
|
+│ └── permclient.go # gRPC 客户端 SDK
|
|
|
+├── cli/goctl/ # 自定义 goctl 模板
|
|
|
+│ ├── api/handler.tpl # Handler 模板(含参数校验 → 400 处理)
|
|
|
+│ └── model/ # Model 模板(含 *WithTx 事务查询方法)
|
|
|
+└── internal/
|
|
|
+ ├── config/config.go # 配置结构体
|
|
|
+ ├── consts/consts.go # 全局常量(Status/MemberType/DeptType/TokenType 等)
|
|
|
+ ├── response/response.go # 统一响应 + 错误码
|
|
|
+ ├── util/ # 工具函数(分页、校验、JWT)
|
|
|
+ ├── loaders/
|
|
|
+ │ └── userDetailsLoader.go # 用户详情加载器(Redis 缓存 + DB 回源)
|
|
|
+ ├── middleware/
|
|
|
+ │ └── jwtauthMiddleware.go # JWT 鉴权中间件(token 验证 + 状态检查 + UserDetails 注入)
|
|
|
+ ├── svc/serviceContext.go # 依赖注入容器
|
|
|
+ ├── server/permserver.go # gRPC 服务实现
|
|
|
+ ├── types/types.go # 请求/响应结构体(goctl 生成)
|
|
|
+ ├── handler/ # HTTP handler(按模块分组,goctl 生成)
|
|
|
+ │ ├── routes.go
|
|
|
+ │ ├── pub/ # 公开接口
|
|
|
+ │ ├── auth/ # 认证接口
|
|
|
+ │ ├── product/ # 产品管理
|
|
|
+ │ ├── dept/ # 部门管理
|
|
|
+ │ ├── perm/ # 权限管理
|
|
|
+ │ ├── role/ # 角色管理
|
|
|
+ │ ├── user/ # 用户管理
|
|
|
+ │ └── member/ # 产品成员
|
|
|
+ ├── logic/ # 业务逻辑层(按模块分组)
|
|
|
+ │ ├── auth/
|
|
|
+ │ │ ├── jwt.go # JWT 生成/解析(含 tokenType 区分)
|
|
|
+ │ │ ├── perms.go # 权限计算核心逻辑
|
|
|
+ │ │ └── access.go # 集中式操作权限控制(超管/管理员/部门/级别检查)
|
|
|
+ │ ├── pub/ # 登录、刷新、同步
|
|
|
+ │ ├── product/ # 产品 CRUD
|
|
|
+ │ ├── dept/ # 部门 CRUD + 树
|
|
|
+ │ ├── perm/ # 权限列表
|
|
|
+ │ ├── role/ # 角色 CRUD + 绑定权限 + 级联删除
|
|
|
+ │ ├── user/ # 用户 CRUD + 绑定角色/权限
|
|
|
+ │ └── member/ # 成员管理 + 级联清理
|
|
|
+ └── model/ # 数据模型层(每张表独立目录)
|
|
|
+ ├── models.go # Models 聚合结构体(统一初始化所有 Model 实例)
|
|
|
+ ├── product/ # sys_product
|
|
|
+ ├── dept/ # sys_dept
|
|
|
+ ├── perm/ # sys_perm
|
|
|
+ ├── role/ # sys_role
|
|
|
+ ├── roleperm/ # sys_role_perm
|
|
|
+ ├── user/ # sys_user
|
|
|
+ ├── userperm/ # sys_user_perm
|
|
|
+ ├── userrole/ # sys_user_role
|
|
|
+ └── productmember/ # sys_product_member
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 配置说明
|
|
|
+
|
|
|
+配置文件位于 `etc/perm-api.yaml`:
|
|
|
+
|
|
|
+| 配置项 | 说明 | 默认值 |
|
|
|
+|--------|------|--------|
|
|
|
+| `Host` / `Port` | HTTP 监听地址 | `0.0.0.0:10001` |
|
|
|
+| `RpcServerConf.ListenOn` | gRPC 监听地址 | `0.0.0.0:10002` |
|
|
|
+| `MySQL.DataSource` | MySQL 连接串 | — |
|
|
|
+| `CacheRedis[].Host` | Redis 地址 | `127.0.0.1:6379` |
|
|
|
+| `Auth.AccessSecret` | JWT accessToken 签名密钥 | — |
|
|
|
+| `Auth.AccessExpire` | accessToken 有效期(秒) | `7200`(2h) |
|
|
|
+| `Auth.RefreshSecret` | JWT refreshToken 签名密钥 | — |
|
|
|
+| `Auth.RefreshExpire` | refreshToken 有效期(秒) | `604800`(7d) |
|
|
|
+
|
|
|
+> **生产环境部署前,务必修改 `AccessSecret` 和 `RefreshSecret` 为安全的随机字符串,并确保产品后端的本地验证密钥与 `AccessSecret` 一致。**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 测试
|
|
|
+
|
|
|
+### 测试概览
|
|
|
+
|
|
|
+| 指标 | 数值 |
|
|
|
+|------|------|
|
|
|
+| 测试用例总数 | 499 |
|
|
|
+| 已覆盖 TC | 498(99.8%) |
|
|
|
+| 测试函数 | 662 |
|
|
|
+| 测试子用例 | 744 |
|
|
|
+| 测试包 | 23 |
|
|
|
+| 通过率 | 99.7% |
|
|
|
+
|
|
|
+测试覆盖范围:
|
|
|
+
|
|
|
+- **REST API 测试** — 全部 HTTP 接口的正常路径、参数边界、错误路径
|
|
|
+- **gRPC 接口测试** — 全部 5 个 RPC 方法的正常/异常场景
|
|
|
+- **中间件测试** — JWT 鉴权、冻结账号拦截、UserDetails 注入、统一响应
|
|
|
+- **访问控制测试** — access.go 全部函数的正面/负面测试
|
|
|
+- **UserDetailsLoader 测试** — 缓存命中/miss、Clean/Del/BatchDel/CleanByProduct、权限计算
|
|
|
+- **Logic 层访问控制负面测试** — 非超管/非管理员操作被拒绝
|
|
|
+- **Model 层测试** — 9 个 Model 的 CRUD、事务、批量操作、唯一索引方法、自定义方法
|
|
|
+- **Logic 层测试** — 业务逻辑单元测试(含 mock 测试)
|
|
|
+- **工具函数测试** — 分页、邮箱/手机校验、JWT 生成解析
|
|
|
+
|
|
|
+### 测试文档
|
|
|
+
|
|
|
+| 文件 | 说明 |
|
|
|
+|------|------|
|
|
|
+| `test-design.md` | 测试用例设计文档(499 个 TC,含测试场景、输入数据、预期结果) |
|
|
|
+| `test-report.md` | 测试执行报告(含各包耗时、TC 明细、源码审计结果) |
|
|
|
+
|
|
|
+### 运行测试
|
|
|
+
|
|
|
+项目提供 `run-test.sh` 脚本,自动完成以下流程:
|
|
|
+
|
|
|
+1. **连通性检查** — 验证 MySQL 和 Redis 是否可达,不可达则提前终止
|
|
|
+2. **编译检查** — 先执行 `go build ./...`,编译失败则不跑测试
|
|
|
+3. **执行测试** — 运行 `go test`,结束后汇总通过/失败的包数和耗时
|
|
|
+
|
|
|
+```bash
|
|
|
+# 运行全部测试
|
|
|
+./run-test.sh
|
|
|
+
|
|
|
+# 运行指定包的测试
|
|
|
+./run-test.sh ./internal/logic/auth/...
|
|
|
+./run-test.sh ./internal/model/...
|
|
|
+
|
|
|
+# 查看帮助
|
|
|
+./run-test.sh -h
|
|
|
+```
|
|
|
+
|
|
|
+**环境变量**:
|
|
|
+
|
|
|
+| 变量 | 默认值 | 说明 |
|
|
|
+|------|--------|------|
|
|
|
+| `TEST_VERBOSE` | 空(关闭) | 设为 `1` 开启详细输出(`-v`) |
|
|
|
+| `TEST_RUN` | 空(全部) | 只运行匹配的测试函数名 |
|
|
|
+| `TEST_TIMEOUT` | `600s` | 单个包的超时时间 |
|
|
|
+| `TEST_COUNT` | `1` | 每个测试运行次数 |
|
|
|
+
|
|
|
+```bash
|
|
|
+# 组合使用示例
|
|
|
+TEST_VERBOSE=1 TEST_RUN="TestLogin" ./run-test.sh ./internal/logic/pub/...
|
|
|
+```
|
|
|
+
|
|
|
+### 测试文件组织
|
|
|
+
|
|
|
+测试文件遵循 Go 标准命名,与被测文件同目录:
|
|
|
+
|
|
|
+```
|
|
|
+internal/logic/auth/
|
|
|
+├── jwt.go # 源码
|
|
|
+├── jwt_test.go # 测试
|
|
|
+├── perms.go # 源码
|
|
|
+├── perms_test.go # 测试
|
|
|
+└── perms_mock_test.go # Mock 测试
|
|
|
+
|
|
|
+internal/model/product/
|
|
|
+├── sysProductModel.go # 自定义 Model
|
|
|
+├── sysProductModel_gen.go # 生成的 Model
|
|
|
+└── sysProductModel_test.go # 测试
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 开发指南
|
|
|
+
|
|
|
+本项目严格遵循 go-zero 标准开发流程:
|
|
|
+
|
|
|
+```mermaid
|
|
|
+flowchart LR
|
|
|
+ A[编辑 perm.api] --> B["./gen-api.sh"]
|
|
|
+ B --> C[生成 handler + types + routes]
|
|
|
+ C --> D[在 logic 中填充业务逻辑]
|
|
|
+ D --> E[go build 验证]
|
|
|
+
|
|
|
+ F[编辑 perm.sql] --> G["./gen-model.sh"]
|
|
|
+ G --> H[生成 model 到对应子目录]
|
|
|
+ H --> I[在 sysXxxModel.go 中扩展自定义查询]
|
|
|
+```
|
|
|
+
|
|
|
+### 新增 HTTP 接口
|
|
|
+
|
|
|
+```bash
|
|
|
+# 1. 在 perm.api 中添加类型和路由定义
|
|
|
+# 2. 重新生成(已有 logic 文件不会被覆盖)
|
|
|
+./gen-api.sh
|
|
|
+# 3. 在生成的 logic 文件中填写业务逻辑
|
|
|
+```
|
|
|
+
|
|
|
+### 新增数据表 Model
|
|
|
+
|
|
|
+```bash
|
|
|
+# 1. 在 perm.sql 中添加建表语句
|
|
|
+# 2. 生成 model(使用自定义模板,包含 *WithTx 事务查询方法)
|
|
|
+./gen-model.sh
|
|
|
+# 3. 在生成的 sysXxxModel.go 中添加自定义查询方法
|
|
|
+```
|
|
|
+
|
|
|
+### 自定义 goctl 模板
|
|
|
+
|
|
|
+项目使用自定义模板(`cli/goctl/`),主要增强:
|
|
|
+
|
|
|
+- **Handler 模板** — 参数解析错误自动包装为 `400 Bad Request`(而非 500)
|
|
|
+- **Model 模板** — 所有 `FindOne` 和 `FindOneByXxx` 方法均提供 `*WithTx` 事务变体,确保事务内查询使用 session 连接
|
|
|
+
|
|
|
+### 代码规范
|
|
|
+
|
|
|
+- 所有魔数已替换为 `internal/consts/consts.go` 中的命名常量
|
|
|
+- 自定义 SQL 中的 MySQL 关键字统一使用大写
|
|
|
+- 多步写入操作均在 `TransactCtx` 中,事务内查询使用 `*WithTx` 方法
|
|
|
+- 删除角色/移除成员时有完整的级联清理
|
|
|
+- 列表接口统一 `NormalizePage`,`pageSize` 上限 100
|
|
|
+- 输入校验涵盖邮箱/手机格式、密码长度、状态值合法性、实体存在性
|
|
|
+- JWT token 通过 `tokenType` 字段区分 `access`/`refresh`,防止混用
|
|
|
+- 管理类接口统一通过 `access.go` 进行操作权限控制
|
|
|
+- 用户完整信息通过 `UserDetailsLoader` 集中加载,Redis 缓存 + 主动失效
|
|
|
+- 所有写操作均触发对应的缓存失效(`Clean`/`Del`/`BatchDel`/`CleanByProduct`)
|
|
|
+- `refreshToken` 不重新签发,具有固定有效期
|