32 KiB
32 KiB
RBAC 权限管理完整指南 v3.3
📋 文档说明
本文档是 v3.3 版本的 RBAC(基于角色的访问控制)和用户管理的完整指南,包含:
- ✅ 核心业务逻辑的详细说明
- ✅ 所有接口的完整定义和请求参数
- ✅ 实际数据库数据示例
- ✅ 权限控制和地区隔离规则
- ✅ 完整的前后端对接示例
适用对象:后端开发、前端开发、产品经理、测试人员
目录
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 |
实际数据:
[
{
"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 |
实际数据(按地区分组):
{
"梅州": [
{"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 | 分配时间 |
实际数据:
[
{"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 权限控制规则
规则矩阵表
| 操作 | 省级管理员 | 市级管理员 | 普通用户 |
|---|---|---|---|
| 查看角色列表 | ✅ 所有角色 | ✅ 所有角色 | ✅ 所有角色 |
| 查看用户列表 | ✅ 所有地区用户 | ✅ 仅同地区用户 | ✅ 仅同地区用户 |
| 分配用户角色 | ✅ 所有地区用户 | ✅ 仅同地区用户 | ❌ 无权限 |
| 移除用户角色 | ✅ 所有地区用户 | ✅ 仅同地区用户 | ❌ 无权限 |
| 修改角色权限 | ✅ 可以修改 | ❌ 无权限 | ❌ 无权限 |
地区隔离逻辑
市级管理员的地区限制:
-- 示例:梅州市级管理员(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 | 是否包含系统角色 |
请求示例:
# 示例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"
返回格式:
{
"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. 返回结果
错误响应:
{
"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 | 否 | - | 搜索关键词(用户名或昵称) |
请求示例:
# 示例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"
返回格式:
{
"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. 返回结果
地区过滤示例:
-- 省级管理员(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列表 |
请求示例:
# 给用户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示例:
{
"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. 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. 返回结果
地区检查示例:
-- 场景1:梅州市级管理员(area='梅州')给梅州用户分配角色
target_user: {id: 20, area: '梅州'}
current_user: {role: 'admin', area: '梅州'}
检查结果: '梅州' == '梅州' → ✅ 允许
-- 场景2:梅州市级管理员尝试给云浮用户分配角色
target_user: {id: 9, area: '云浮'}
current_user: {role: 'admin', area: '梅州'}
检查结果: '云浮' != '梅州' → ❌ 返回403错误
错误响应:
{
"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 |
请求示例:
# 移除用户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"
返回格式:
{
"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=梅州)
操作流程:
# 步骤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=云浮)
操作流程:
# 步骤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=梅州)
操作流程:
# 步骤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 角色数据
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 用户数据(按地区分组)
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 用户角色分配数据
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 完整示例
<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解析
// 解析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. 错误处理
// 403错误:跨地区操作
if (response.status === 403) {
ElMessage.error('权限不足:只能给同地区用户分配角色')
}
// 401错误:Token过期
if (response.status === 401) {
ElMessage.error('登录已过期,请重新登录')
router.push('/login')
}
3. 地区提示
// 市级管理员登录时,提示当前地区
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