Files
leaudit-platform-frontend/auth_doc/RBAC_COMPLETE_GUIDE_V3.3.md
T
2025-12-05 00:09:32 +08:00

32 KiB
Raw Blame History

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_normaluser_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:省级管理员的完整权限

前置条件

  • 当前用户:adminuser_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