1147 lines
32 KiB
Markdown
1147 lines
32 KiB
Markdown
# RBAC 权限管理完整指南 v3.3
|
||
|
||
## 📋 文档说明
|
||
|
||
本文档是 v3.3 版本的 RBAC(基于角色的访问控制)和用户管理的**完整指南**,包含:
|
||
- ✅ 核心业务逻辑的详细说明
|
||
- ✅ 所有接口的完整定义和请求参数
|
||
- ✅ 实际数据库数据示例
|
||
- ✅ 权限控制和地区隔离规则
|
||
- ✅ 完整的前后端对接示例
|
||
|
||
**适用对象**:后端开发、前端开发、产品经理、测试人员
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
- [1. 核心业务逻辑](#1-核心业务逻辑)
|
||
- [1.1 设计理念](#11-设计理念)
|
||
- [1.2 数据模型](#12-数据模型)
|
||
- [1.3 权限控制规则](#13-权限控制规则)
|
||
- [2. 完整接口文档](#2-完整接口文档)
|
||
- [2.1 角色管理接口](#21-角色管理接口)
|
||
- [2.2 用户管理接口](#22-用户管理接口)
|
||
- [2.3 角色分配接口](#23-角色分配接口)
|
||
- [3. 业务场景示例](#3-业务场景示例)
|
||
- [4. 数据库实际数据](#4-数据库实际数据)
|
||
- [5. 前端对接指南](#5-前端对接指南)
|
||
|
||
---
|
||
|
||
## 1. 核心业务逻辑
|
||
|
||
### 1.1 设计理念
|
||
|
||
#### 三层设计架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 1. 角色层(全局统一) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ - 角色是全局配置,不区分地区 │
|
||
│ - 所有用户都能看到所有角色 │
|
||
│ - 角色包括:provincial_admin, admin, common │
|
||
└─────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 2. 用户层(地区隔离) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ - 用户按地区(area字段)隔离管理 │
|
||
│ - 省级管理员可以操作所有地区用户 │
|
||
│ - 市级管理员只能操作同地区用户 │
|
||
└─────────────────────────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ 3. 权限层(操作控制) │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ - 查看权限:所有登录用户 │
|
||
│ - 分配权限:省级/市级管理员(受地区限制) │
|
||
│ - 修改配置:仅省级管理员 │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 核心原则
|
||
|
||
| 原则 | 说明 | 示例 |
|
||
|-----|------|------|
|
||
| **角色统一** | 角色是全局的,不分地区 | 梅州和云浮都使用相同的 admin 角色 |
|
||
| **用户隔离** | 用户按地区管理,市级管理员只能操作同地区用户 | 梅州管理员只能管理梅州的用户 |
|
||
| **权限分层** | 查看开放,修改受限 | 所有人能看角色列表,只有省级能改角色权限 |
|
||
|
||
---
|
||
|
||
### 1.2 数据模型
|
||
|
||
#### 数据库表结构
|
||
|
||
**1. roles 表(角色表)**
|
||
|
||
| 字段 | 类型 | 说明 | 示例 |
|
||
|------|------|------|------|
|
||
| `id` | integer | 角色ID(主键) | 1, 2, 52 |
|
||
| `role_key` | varchar(30) | 角色标识(唯一) | admin, common, provincial_admin |
|
||
| `role_name` | varchar(50) | 角色名称 | 市级管理员, 普通员工, 省级管理员 |
|
||
| `description` | varchar(200) | 角色描述 | 负责本地区的所有业务管理 |
|
||
| `is_system_role` | boolean | 是否系统角色 | true, false |
|
||
| `created_at` | timestamp | 创建时间 | 2025-01-15 10:30:00 |
|
||
|
||
**实际数据**:
|
||
```json
|
||
[
|
||
{
|
||
"id": 1,
|
||
"role_key": "admin",
|
||
"role_name": "市级管理员",
|
||
"description": "负责本地区的所有业务管理,不包括系统设置和角色权限管理"
|
||
},
|
||
{
|
||
"id": 2,
|
||
"role_key": "common",
|
||
"role_name": "普通员工",
|
||
"description": "仅能操作自己的数据"
|
||
},
|
||
{
|
||
"id": 52,
|
||
"role_key": "provincial_admin",
|
||
"role_name": "省级管理员",
|
||
"description": "拥有全部权限,可以管理所有地区的评查点规则"
|
||
}
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
**2. sso_users 表(用户表)**
|
||
|
||
| 字段 | 类型 | 说明 | 示例 |
|
||
|------|------|------|------|
|
||
| `id` | integer | 用户ID(主键) | 5, 6, 8 |
|
||
| `username` | varchar(255) | 用户名 | admin, 梅州烟草 |
|
||
| `nick_name` | varchar(255) | 昵称 | 张三, 李四 |
|
||
| `area` | varchar(255) | **地区**(用于权限隔离)⭐ | 梅州, 云浮, 揭阳, 潮州 |
|
||
| `ou_name` | varchar(255) | **部门名称**(用于组织划分) | 测试技术部, 研发部 |
|
||
| `ou_id` | varchar(255) | 组织单位ID | 0000000A1ML |
|
||
| `status` | smallint | 用户状态(0=正常) | 0 |
|
||
|
||
**实际数据(按地区分组)**:
|
||
```json
|
||
{
|
||
"梅州": [
|
||
{"id": 5, "username": "admin", "nick_name": "admin", "area": "梅州", "ou_name": "test"},
|
||
{"id": 6, "username": "烟草科技", "nick_name": "烟草科技", "area": "梅州", "ou_name": "测试技术部"},
|
||
{"id": 8, "username": "梅州烟草", "nick_name": "梅州烟草", "area": "梅州", "ou_name": "梅州管理员账号"},
|
||
{"id": 19, "username": "test_admin", "nick_name": "测试管理员", "area": "梅州"},
|
||
{"id": 20, "username": "test_normal", "nick_name": "测试普通用户", "area": "梅州"},
|
||
{"id": 21, "username": "test_other", "nick_name": "测试其他用户", "area": "梅州"}
|
||
],
|
||
"云浮": [
|
||
{"id": 9, "username": "云浮烟草", "nick_name": "云浮烟草", "area": "云浮"},
|
||
{"id": 22, "username": "OAuth已存在用户测试", "area": "云浮"}
|
||
],
|
||
"揭阳": [
|
||
{"id": 7, "username": "揭阳惠来烟草", "area": "揭阳"}
|
||
],
|
||
"潮州": [
|
||
{"id": 12, "username": "潮州烟草", "area": "潮州"}
|
||
]
|
||
}
|
||
```
|
||
|
||
**⚠️ 重要区分**:
|
||
- `area`:**地区**(省/市级别),用于**权限隔离**
|
||
- `ou_name`:**部门名称**(部门级别),用于**组织划分**
|
||
|
||
---
|
||
|
||
**3. user_role 表(用户角色关联表)**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | integer | 主键 |
|
||
| `user_id` | integer | 用户ID(外键 → sso_users.id) |
|
||
| `role_id` | integer | 角色ID(外键 → roles.id) |
|
||
| `created_at` | timestamp | 分配时间 |
|
||
|
||
**实际数据**:
|
||
```json
|
||
[
|
||
{"user_id": 5, "role_id": 52, "user": "admin", "role": "provincial_admin", "area": "梅州"},
|
||
{"user_id": 6, "role_id": 1, "user": "烟草科技", "role": "admin", "area": "梅州"},
|
||
{"user_id": 8, "role_id": 1, "user": "梅州烟草", "role": "admin", "area": "梅州"},
|
||
{"user_id": 9, "role_id": 1, "user": "云浮烟草", "role": "admin", "area": "云浮"},
|
||
{"user_id": 12, "role_id": 1, "user": "潮州烟草", "role": "admin", "area": "潮州"},
|
||
{"user_id": 20, "role_id": 2, "user": "test_normal", "role": "common", "area": "梅州"},
|
||
{"user_id": 21, "role_id": 2, "user": "test_other", "role": "common", "area": "梅州"}
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
### 1.3 权限控制规则
|
||
|
||
#### 规则矩阵表
|
||
|
||
| 操作 | 省级管理员 | 市级管理员 | 普通用户 |
|
||
|------|----------|----------|---------|
|
||
| **查看角色列表** | ✅ 所有角色 | ✅ 所有角色 | ✅ 所有角色 |
|
||
| **查看用户列表** | ✅ 所有地区用户 | ✅ 仅同地区用户 | ✅ 仅同地区用户 |
|
||
| **分配用户角色** | ✅ 所有地区用户 | ✅ 仅同地区用户 | ❌ 无权限 |
|
||
| **移除用户角色** | ✅ 所有地区用户 | ✅ 仅同地区用户 | ❌ 无权限 |
|
||
| **修改角色权限** | ✅ 可以修改 | ❌ 无权限 | ❌ 无权限 |
|
||
|
||
#### 地区隔离逻辑
|
||
|
||
**市级管理员的地区限制**:
|
||
|
||
```sql
|
||
-- 示例:梅州市级管理员(area='梅州')
|
||
|
||
-- 查看用户列表
|
||
SELECT * FROM sso_users
|
||
WHERE status = 0
|
||
AND area = '梅州' -- ⭐强制过滤同地区
|
||
|
||
-- 分配用户角色(检查目标用户地区)
|
||
SELECT area FROM sso_users WHERE id = $1
|
||
-- 如果 target_area != '梅州' → 返回403错误
|
||
|
||
-- 移除用户角色(检查目标用户地区)
|
||
SELECT area FROM sso_users WHERE id = $1
|
||
-- 如果 target_area != '梅州' → 返回403错误
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 完整接口文档
|
||
|
||
### 2.1 角色管理接口
|
||
|
||
#### 2.1.1 获取角色列表
|
||
|
||
**接口地址**:
|
||
```
|
||
GET /api/v3/rbac/roles
|
||
```
|
||
|
||
**权限要求**:
|
||
- 仅需登录(所有登录用户可访问)
|
||
|
||
**请求参数**:
|
||
|
||
| 参数名 | 位置 | 类型 | 必填 | 默认值 | 说明 |
|
||
|-------|------|------|------|--------|------|
|
||
| `page` | query | integer | 否 | 1 | 页码(从1开始) |
|
||
| `page_size` | query | integer | 否 | 20 | 每页数量(1-100) |
|
||
| `role_key` | query | string | 否 | - | 角色标识过滤(精确匹配) |
|
||
| `role_name` | query | string | 否 | - | 角色名称模糊搜索 |
|
||
| `include_system` | query | boolean | 否 | true | 是否包含系统角色 |
|
||
|
||
**请求示例**:
|
||
|
||
```bash
|
||
# 示例1:获取第1页,每页20条
|
||
curl -X GET "http://localhost:8073/api/v3/rbac/roles?page=1&page_size=20" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# 示例2:搜索角色名称包含"管理"的角色
|
||
curl -X GET "http://localhost:8073/api/v3/rbac/roles?role_name=管理" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# 示例3:查询特定角色
|
||
curl -X GET "http://localhost:8073/api/v3/rbac/roles?role_key=admin" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
```
|
||
|
||
**返回格式**:
|
||
|
||
```json
|
||
{
|
||
"total": 3,
|
||
"page": 1,
|
||
"page_size": 20,
|
||
"data": [
|
||
{
|
||
"id": 52,
|
||
"role_key": "provincial_admin",
|
||
"role_name": "省级管理员",
|
||
"description": "拥有全部权限,可以管理所有地区的评查点规则",
|
||
"is_system": true,
|
||
"created_at": "2025-01-15 10:30:00",
|
||
"updated_at": "2025-01-20 14:20:00",
|
||
"user_count": 1,
|
||
"permission_count": 128
|
||
},
|
||
{
|
||
"id": 1,
|
||
"role_key": "admin",
|
||
"role_name": "市级管理员",
|
||
"description": "负责本地区的所有业务管理,不包括系统设置和角色权限管理",
|
||
"is_system": false,
|
||
"created_at": "2025-01-10 09:00:00",
|
||
"updated_at": "2025-01-18 16:45:00",
|
||
"user_count": 4,
|
||
"permission_count": 85
|
||
},
|
||
{
|
||
"id": 2,
|
||
"role_key": "common",
|
||
"role_name": "普通员工",
|
||
"description": "仅能操作自己的数据",
|
||
"is_system": false,
|
||
"created_at": "2025-01-10 09:05:00",
|
||
"updated_at": "2025-01-15 11:30:00",
|
||
"user_count": 2,
|
||
"permission_count": 32
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**字段说明**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `total` | integer | 总记录数 |
|
||
| `page` | integer | 当前页码 |
|
||
| `page_size` | integer | 每页大小 |
|
||
| `data` | array | 角色列表 |
|
||
| `data[].id` | integer | 角色ID |
|
||
| `data[].role_key` | string | 角色标识(唯一) |
|
||
| `data[].role_name` | string | 角色名称 |
|
||
| `data[].description` | string | 角色描述 |
|
||
| `data[].is_system` | boolean | 是否系统角色 |
|
||
| `data[].created_at` | string | 创建时间(北京时间) |
|
||
| `data[].updated_at` | string | 更新时间(北京时间) |
|
||
| `data[].user_count` | integer | 拥有该角色的用户数量 |
|
||
| `data[].permission_count` | integer | 该角色拥有的权限数量 |
|
||
|
||
**业务逻辑**:
|
||
|
||
```
|
||
1. JWT Token验证
|
||
└─ 验证通过 → 解析用户信息
|
||
|
||
2. 构建查询条件
|
||
├─ 如果提供 role_key → 添加精确匹配条件
|
||
├─ 如果提供 role_name → 添加模糊搜索条件
|
||
└─ 如果 include_system=false → 过滤系统角色
|
||
|
||
3. ⭐核心逻辑:所有人看到所有角色
|
||
└─ 不进行用户角色过滤(角色是全局统一的)
|
||
|
||
4. 分页查询
|
||
├─ OFFSET = (page - 1) * page_size
|
||
└─ LIMIT = page_size
|
||
|
||
5. 统计额外信息
|
||
├─ 查询每个角色的用户数量(COUNT)
|
||
└─ 查询每个角色的权限数量(COUNT)
|
||
|
||
6. 返回结果
|
||
```
|
||
|
||
**错误响应**:
|
||
|
||
```json
|
||
{
|
||
"detail": "Token已过期"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2.2 用户管理接口
|
||
|
||
#### 2.2.1 获取用户列表
|
||
|
||
**接口地址**:
|
||
```
|
||
GET /api/v2/users
|
||
```
|
||
|
||
**权限要求**:
|
||
- 仅需登录
|
||
|
||
**请求参数**:
|
||
|
||
| 参数名 | 位置 | 类型 | 必填 | 默认值 | 说明 |
|
||
|-------|------|------|------|--------|------|
|
||
| `page` | query | integer | 否 | 1 | 页码(从1开始) |
|
||
| `page_size` | query | integer | 否 | 20 | 每页数量(1-100) |
|
||
| `ou_id` | query | string | 否 | - | 组织单位ID过滤 |
|
||
| `is_leader` | query | boolean | 否 | - | 是否为领导过滤 |
|
||
| `status` | query | integer | 否 | 0 | 用户状态(0=正常) |
|
||
| `search` | query | string | 否 | - | 搜索关键词(用户名或昵称) |
|
||
|
||
**请求示例**:
|
||
|
||
```bash
|
||
# 示例1:获取第1页用户
|
||
curl -X GET "http://localhost:8073/api/v2/users?page=1&page_size=20" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# 示例2:搜索用户名或昵称包含"张"的用户
|
||
curl -X GET "http://localhost:8073/api/v2/users?search=张" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
|
||
# 示例3:查询特定部门的用户
|
||
curl -X GET "http://localhost:8073/api/v2/users?ou_id=0000000A1ML" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
```
|
||
|
||
**返回格式**:
|
||
|
||
```json
|
||
{
|
||
"users": [
|
||
{
|
||
"id": 5,
|
||
"username": "admin",
|
||
"nick_name": "admin",
|
||
"ou_id": "test",
|
||
"ou_name": "test",
|
||
"is_leader": false,
|
||
"status": 0
|
||
},
|
||
{
|
||
"id": 6,
|
||
"username": "烟草科技",
|
||
"nick_name": "烟草科技",
|
||
"ou_id": "0000000A1ML",
|
||
"ou_name": "测试技术部",
|
||
"is_leader": false,
|
||
"status": 0
|
||
}
|
||
],
|
||
"total": 6
|
||
}
|
||
```
|
||
|
||
**业务逻辑**:
|
||
|
||
```
|
||
1. JWT Token验证
|
||
└─ 解析用户角色(user_role)和地区(area)
|
||
|
||
2. ⭐核心逻辑:地区数据隔离
|
||
├─ 如果 user_role == 'provincial_admin'
|
||
│ └─ 查询所有地区用户(无地区过滤)
|
||
└─ 否则(admin或其他用户)
|
||
└─ 强制添加条件:WHERE area = current_user.area
|
||
|
||
3. 应用额外过滤条件
|
||
├─ 如果提供 ou_id → 添加 ou_id = {ou_id}
|
||
├─ 如果提供 is_leader → 添加 is_leader = {is_leader}
|
||
└─ 如果提供 search → 添加 (username ILIKE %{search}% OR nick_name ILIKE %{search}%)
|
||
|
||
4. 分页查询
|
||
├─ OFFSET = (page - 1) * page_size
|
||
└─ LIMIT = page_size
|
||
|
||
5. 查询总数(使用相同过滤条件)
|
||
|
||
6. 返回结果
|
||
```
|
||
|
||
**地区过滤示例**:
|
||
|
||
```sql
|
||
-- 省级管理员(user_role='provincial_admin')
|
||
SELECT * FROM sso_users WHERE status = 0
|
||
-- 返回:所有地区的10个用户
|
||
|
||
-- 市级管理员(user_role='admin', area='梅州')
|
||
SELECT * FROM sso_users WHERE status = 0 AND area = '梅州'
|
||
-- 返回:仅梅州的6个用户
|
||
```
|
||
|
||
---
|
||
|
||
### 2.3 角色分配接口
|
||
|
||
#### 2.3.1 分配用户到角色
|
||
|
||
**接口地址**:
|
||
```
|
||
POST /api/v3/rbac/users/{user_id}/roles
|
||
```
|
||
|
||
**权限要求**:
|
||
- 需要角色分配权限
|
||
|
||
**请求参数**:
|
||
|
||
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|
||
|-------|------|------|------|------|
|
||
| `user_id` | path | integer | 是 | 用户ID |
|
||
| `role_ids` | body | array | 是 | 角色ID列表 |
|
||
|
||
**请求示例**:
|
||
|
||
```bash
|
||
# 给用户ID=20分配common角色(role_id=2)
|
||
curl -X POST "http://localhost:8073/api/v3/rbac/users/20/roles" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"role_ids": [2]
|
||
}'
|
||
```
|
||
|
||
**请求Body示例**:
|
||
|
||
```json
|
||
{
|
||
"role_ids": [2]
|
||
}
|
||
```
|
||
|
||
**返回格式**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"user_id": 20,
|
||
"username": "test_normal",
|
||
"assigned_roles": [
|
||
{
|
||
"role_id": 2,
|
||
"role_key": "common",
|
||
"role_name": "普通员工"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**业务逻辑**:
|
||
|
||
```
|
||
1. JWT Token验证
|
||
└─ 解析当前用户角色(current_user_role)和地区(current_user_area)
|
||
|
||
2. 查询目标用户信息
|
||
└─ SELECT id, username, area FROM sso_users WHERE id = {user_id}
|
||
|
||
3. ⭐核心逻辑:地区检查
|
||
├─ 如果 current_user_role == 'provincial_admin'
|
||
│ └─ 跳过地区检查(可操作所有地区用户)
|
||
└─ 否则(admin或其他用户)
|
||
├─ 如果 target_area != current_user_area
|
||
│ └─ 返回403错误:"无权限给其他地区用户分配角色"
|
||
└─ 否则,继续执行
|
||
|
||
4. 防止自己给自己分配角色
|
||
└─ 如果 user_id == current_user_id → 返回403错误
|
||
|
||
5. 验证角色是否存在
|
||
└─ SELECT * FROM roles WHERE id IN ({role_ids})
|
||
|
||
6. 检查provincial_admin角色限制
|
||
└─ 只有省级管理员可以分配provincial_admin角色
|
||
|
||
7. 开始事务
|
||
├─ 插入 user_role 记录(批量)
|
||
└─ 提交事务
|
||
|
||
8. 返回结果
|
||
```
|
||
|
||
**地区检查示例**:
|
||
|
||
```sql
|
||
-- 场景1:梅州市级管理员(area='梅州')给梅州用户分配角色
|
||
target_user: {id: 20, area: '梅州'}
|
||
current_user: {role: 'admin', area: '梅州'}
|
||
检查结果: '梅州' == '梅州' → ✅ 允许
|
||
|
||
-- 场景2:梅州市级管理员尝试给云浮用户分配角色
|
||
target_user: {id: 9, area: '云浮'}
|
||
current_user: {role: 'admin', area: '梅州'}
|
||
检查结果: '云浮' != '梅州' → ❌ 返回403错误
|
||
```
|
||
|
||
**错误响应**:
|
||
|
||
```json
|
||
{
|
||
"detail": "无权限给其他地区用户分配角色: target_area=云浮, your_area=梅州"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
#### 2.3.2 移除用户角色
|
||
|
||
**接口地址**:
|
||
```
|
||
DELETE /api/v3/rbac/users/{user_id}/roles/{role_id}
|
||
```
|
||
|
||
**权限要求**:
|
||
- 需要角色分配权限
|
||
|
||
**请求参数**:
|
||
|
||
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|
||
|-------|------|------|------|------|
|
||
| `user_id` | path | integer | 是 | 用户ID |
|
||
| `role_id` | path | integer | 是 | 角色ID |
|
||
|
||
**请求示例**:
|
||
|
||
```bash
|
||
# 移除用户ID=20的common角色(role_id=2)
|
||
curl -X DELETE "http://localhost:8073/api/v3/rbac/users/20/roles/2" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||
```
|
||
|
||
**返回格式**:
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"user_id": 20,
|
||
"role_id": 2,
|
||
"message": "角色移除成功"
|
||
}
|
||
}
|
||
```
|
||
|
||
**业务逻辑**:
|
||
|
||
```
|
||
1. JWT Token验证
|
||
|
||
2. 查询目标用户信息
|
||
└─ SELECT id, username, area FROM sso_users WHERE id = {user_id}
|
||
|
||
3. ⭐核心逻辑:地区检查(同分配用户逻辑)
|
||
├─ 省级管理员 → 无限制
|
||
└─ 市级管理员 → 只能操作同地区用户
|
||
|
||
4. 验证角色分配是否存在
|
||
└─ SELECT * FROM user_role WHERE user_id = {user_id} AND role_id = {role_id}
|
||
|
||
5. 删除角色分配
|
||
└─ DELETE FROM user_role WHERE user_id = {user_id} AND role_id = {role_id}
|
||
|
||
6. 返回结果
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 业务场景示例
|
||
|
||
### 场景1:梅州市级管理员给本地用户分配角色
|
||
|
||
**前置条件**:
|
||
- 当前用户:梅州烟草(user_id=8, role=admin, area=梅州)
|
||
- 目标用户:test_normal(user_id=20, area=梅州)
|
||
|
||
**操作流程**:
|
||
|
||
```bash
|
||
# 步骤1:查看所有角色
|
||
GET /api/v3/rbac/roles
|
||
|
||
# 返回(所有人都能看到):
|
||
{
|
||
"data": [
|
||
{"id": 1, "role_key": "admin", "role_name": "市级管理员"},
|
||
{"id": 2, "role_key": "common", "role_name": "普通员工"},
|
||
{"id": 52, "role_key": "provincial_admin", "role_name": "省级管理员"}
|
||
]
|
||
}
|
||
|
||
# 步骤2:查看可分配的用户列表
|
||
GET /api/v2/users
|
||
|
||
# 返回(只看到梅州的用户):
|
||
{
|
||
"users": [
|
||
{"id": 5, "username": "admin", "area": "梅州"},
|
||
{"id": 6, "username": "烟草科技", "area": "梅州"},
|
||
{"id": 8, "username": "梅州烟草", "area": "梅州"},
|
||
{"id": 19, "username": "test_admin", "area": "梅州"},
|
||
{"id": 20, "username": "test_normal", "area": "梅州"},
|
||
{"id": 21, "username": "test_other", "area": "梅州"}
|
||
],
|
||
"total": 6
|
||
}
|
||
|
||
# 步骤3:给test_normal分配common角色
|
||
POST /api/v3/rbac/users/20/roles
|
||
{
|
||
"role_ids": [2]
|
||
}
|
||
|
||
# 返回:
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"user_id": 20,
|
||
"username": "test_normal",
|
||
"assigned_roles": [
|
||
{"role_id": 2, "role_key": "common", "role_name": "普通员工"}
|
||
]
|
||
}
|
||
}
|
||
|
||
# ✅ 成功!因为:
|
||
# 1. 角色列表可见(所有人都能看到所有角色)
|
||
# 2. 用户列表只显示梅州用户(地区过滤)
|
||
# 3. 分配成功(同地区用户)
|
||
```
|
||
|
||
---
|
||
|
||
### 场景2:梅州市级管理员尝试给云浮用户分配角色
|
||
|
||
**前置条件**:
|
||
- 当前用户:梅州烟草(user_id=8, role=admin, area=梅州)
|
||
- 目标用户:云浮烟草(user_id=9, area=云浮)
|
||
|
||
**操作流程**:
|
||
|
||
```bash
|
||
# 步骤1:查看用户列表
|
||
GET /api/v2/users
|
||
|
||
# 返回(只看到梅州的6个用户,看不到云浮烟草):
|
||
{
|
||
"users": [
|
||
{"id": 5, "username": "admin", "area": "梅州"},
|
||
{"id": 6, "username": "烟草科技", "area": "梅州"},
|
||
...
|
||
],
|
||
"total": 6
|
||
}
|
||
|
||
# 步骤2:尝试直接调用API给云浮用户分配角色
|
||
POST /api/v3/rbac/users/9/roles
|
||
{
|
||
"role_ids": [2]
|
||
}
|
||
|
||
# 返回:
|
||
{
|
||
"detail": "无权限给其他地区用户分配角色: target_area=云浮, your_area=梅州"
|
||
}
|
||
|
||
# ❌ 失败!因为:
|
||
# 1. 地区检查:云浮 != 梅州
|
||
# 2. 后端强制拦截跨地区操作
|
||
```
|
||
|
||
---
|
||
|
||
### 场景3:省级管理员的完整权限
|
||
|
||
**前置条件**:
|
||
- 当前用户:admin(user_id=5, role=provincial_admin, area=梅州)
|
||
|
||
**操作流程**:
|
||
|
||
```bash
|
||
# 步骤1:查看所有角色
|
||
GET /api/v3/rbac/roles
|
||
|
||
# 返回(所有角色):
|
||
{
|
||
"data": [
|
||
{"id": 1, "role_key": "admin"},
|
||
{"id": 2, "role_key": "common"},
|
||
{"id": 52, "role_key": "provincial_admin"}
|
||
]
|
||
}
|
||
|
||
# 步骤2:查看所有用户(包括所有地区)
|
||
GET /api/v2/users
|
||
|
||
# 返回(所有地区的10个用户):
|
||
{
|
||
"users": [
|
||
{"id": 5, "area": "梅州"},
|
||
{"id": 6, "area": "梅州"},
|
||
{"id": 7, "area": "揭阳"},
|
||
{"id": 8, "area": "梅州"},
|
||
{"id": 9, "area": "云浮"},
|
||
{"id": 12, "area": "潮州"},
|
||
...
|
||
],
|
||
"total": 10
|
||
}
|
||
|
||
# 步骤3:给云浮用户分配角色(跨地区)
|
||
POST /api/v3/rbac/users/9/roles
|
||
{
|
||
"role_ids": [1]
|
||
}
|
||
|
||
# 返回:
|
||
{
|
||
"code": 200,
|
||
"msg": "success"
|
||
}
|
||
|
||
# ✅ 成功!因为:
|
||
# 1. 省级管理员可以看到所有地区用户
|
||
# 2. 省级管理员可以操作所有地区用户
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 数据库实际数据
|
||
|
||
### 4.1 角色数据
|
||
|
||
```sql
|
||
SELECT id, role_key, role_name, description
|
||
FROM roles
|
||
ORDER BY id;
|
||
```
|
||
|
||
| id | role_key | role_name | description |
|
||
|----|----------|-----------|-------------|
|
||
| 1 | admin | 市级管理员 | 负责本地区的所有业务管理 |
|
||
| 2 | common | 普通员工 | 仅能操作自己的数据 |
|
||
| 52 | provincial_admin | 省级管理员 | 拥有全部权限 |
|
||
|
||
---
|
||
|
||
### 4.2 用户数据(按地区分组)
|
||
|
||
```sql
|
||
SELECT id, username, nick_name, area, ou_name
|
||
FROM sso_users
|
||
WHERE status = 0
|
||
ORDER BY area, id;
|
||
```
|
||
|
||
**梅州(6人)**:
|
||
|
||
| id | username | nick_name | area | ou_name |
|
||
|----|----------|-----------|------|---------|
|
||
| 5 | admin | admin | 梅州 | test |
|
||
| 6 | 烟草科技 | 烟草科技 | 梅州 | 测试技术部 |
|
||
| 8 | 梅州烟草 | 梅州烟草 | 梅州 | 梅州管理员账号 |
|
||
| 19 | test_admin | 测试管理员 | 梅州 | 测试部门 |
|
||
| 20 | test_normal | 测试普通用户 | 梅州 | 测试部门 |
|
||
| 21 | test_other | 测试其他用户 | 梅州 | 测试部门 |
|
||
|
||
**云浮(2人)**:
|
||
|
||
| id | username | area |
|
||
|----|----------|------|
|
||
| 9 | 云浮烟草 | 云浮 |
|
||
| 22 | OAuth已存在用户测试 | 云浮 |
|
||
|
||
**揭阳(1人)**、**潮州(1人)**...
|
||
|
||
---
|
||
|
||
### 4.3 用户角色分配数据
|
||
|
||
```sql
|
||
SELECT
|
||
u.id as user_id,
|
||
u.username,
|
||
u.area,
|
||
r.role_key,
|
||
r.role_name
|
||
FROM user_role ur
|
||
JOIN sso_users u ON ur.user_id = u.id
|
||
JOIN roles r ON ur.role_id = r.id
|
||
WHERE u.status = 0
|
||
ORDER BY u.area, u.id;
|
||
```
|
||
|
||
| user_id | username | area | role_key | role_name |
|
||
|---------|----------|------|----------|-----------|
|
||
| 5 | admin | 梅州 | provincial_admin | 省级管理员 |
|
||
| 6 | 烟草科技 | 梅州 | admin | 市级管理员 |
|
||
| 8 | 梅州烟草 | 梅州 | admin | 市级管理员 |
|
||
| 20 | test_normal | 梅州 | common | 普通员工 |
|
||
| 21 | test_other | 梅州 | common | 普通员工 |
|
||
| 9 | 云浮烟草 | 云浮 | admin | 市级管理员 |
|
||
| 12 | 潮州烟草 | 潮州 | admin | 市级管理员 |
|
||
|
||
---
|
||
|
||
## 5. 前端对接指南
|
||
|
||
### 5.1 角色权限管理页面
|
||
|
||
#### Vue 3 完整示例
|
||
|
||
```vue
|
||
<template>
|
||
<div class="role-permission-page">
|
||
<!-- 用户列表 -->
|
||
<el-table :data="users" @selection-change="handleUserSelect">
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column prop="username" label="用户名" />
|
||
<el-table-column prop="nick_name" label="姓名" />
|
||
<el-table-column prop="area" label="地区" />
|
||
<el-table-column label="操作">
|
||
<template #default="{ row }">
|
||
<el-button size="small" @click="openAssignDialog(row)">
|
||
分配角色
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分配角色对话框 -->
|
||
<el-dialog v-model="dialogVisible" title="分配角色">
|
||
<el-form>
|
||
<el-form-item label="选择角色">
|
||
<el-checkbox-group v-model="selectedRoles">
|
||
<el-checkbox
|
||
v-for="role in roles"
|
||
:key="role.id"
|
||
:label="role.id"
|
||
>
|
||
{{ role.role_name }}
|
||
</el-checkbox>
|
||
</el-checkbox-group>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="assignRoles">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
|
||
const users = ref([])
|
||
const roles = ref([])
|
||
const currentUser = ref(null)
|
||
const dialogVisible = ref(false)
|
||
const selectedRoles = ref([])
|
||
|
||
// 获取当前用户信息
|
||
const getCurrentUser = () => {
|
||
const token = localStorage.getItem('token')
|
||
if (token) {
|
||
const payload = JSON.parse(atob(token.split('.')[1]))
|
||
currentUser.value = {
|
||
id: payload.user_id,
|
||
role: payload.user_role,
|
||
area: payload.area
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取角色列表(所有人都能看到所有角色)
|
||
const loadRoles = async () => {
|
||
try {
|
||
const response = await fetch('/api/v3/rbac/roles', {
|
||
headers: {
|
||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||
}
|
||
})
|
||
const result = await response.json()
|
||
roles.value = result.data
|
||
} catch (error) {
|
||
ElMessage.error('加载角色列表失败')
|
||
}
|
||
}
|
||
|
||
// 获取用户列表(自动按地区过滤)
|
||
const loadUsers = async () => {
|
||
try {
|
||
const response = await fetch('/api/v2/users?page=1&page_size=100', {
|
||
headers: {
|
||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||
}
|
||
})
|
||
const result = await response.json()
|
||
users.value = result.users
|
||
|
||
// 提示:市级管理员只能看到同地区用户
|
||
if (currentUser.value.role === 'admin') {
|
||
ElMessage.info(`当前显示 ${currentUser.value.area} 地区的 ${result.total} 个用户`)
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('加载用户列表失败')
|
||
}
|
||
}
|
||
|
||
// 打开分配对话框
|
||
const openAssignDialog = (user) => {
|
||
currentUser.value.targetUser = user
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 分配角色
|
||
const assignRoles = async () => {
|
||
const targetUser = currentUser.value.targetUser
|
||
|
||
try {
|
||
const response = await fetch(`/api/v3/rbac/users/${targetUser.id}/roles`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${localStorage.getItem('token')}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
role_ids: selectedRoles.value
|
||
})
|
||
})
|
||
|
||
const result = await response.json()
|
||
|
||
if (response.ok) {
|
||
ElMessage.success('角色分配成功')
|
||
dialogVisible.value = false
|
||
} else if (response.status === 403) {
|
||
ElMessage.error(result.detail || '权限不足:只能给同地区用户分配角色')
|
||
} else {
|
||
ElMessage.error(result.detail || '分配失败')
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('分配失败:' + error.message)
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
getCurrentUser()
|
||
loadRoles()
|
||
loadUsers()
|
||
})
|
||
</script>
|
||
```
|
||
|
||
### 5.2 关键要点
|
||
|
||
#### 1. Token解析
|
||
|
||
```javascript
|
||
// 解析JWT Token获取用户信息
|
||
const token = localStorage.getItem('token')
|
||
const payload = JSON.parse(atob(token.split('.')[1]))
|
||
|
||
// Payload包含:
|
||
{
|
||
user_id: 8,
|
||
username: "梅州烟草",
|
||
user_role: "admin", // 用户角色
|
||
area: "梅州", // 用户地区(⭐重要)
|
||
exp: 1738036800
|
||
}
|
||
```
|
||
|
||
#### 2. 错误处理
|
||
|
||
```javascript
|
||
// 403错误:跨地区操作
|
||
if (response.status === 403) {
|
||
ElMessage.error('权限不足:只能给同地区用户分配角色')
|
||
}
|
||
|
||
// 401错误:Token过期
|
||
if (response.status === 401) {
|
||
ElMessage.error('登录已过期,请重新登录')
|
||
router.push('/login')
|
||
}
|
||
```
|
||
|
||
#### 3. 地区提示
|
||
|
||
```javascript
|
||
// 市级管理员登录时,提示当前地区
|
||
if (user_role === 'admin') {
|
||
ElMessage.info(`您当前管理的地区:${area}`)
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 附录
|
||
|
||
### A. 错误码对照表
|
||
|
||
| HTTP状态码 | 说明 | 常见原因 | 示例 |
|
||
|-----------|------|---------|------|
|
||
| 200 | 成功 | - | - |
|
||
| 400 | 请求参数错误 | 参数格式不正确 | `{"detail": "role_ids必须是数组"}` |
|
||
| 401 | 未认证 | Token无效或过期 | `{"detail": "Token已过期"}` |
|
||
| 403 | 无权限 | 跨地区操作或权限不足 | `{"detail": "无权限给其他地区用户分配角色"}` |
|
||
| 404 | 资源不存在 | 用户或角色不存在 | `{"detail": "用户不存在: user_id=999"}` |
|
||
| 500 | 服务器错误 | 后端异常 | `{"detail": "数据库连接失败"}` |
|
||
|
||
---
|
||
|
||
### B. JWT Token规范
|
||
|
||
**Header格式**:
|
||
```
|
||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||
```
|
||
|
||
**Payload字段**:
|
||
|
||
| 字段 | 类型 | 必填 | 说明 | 示例 |
|
||
|------|------|------|------|------|
|
||
| `user_id` | integer | 是 | 用户ID | 8 |
|
||
| `username` | string | 是 | 用户名 | 梅州烟草 |
|
||
| `user_role` | string | 是 | 用户角色 | admin, provincial_admin, common |
|
||
| `area` | string | 是 | 用户地区(⭐用于权限隔离) | 梅州, 云浮, 揭阳, 潮州 |
|
||
| `exp` | integer | 是 | 过期时间(Unix时间戳) | 1738036800 |
|
||
| `aud` | string | 是 | 受众标识 | docauditai |
|
||
|
||
---
|
||
|
||
### C. 数据库字段重要说明
|
||
|
||
| 字段 | 表 | 用途 | 示例 | 说明 |
|
||
|------|-----|------|------|------|
|
||
| `area` | sso_users | **地区隔离**(权限控制) | 梅州, 云浮 | ⭐用于判断市级管理员的操作范围 |
|
||
| `ou_name` | sso_users | **部门划分**(组织结构) | 测试技术部, 研发部 | 仅用于显示,不用于权限控制 |
|
||
| `ou_id` | sso_users | 组织单位ID | 0000000A1ML | 用于组织树结构 |
|
||
|
||
**⚠️ 关键区别**:
|
||
- `area`:用于**权限隔离**(省/市级别)
|
||
- `ou_name`:用于**组织显示**(部门级别)
|
||
|
||
---
|
||
|
||
### D. 快速参考
|
||
|
||
#### 角色类型
|
||
|
||
| role_key | role_name | 说明 |
|
||
|----------|-----------|------|
|
||
| `provincial_admin` | 省级管理员 | 最高权限,可操作所有地区 |
|
||
| `admin` | 市级管理员 | 地区管理员,只能操作同地区用户 |
|
||
| `common` | 普通员工 | 基础权限 |
|
||
|
||
#### API端点速查
|
||
|
||
| 接口 | 方法 | 说明 |
|
||
|------|------|------|
|
||
| `/api/v3/rbac/roles` | GET | 获取角色列表(所有人可见) |
|
||
| `/api/v2/users` | GET | 获取用户列表(按地区过滤) |
|
||
| `/api/v3/rbac/users/{id}/roles` | POST | 分配角色(地区检查) |
|
||
| `/api/v3/rbac/users/{id}/roles/{rid}` | DELETE | 移除角色(地区检查) |
|
||
|
||
---
|
||
|
||
**文档版本**: v3.3
|
||
**最后更新**: 2025-11-28
|
||
**维护者**: Backend Team
|
||
**反馈渠道**: backend-team@example.com
|