权限管理系统文档
权限管理系统文档
权限管理系统文档
本文档整合了权限管理系统的完整说明,包括系统架构、配置方法、实现细节和运维指南。
📚 目录
系统概述
本系统采用基于RBAC(基于角色的访问控制)和权限位相结合的权限管理模型,实现了细粒度的权限控制,支持菜单、API、资源等多维度的权限管理。
核心特性
- ✅ 权限位控制:使用整数位运算实现权限组合
- ✅ 级联权限继承:子菜单权限自动传播到父菜单
- ✅ 双重验证机制:本地缓存 + 远程服务验证
- ✅ 灵活的菜单控制:支持权限控制、隐藏、无权隐藏等多种显示策略
- ✅ API动态管理:支持运行时修改API权限
- ✅ 多源登录支持:支持浏览器、HMI等多种登录方式
- ✅ 登录限制策略:支持禁止重复登录、同源登录、多源登录
- ✅ 服务总线架构:通过
@BusService注解实现透明的远程服务调用
核心概念
1. 用户(User)
用户是系统的使用者,具有以下属性:
| 属性 | 说明 |
|---|---|
| userId | 用户ID(主键) |
| username | 用户名(唯一标识) |
| userPassword | 用户密码 |
| accountStatus | 账户状态(0=正常,1=锁定等) |
| authenticationmode | 身份验证方式 |
| lockEndTime | 锁定结束时间 |
| updatePwdTime | 密码更新时间 |
| isInitialPassword | 是否初始密码 |
| lastLoginTime | 上次登录时间 |
| loginErrorTimes | 登录错误次数 |
| visitPeriod | 访问周期 |
| validDays | 有效天数 |
2. 角色(Role)
角色是权限的集合,用户通过角色获得权限:
| 属性 | 说明 |
|---|---|
| roleId | 角色ID(主键) |
| roleName | 角色名称 |
| roleAuthority | 角色权限(权限位组合,使用Long类型存储,支持位运算) |
角色权限存储示例:
- 角色权限
roleAuthority = 7表示拥有权限位 0、1、2(二进制:111) - 角色权限
roleAuthority = 5表示拥有权限位 0、2(二进制:101) - 使用
RightBitUtils.extract(roleAuthority)提取权限位集合
3. 权限位(Permission Bit)
权限位是系统中最小的权限单元,使用整数表示,支持位运算组合:
3.1 权限位原理
| 权限位 | 值 | 二进制 | 说明 |
|---|---|---|---|
| 权限位0 | 0 | 0 | 特殊权限(内置用户) |
| 权限位1 | 1 | 1 | 基础权限位 |
| 权限位2 | 2 | 10 | 基础权限位 |
| 权限位3 | 4 | 100 | 基础权限位 |
| 权限位n | 2^n | 1<<n | 第n个权限位 |
权限组合示例:
- 角色权限
roleAuthority = 7= 1 + 2 + 4 = 权限位1 + 权限位2 + 权限位3 - 角色权限
roleAuthority = 5= 1 + 4 = 权限位1 + 权限位3
3.2 实际项目权限位定义
本项目定义了以下权限位(参考 RightConstant.java):
| 权限位 | 值 | 权限名称 | 适用角色 | 说明 |
|---|---|---|---|---|
| 特殊权限 | -1 | SPECIAL_PERMISSION | 内置用户 | 系统管理相关界面操作权限 |
| 用户管理 | 0 | USER_MANAGEMENT | 用户管理员 | 用户权限配置中系统信息和用户管理 |
| 角色管理 | 1 | ROLE_MANAGEMENT | 用户管理员 | 角色管理菜单相关操作权限 |
| 白名单管理 | 2 | WHITELIST_MANAGEMENT | 用户管理员 | 网络白名单菜单相关操作权限 |
| 资源分配 | 3 | RESOURCE_ALLOCATION | 配置员 | 配置管理->系统资源配置相关操作权限 |
| 运维管理 | 4 | MAINTENANCE_MANAGEMENT | 配置员 | 运维管理相关界面操作权限 |
| 配置管理 | 5 | CONFIGURATION_MANAGEMENT | 配置员 | 配置管理相关界面操作权限 |
| 实时监视 | 6 | REAL_TIME_MONITORING | 操作员 | 实时监控相关界面的浏览、查询权限 |
| 设备控制 | 7 | DEVICE_CONTROL | 操作员 | 实时监控相关界面中摄像机、机器人、无人机、声纹相关设备的控制权限 |
| 巡视监控 | 8 | INSPECTION_MONITORING | 操作员 | 任务管理->巡视任务执行、日历、巡视任务详情相关界面中的操作权限 |
| 任务管理 | 9 | TASK_MANAGEMENT | 操作员 | 任务管理->巡视任务管理、检修区域设置相关界面中的操作权限 |
| 巡视结果确认 | 10 | INSPECTION_RESULTS_CONFIRMATION | 操作员 | 任务管理->巡视结果查询、巡视报告相关界面中的操作权限 |
| 巡视结果分析 | 11 | INSPECTION_RESULTS_ANALYSIS | 操作员 | 任务管理->巡视数据分析相关界面中的操作权限 |
| 联动记录查询 | 12 | INTERACTION_RECORDS_QUERY | 操作员 | 拓展功能->智能联动记录查询相关界面中的操作权限 |
| 统计数据查询 | 13 | STATISTICAL_DATA_QUERY | 操作员 | 查询统计->巡视设备可靠性统计、任务执行情况统计、算法指标查询相关界面中的操作权限 |
| 告警记录查询 | 14 | ALARM_RECORDS_QUERY | 操作员 | 查询统计->告警信息查询统计、缺陷信息查询统计、系统告警查询统计相关界面中的操作权限 |
| 审计数据管理 | 15 | AUDIT_DATA_MANAGEMENT | 审计员 | 配置管理->审计配置相关界面中的操作权限 |
| 审计记录查询 | 16 | AUDIT_RECORDS_QUERY | 审计员 | 查询统计->审计日志查询相关界面中的操作权限 |
| 三维浏览 | 17 | THREE_DIMENSIONAL_BROWSING | 操作员 | 立体巡视->三维浏览相关界面中的操作权限 |
| 三维配置 | 18 | THREE_DIMENSIONAL_CONFIGURATION | 配置员 | 立体巡视->三维配置和立体巡视->文件配置相关界面中的操作权限 |
| 算法管理 | 19 | ALGORITHM_MANAGEMENT | 操作员 | 拓展功能->算法更新相关界面中的操作权限 |
| 算法验证 | 20 | ALGORITHM_VERIFICATION | 操作员 | 拓展功能->算法验证相关界面中的操作权限 |
| 录像回放 | 21 | RECORD_PLAYBACK | 操作员 | 实时监控->录像回放相关界面的浏览、查询权限 |
4. 菜单(Menu)
菜单是系统功能的组织结构,具有以下属性:
| 属性 | 说明 |
|---|---|
| id | 菜单ID(主键) |
| nodeid | 菜单节点ID(唯一标识,用于前后端交互) |
| name | 菜单名称 |
| url | 菜单URL(可以为空,占位符菜单) |
| pid | 父菜单ID |
| ctrl | 是否需要权限控制(true=需要,false=不需要) |
| hide | 是否隐藏(true=隐藏,false=显示) |
| invalidHide | 无权时是否隐藏(true=隐藏,false=不隐藏) |
菜单权限关联:
- 菜单与权限位通过
cfg_resource_right表关联 - 一个菜单可以绑定多个权限位
- 子菜单的权限会自动传播到父菜单
5. API接口
API接口是后端暴露的功能接口,具有以下属性:
| 属性 | 说明 |
|---|---|
| id | API ID(主键) |
| url | API URL(支持占位符,如 /api/video/{id}) |
| method | HTTP方法(GET/POST/PUT/DELETE等) |
| right | 权限位(绑定的权限位) |
API权限验证:
- 使用
@ApiRight注解标记API需要的权限位 - 支持多个权限位组合:
@ApiRight(value = {6, 21}) - 支持权限位或运算:
@ApiRight(required = false)
数据库表结构
1. 用户表(user_info)
| 字段 | 类型 | 说明 |
|---|---|---|
| userId | BIGINT | 用户ID(主键) |
| username | VARCHAR(50) | 用户名(唯一索引) |
| userPassword | VARCHAR(255) | 用户密码(加密存储) |
| accountStatus | INT | 账户状态(0=正常,1=锁定) |
| authenticationmode | INT | 身份验证方式 |
| lockEndTime | DATETIME | 锁定结束时间 |
| updatePwdTime | DATETIME | 密码更新时间 |
| isInitialPassword | INT | 是否初始密码(0=否,1=是) |
| lastLoginTime | DATETIME | 上次登录时间 |
| loginErrorTimes | INT | 登录错误次数 |
| visitPeriod | INT | 访问周期 |
| validDays | INT | 有效天数 |
2. 角色表(user_role_info)
| 字段 | 类型 | 说明 |
|---|---|---|
| roleId | BIGINT | 角色ID(主键) |
| roleName | VARCHAR(50) | 角色名称 |
| roleAuthority | BIGINT | 角色权限(权限位组合) |
权限位计算示例:
- 权限位6(实时监控):
1 << 6 = 64 - 权限位21(录像回放):
1 << 21 = 2097152 - 同时拥有权限位6和21:
64 | 2097152 = 2097216
3. 用户角色关联表(user_role_mapping)
| 字段 | 类型 | 说明 |
|---|---|---|
| userId | BIGINT | 用户ID(外键) |
| roleId | BIGINT | 角色ID(外键) |
4. 菜单表(cfg_menu)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT | 菜单ID(主键) |
| nodeid | VARCHAR(50) | 菜单节点ID(唯一索引) |
| name | VARCHAR(100) | 菜单名称 |
| url | VARCHAR(255) | 菜单URL |
| pid | INT | 父菜单ID |
| ctrl | TINYINT | 是否需要权限控制(0=否,1=是) |
| hide | TINYINT | 是否隐藏(0=否,1=是) |
| invalidHide | TINYINT | 无权时是否隐藏(0=否,1=是) |
5. 菜单权限关联表(cfg_resource_right)
| 字段 | 类型 | 说明 |
|---|---|---|
| resourceId | INT | 菜单ID(外键,关联cfg_menu.id) |
| right | INT | 权限位 |
联合主键:(resourceId, right)
6. API权限表(cfg_api)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT | API ID(主键) |
| url | VARCHAR(255) | API URL |
| method | VARCHAR(10) | HTTP方法 |
| right | INT | 权限位 |
7. 权限常量表(cfg_auth_const)
| 字段 | 类型 | 说明 |
|---|---|---|
| right | INT | 权限位(主键) |
| description | VARCHAR(100) | 权限描述 |
| createTime | DATETIME | 创建时间 |
| updateTime | DATETIME | 更新时间 |
权限架构设计
1. 权限架构层次
用户(User)
↓ 拥有
角色(Role)
↓ 包含
权限位(Permission Bit)
↓ 绑定到
菜单(Menu)/ API(API)2. 权限验证架构
前端请求
↓
Spring Security / Token验证
↓
权限拦截器(AuthorityCacheValidateHandle)
↓
本地权限缓存(AuthorityResourceHandle)
↓ 权限不存在
远程服务验证(@BusService → AuthorityExpandService)
↓
权限验证通过/拒绝3. 权限缓存机制
本地缓存:
- 存储位置:
AuthorityResourceHandle.userRightMap - 缓存键:用户名
- 缓存内容:
UserAuthority(Token、过期时间、权限位集合)
远程验证:
- 验证服务:
AuthorityExpandService - 通信协议:Thrift(通过
@BusService注解) - 缓存策略:远程验证结果缓存到本地
权限验证流程
1. 用户登录流程
1. 用户提交登录请求(用户名、密码)
↓
2. UserLoginController.login() 验证用户名密码
↓
3. 查询用户信息和角色权限
↓
4. 生成JWT Token
↓
5. AuthorityResourceHandle 缓存用户权限到本地
↓
6. 返回Token和用户信息给前端关键代码:
- 登录控制器:
UserLoginController.java - 权限缓存:
AuthorityResourceHandle.java:36-51 - Token生成:
JwtUtils.java
2. API权限验证流程
1. 前端发起API请求(携带Token)
↓
2. Spring Security验证Token有效性
↓
3. 权限拦截器拦截请求
↓
4. AuthorityCacheValidateHandle 验证权限
↓
5. 从本地缓存获取用户权限
↓
6. 检查API注解 @ApiRight 的权限要求
↓
7. 权限位匹配验证
↓
8. 通过:执行API方法;拒绝:抛出 AuthorityValidateFailedException关键代码:
- 权限拦截器:
AuthorityCacheValidateHandle.java - 权限缓存:
AuthorityResourceHandle.java:124-150 - 权限验证:
APIAuthorityHandle.java
3. 菜单权限过滤流程
1. 前端请求菜单列表(携带Token)
↓
2. AuthorityResourceController.getUserAuthorityResource()
↓
3. AuthorityResourceHandle.getAuthMenus(username)
↓
4. 从本地缓存获取用户权限位
↓
5. 查询完整菜单树
↓
6. 过滤用户无权访问的菜单
↓
7. 级联处理:子菜单有权限,父菜单自动显示
↓
8. 移除无权且设置为invalidHide的菜单
↓
9. 返回过滤后的菜单树给前端关键代码:
- 菜单查询:
AuthorityResourceController.java:46-56 - 菜单过滤:
AuthorityResourceHandle.java:57-98 - 菜单级联:
AuthorityResourceHandle.java:100-122
菜单权限管理
1. 菜单权限绑定
接口:POST /api/authority/resource/update/menu/right
请求参数:
{
"nodeId": "LXHF",
"rights": [21]
}处理逻辑:
- 根据nodeId查询菜单ID
- 删除菜单原有的所有权限绑定
- 批量插入新的权限绑定
- 更新子菜单的权限(自动继承)
- 更新父菜单的权限(自动传播)
关键代码:AuthorityResourcesServiceImpl.java:74-105
2. 菜单权限查询
接口:GET /api/authority/resource/get/bind/rights?nodeId=LXHF
返回数据:
{
"code": 200,
"data": [
{
"right": 21,
"description": "录像回放",
"bitHash": "0x000000200000"
}
]
}关键代码:AuthorityResourceController.java:129-145
3. 菜单树查询
接口:GET /api/authority/resource/get/menu/tree
返回数据:完整的菜单树结构,包含每个菜单的权限信息
关键代码:AuthorityResourceController.java:65-73
API权限管理
1. API权限绑定
接口:POST /api/authority/resource/add/api/right
请求参数:
apiId: API IDright: 权限位
处理逻辑:
- 根据apiId查询API信息
- 更新API权限到数据库(
cfg_api表) - 更新内存中的API权限缓存(
APIAuthorityHandle)
关键代码:AuthorityResourceController.java:184-197
2. API权限移除
接口:POST /api/authority/resource/remove/api/right
请求参数:
apiId: API IDright: 权限位
处理逻辑:
- 根据apiId查询API信息
- 删除API权限(从数据库和内存缓存中)
关键代码:AuthorityResourceController.java:200-214
3. API权限重置
接口:POST /api/authority/resource/reset/api/authority
处理逻辑:
- 清空内存中的所有API权限缓存
- 从数据库重新加载API权限到内存缓存
- 扫描所有Controller方法,恢复默认权限配置
关键代码:AuthorityResourceController.java:217-224
用户登录流程
1. 登录接口
接口:POST /api/authority/login
请求参数:
{
"username": "admin",
"password": "123456",
"captcha": "1234",
"captchaId": "uuid"
}返回数据:
{
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"username": "admin",
"roleName": "管理员",
"userRights": [0, 1, 2, 3, 4, 5, 6, 21]
}
}关键代码:UserLoginController.java
2. 登出接口
接口:POST /api/authority/logout
处理逻辑:
- 验证Token有效性
- 清除用户权限缓存:
AuthorityResourceHandle.deleteUserAuth(username) - 记录登出日志
关键代码:UserLogoutController.java:50-86
3. 登录限制策略
系统支持三种登录限制策略:
| 策略 | 说明 | 实现方式 |
|---|---|---|
| 禁止重复登录 | 同一用户只能在一个设备登录 | 新登录踢出旧登录 |
| 同源登录 | 同一用户只能在相同来源登录 | 根据登录来源限制 |
| 多源登录 | 同一用户可以在多个设备登录 | 允许多设备同时在线 |
配置方式:在系统配置中设置登录策略
关键代码:UserLoginController.java 中的登录限制逻辑
权限缓存机制
1. 缓存结构
缓存位置:AuthorityResourceHandle.userRightMap
缓存键:用户名(String)
缓存值:UserAuthority
class UserAuthority {
String token; // JWT Token
long expireTime; // 过期时间(时间戳)
Set<Integer> userRights; // 权限位集合
String remoteIp; // 远程IP地址
}2. 缓存操作
添加缓存:
AuthorityResourceHandle.addUserAuth(username, userAuthority);获取缓存:
UserAuthority auth = AuthorityResourceHandle.getUserAuth(username);删除缓存:
AuthorityResourceHandle.deleteUserAuth(username);关键代码:AuthorityResourceHandle.java:124-150
3. 缓存清理时机
- 用户登出:清除当前用户权限缓存
- 角色权限变更:清除所有受影响用户的权限缓存
- Token过期:自动清理过期缓存
- 手动清理:管理员可以通过管理接口清理缓存
权限常量配置
1. 权限常量配置文件
权限常量的配置存储在 baseDict.json 文件中,该文件在服务启动时被读取并写入到数据库的 cfgauthconst 表。
配置文件路径优先级:
配置文件路径(最高优先级)
- 配置项:
sunri-configuration.file-path - 来源:Spring Boot 配置文件(application.yml/properties)
- 示例:如果配置了
sunri-configuration.file-path=/custom/path,则读取/custom/path/baseDict.json
- 配置项:
环境变量 CYGPRODUCT
- 环境变量:
CYGPRODUCT - 路径格式:
/home/sunri/.{CYGPRODUCT}_resource/local_conf/baseDict.json - 示例:
CYGPRODUCT=xs→/home/sunri/.xs_resource/local_conf/baseDict.jsonCYGPRODUCT=patrol→/home/sunri/.patrol_resource/local_conf/baseDict.json
- 环境变量:
默认路径(最低优先级)
- 默认路径:
/home/sunri/.jk_resource/local_conf/baseDict.json - 触发条件:当以上两个配置都不存在时使用
- 默认路径:
不同环境的默认路径:
| 环境变量 CYGPRODUCT | 默认路径 |
|---|---|
| 未设置 | /home/sunri/.jk_resource/local_conf/baseDict.json |
CYGPRODUCT=xs | /home/sunri/.xs_resource/local_conf/baseDict.json |
CYGPRODUCT=patrol | /home/sunri/.patrol_resource/local_conf/baseDict.json |
CYGPRODUCT=jk | /home/sunri/.jk_resource/local_conf/baseDict.json |
2. 权限常量加载流程
加载时机:
- 触发时机:服务启动时通过
@PostConstruct注解自动执行 - 执行类:
AuthoritySignConstHandle.java - 执行方法:
loadAuthConstFile() - 执行条件:当
sunri-service-auth服务启动时自动执行
加载流程:
服务启动
↓
AuthoritySignConstHandle.@PostConstruct
↓
loadAuthConstFile() 方法执行
↓
读取 baseDict.json 配置文件(按优先级查找)
↓
解析 JSON 中的 "auth.authConst" 数组
↓
清空数据库表 cfgauthconst (clearIfPresent)
↓
批量插入权限常量数据 (insertLoadedConfig)
↓
写入 cfgauthconst 表完成关键代码位置:
- Java 实现:
platform/02_platservice/07_sunri-service-auth/sunri-service-auth-core/src/main/java/com/sunri/model/signconst/AuthoritySignConstHandle.java:40-129 - Go 实现:
platform/sunri-infrastructure/runset/cygLocalConfigUtil.go:136-152
3. 配置文件示例
baseDict.json 文件结构:
{
"auth": {
"authConst": [
{
"description": "特殊权限",
"code": "0x000000000000"
},
{
"description": "用户管理",
"code": "0x000000000001"
},
{
"description": "实时监视",
"code": "0x000000000040"
},
{
"description": "录像回放",
"code": "0x000000200000"
}
]
}
}字段说明:
description: 权限位描述code: 权限位值(十六进制格式)- 特殊权限:
0x000000000000= 2^0 - 用户管理:
0x000000000001= 2^1 - 实时监视:
0x000000000040= 2^6 - 录像回放:
0x000000200000= 2^21
- 特殊权限:
4. 重要注意事项
⚠️ 权限常量写入机制:
自动覆盖:每次服务启动时,系统会先清空
cfgauthconst表,然后从baseDict.json文件重新写入所有权限常量不要直接修改数据库:
- ❌ 不要直接修改
cfgauthconst表,因为服务重启后会被覆盖 - ✅ 应该修改
baseDict.json配置文件,然后重启服务
- ❌ 不要直接修改
需要重启的服务:
sunri-service-auth(认证服务)- 重启后权限常量会重新加载到数据库
文件不存在处理:
- 如果
baseDict.json文件不存在,系统会从数据库cfgauthconst表读取权限常量到内存缓存 - 记录错误日志:"默认路径基础字典配置文件未找到"
- 如果
配置文件位置:
- 模板文件:
/home/liumangmang/IdeaProjects/PR7050/V1.00/resource/local_conf_template/baseDict.json - 运行时文件:根据上述优先级确定的路径
- 部署脚本:
packagemake/prs7050_install.sh会将模板文件复制到运行目录
- 模板文件:
5. 验证权限常量
可以通过以下方式验证权限常量是否正确配置:
方法1:数据库查询
-- 查询所有权限常量
SELECT * FROM cfgauthconst;
-- 查询录像回放权限
SELECT * FROM cfgauthconst WHERE right = 21;方法2:API接口查询
GET /api/authority/resource/get/const该接口会从 cfgauthconst 表读取数据并返回给前端,包含权限位ID、描述、位值等信息。
6. 权限常量管理
通过管理接口可以对权限常量进行增删改操作:
- 添加权限常量:
POST /api/authority/resource/add/right/const - 更新权限描述:
POST /api/authority/resource/update/right/const/desc - 删除权限常量:
POST /api/authority/resource/delete/right/const
注意:这些操作会同时更新数据库和 baseDict.json 配置文件,确保持久化。
服务总线架构
1. @BusService 注解与 CGLIB 动态代理机制
系统采用基于 @BusService 注解的服务总线架构,实现透明的远程服务调用。
1.1 @BusService 注解定义
位置:/platform/00_depends/00_sunri-bus-service-dependency-thrift/src/main/java/com/sunri/annotation/BusService.java
关键属性:
name()- 服务名称(如 "cygdevopsweb")dataclass()- 数据类别(如 "statistic")app()- 应用名称datatype()- 数据类型
1.2 注解处理流程
- 扫描:
ServiceBusBeanAutoDefinitionConfig.definitionConsumer()方法扫描带有@BusService注解的接口 - 验证:检查注解有效性、服务名称、排除列表等
- 过滤:确保接口属于指定包名且未重复注册
1.3 CGLIB 动态代理机制
代理对象创建位置:ServiceBusBeanAutoDefinitionConfig.java:409-445
if (c.isInterface()) {
// 验证接口和注解
BusService service = (BusService) c.getAnnotation(BusService.class);
if (service == null || "".equals(service.name()) || exclude.contains(service.name())) {
continue;
}
// 创建服务信息
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.setPaname(StringUtils.isEmpty(service.app()) ? busClientConfig.getPaname() : service.app());
serviceInfo.setServname(service.name());
// 注册Bean定义
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(c);
GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
definition.getPropertyValues().add("interfaceClass", c);
definition.getPropertyValues().add("serviceInfo", serviceInfo);
definition.getPropertyValues().add("clientManager", clientManager);
definition.setBeanClass(BusClientFactoryBean.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
String name = toLowerCaseFirstOne(c.getSimpleName());
dbf.registerBeanDefinition(name, definition);
}BusClientFactoryBean 核心实现:
@Override
public T getObject() throws Exception {
enhancer.setSuperclass(interfaceClass);
interceptor.setClientManager(clientManager);
interceptor.setServiceInfo(serviceInfo);
enhancer.setCallback(interceptor);
return (T) enhancer.create();
}BusClientInterceptor 拦截器:
- 拦截所有接口方法调用
- 将本地方法调用转换为远程服务调用
- 通过 Thrift 协议发送到远程服务端
1.4 Spring Bean 生命周期集成
Bean 定义注册:
- 触发位置:
ServiceBusBeanAutoDefinitionConfig.postProcessBeanDefinitionRegistry() - 执行时机:Spring 容器启动时,在
BeanDefinitionRegistryPostProcessor阶段 - 结果:将每个带
@BusService注解的接口注册为由BusClientFactoryBean创建的 Bean
属性自动注入机制:
definition.getPropertyValues().add("interfaceClass", c);
definition.getPropertyValues().add("serviceInfo", serviceInfo);
definition.getPropertyValues().add("clientManager", clientManager);Spring 框架内部通过反射机制自动调用对应的 setter 方法。
Bean 实例化:
当应用通过 @Autowired 或 context.getBean() 获取接口实例时:
- Spring 发现该 Bean 由
BusClientFactoryBean创建 - 调用
BusClientFactoryBean.getObject() - CGLIB 创建动态代理对象
- 返回代理对象给应用
1.5 工作流程总结
应用启动 → ServiceBusBeanAutoDefinitionConfig 扫描 @BusService 注解的接口
↓
为每个接口创建 BusClientFactoryBean 定义并注册到 Spring 容器
↓
应用代码获取接口实例 (@Autowired 或 getBean)
↓
Spring 调用 BusClientFactoryBean.getObject()
↓
CGLIB 创建接口的动态代理对象
↓
应用调用接口方法时由 BusClientInterceptor 拦截
↓
拦截器将方法调用转换为远程服务调用
↓
通过 Thrift 协议发送到远程服务端执行
↓
返回结果给调用方1.6 关键特点
- 透明性:客户端代码无需关心底层网络通信,接口调用如同本地调用一样简单
- 解耦性:客户端只依赖接口定义,无需知道服务实现的位置和细节
- 动态性:运行时动态生成代理类,支持灵活的服务定位和负载均衡
1.7 技术栈
- CGLIB:用于动态代理类生成
- Spring Framework:用于 Bean 生命周期管理和依赖注入
- Thrift:用于远程服务通信协议
- Zookeeper:用于服务注册与发现
- Curator:用于Zookeeper客户端连接管理
录像回放权限配置
1. 权限隔离背景
1.1 问题描述
在之前的权限设计中,实时监控和录像回放共用同一个权限位(权限位6:REAL_TIME_MONITORING),这导致:
- 权限粒度过粗:无法区分"只能看实时监控"和"只能看录像回放"的用户
- 业务需求不满足:某些场景需要细粒度的权限控制
1.2 解决方案
为不同的功能模块分配独立的权限位:
实时监视: REAL_TIME_MONITORING = 6
录像回放: RECORD_PLAYBACK = 21(新增)2. 权限隔离的优势
| 优势 | 说明 |
|---|---|
| 灵活配置 | 支持各种权限组合场景 |
| 清晰明了 | 权限职责边界清晰 |
| 易于扩展 | 新增功能只需新增权限位 |
| 便于维护 | 权限变更不影响其他功能 |
3. 典型应用场景
使用独立权限位后,可以支持以下场景:
| 场景 | 实时监控权限(6) | 录像回放权限(21) | 说明 |
|---|---|---|---|
| 实时监控员 | ✅ | ❌ | 只能查看实时监控画面 |
| 录像查询员 | ❌ | ✅ | 只能查看历史录像回放 |
| 监控操作员 | ✅ | ✅ | 既能看实时监控也能看录像回放 |
| 数据分析员 | ❌ | ✅ | 只能进行录像数据分析 |
4. 权位配置
4.1 权限常量定义
文件:platapp/04_sunri-web-patrol/sunri-web-center-utils/src/main/java/com/sunri/constant/RightConstant.java:58
// 录像回放: 操作员,实时监控->录像回放相关界面的浏览、查询权限
public static final int RECORD_PLAYBACK = 21;4.2 baseDict.json 配置
文件:/home/liumangmang/IdeaProjects/PR7050/V1.00/resource/local_conf_template/baseDict.json
{
"auth": {
"authConst": [
{
"description": "实时监视",
"code": "0x000000000040"
},
{
"description": "录像回放",
"code": "0x000000200000"
}
]
}
}权限位计算:
- 实时监视:
0x000000000040= 2^6 = 64 - 录像回放:
0x000000200000= 2^21 = 2,097,152
5. Controller API权限注解更新
5.1 HighDefinitionVideoEquipmentController.java
文件:platapp/04_sunri-web-patrol/sunri-web-center-cygbusiness/cygbusiness-model/src/main/java/com/sunri/model/controller/account/HighDefinitionVideoEquipmentController.java
修改的API方法:
getVideoFileList- 录像-获取文件列表getVideoFileUrl- 录像-获取回放地址stopVideoFileUrl- 录像-停止回放/推流videoPlayBackControl- 录像-回放控制videoProgress- 录像-进度获取getAfterCalibrationStartTime- 录像-获取校准后开始时间
权限变更:从 @ApiRight(value = {REAL_TIME_MONITORING}) 改为 @ApiRight(value = {RECORD_PLAYBACK})
5.2 ResourceController.java
文件:platapp/04_sunri-web-patrol/sunri-web-center-standalone/standalone-patrol/src/main/java/com/sunri/controller/ResourceController.java
修改的API方法:
downloadVideoChunk- 下载文件到浏览器(分片)getDownloadChunkSize- 获取下载文件分片数
权限变更:从 @ApiRight(value = {REAL_TIME_MONITORING, ALGORITHM_VERIFICATION}) 改为 @ApiRight(value = {REAL_TIME_MONITORING, RECORD_PLAYBACK, ALGORITHM_VERIFICATION})
5.3 HighDefinitionVideoMonitorController.java
文件:platapp/04_sunri-web-patrol/sunri-web-center-cygbusiness/cygbusiness-model/src/main/java/com/sunri/model/controller/monitor/HighDefinitionVideoMonitorController.java
修改的API方法:
videoDownload- 录像-开始下载/手动录像
权限变更:从 @ApiRight(value = {DEVICE_CONTROL}) 改为 @ApiRight(value = {RECORD_PLAYBACK})
6. 数据库配置
6.1 cfgauthconst 表
系统启动时会自动从 baseDict.json 文件读取权限常量并写入数据库,无需手动配置。
验证SQL:
SELECT * FROM cfgauthconst WHERE right = 21;6.2 cfgresourceright 表
中文配置文件:dbupdate_cfg_zh_CN.xml
<!-- 录像回放菜单ID=7 -->
<sql>insert into cfgresourceright values(7, 21);</sql>英文配置文件:dbupdate_cfg_en_US.xml
<!-- 录像回放菜单ID=11 -->
<sql>insert into cfgresourceright values(11, 21);</sql>6.3 角色权限配置示例
操作员:增加录像回放权限
UPDATE user_role_info SET role_authority = role_authority | 2097152 WHERE role_name = '操作员';监控操作员:拥有实时监控和录像回放权限
UPDATE user_role_info SET role_authority = role_authority | 2097216 WHERE role_name = '监控操作员';录像查询员:只拥有录像回放权限
UPDATE user_role_info SET role_authority = role_authority | 2097152 WHERE role_name = '录像查询员';7. 实施步骤
7.1 代码修改(已完成)
- ✅ 在
RightConstant.java中添加RECORD_PLAYBACK = 21常量 - ✅ 更新录像回放相关API的权限注解
- ✅ 创建数据库配置脚本
7.2 数据库配置(需要手动执行)
- 备份数据库
- 执行数据库升级脚本(
dbupdate_cfg_zh_CN.xml和dbupdate_cfg_en_US.xml) - 根据实际菜单数据,确认录像回放菜单的ID或nodeid
- 更新角色权限配置
- 验证配置是否正确
7.3 系统部署
- 编译代码
- 部署应用
- 执行数据库配置脚本
- 相关用户重新登录以获取新权限
8. 权限位计算
- 权限位21的值: 2^21 = 2,097,152
- 实时监控权限位6的值: 2^6 = 64
- 同时拥有实时监控和录像回放权限: 64 + 2,097,152 = 2,097,216
9. 注意事项
- 数据库备份: 执行数据库脚本前,请务必备份数据库
- 菜单确认: 脚本中的菜单ID需要根据实际情况进行确认和修改
- 角色权限: 角色权限配置需要根据实际业务需求进行调整
- 用户登录: 修改角色权限后,相关用户需要重新登录才能生效
- 权限测试: 部署后请进行全面的权限测试,确保权限控制正确
10. 回滚方案
如需回滚,请执行以下操作:
- 恢复代码版本
- 执行回滚SQL脚本
- 重新部署应用
- 相关用户重新登录
回滚SQL:
-- 删除菜单权限绑定
DELETE FROM cfg_resource_right WHERE right = 21;
-- 移除角色权限(权限位21)
UPDATE user_role_info SET role_authority = role_authority & ~2097152 WHERE role_authority & 2097152 > 0;11. 验证方法
11.1 代码验证
# 搜索所有使用RECORD_PLAYBACK权限的API
grep -r "RECORD_PLAYBACK" --include="*.java" platapp/11.2 数据库验证
-- 查看权限常量
SELECT * FROM cfg_auth_const WHERE right = 21;
-- 查看菜单权限绑定
SELECT mr.*, m.name, m.nodeid
FROM cfg_resource_right mr
JOIN cfg_menu m ON mr.resource_id = m.id
WHERE mr.right = 21;
-- 查看角色权限
SELECT role_id, role_name, role_authority,
(role_authority & 2097152) > 0 AS has_record_playback_permission
FROM user_role_info;11.3 功能验证
- 创建只拥有录像回放权限的用户角色
- 使用该角色登录系统
- 验证只能访问录像回放功能,不能访问实时监控功能
- 创建只拥有实时监控权限的用户角色
- 使用该角色登录系统
- 验证只能访问实时监控功能,不能访问录像回放功能
管理接口说明
1. 权限资源接口
基础路径:/api/authority/resource
1.1 查询用户菜单权限资源
- 接口:
GET /api/authority/resource/get/user/resource - 权限:无需特殊权限(登录即可)
- 说明:获取当前用户的菜单权限资源
1.2 查询权限位常量列表
- 接口:
GET /api/authority/resource/get/const - 权限:
@ApiRight(value = -1) - 说明:获取所有权限位常量
1.3 查询系统API列表
- 接口:
GET /api/authority/resource/get/api/list - 权限:
@ApiRight(value = -1) - 说明:查询所有系统API
1.4 修改菜单权限
- 接口:
POST /api/authority/resource/update/menu/right - 权限:
@ApiRight(value = -1) - 参数:
{ "nodeId": "LXHF", "rights": [21] } - 说明:修改菜单绑定的权限位
1.5 添加API权限
- 接口:
POST /api/authority/resource/add/api/right - 权限:
@ApiRight(value = -1) - 参数:
apiId,right - 说明:为API添加权限位
1.6 移除API权限
- 接口:
POST /api/authority/resource/remove/api/right - 权限:
@ApiRight(value = -1) - 参数:
apiId,right - 说明:移除API的权限位
2. 用户管理接口
基础路径:/api/authority/user
2.1 用户登录
- 接口:
POST /api/authority/login - 权限:无需特殊权限
- 参数:
{ "username": "admin", "password": "123456", "captcha": "1234", "captchaId": "uuid" }
2.2 用户登出
- 接口:
POST /api/authority/logout - 权限:
@ApiRight(required = false) - 说明:用户登出并清除权限缓存
3. 角色管理接口
基础路径:/api/authority/role
3.1 查询角色权限
- 接口:
GET /api/authority/role/query/right - 权限:
@ApiRight(value = -1) - 参数:
roleId - 说明:查询角色的权限信息
常见问题
Q1: 为什么菜单会级联继承权限?
A: 这是为了确保用户体验的连贯性。如果用户有子菜单的权限,但父菜单被隐藏,用户就无法访问子菜单。因此,系统会自动将子菜单的权限传播到父菜单。
Q2: 如何处理占位符URL的权限?
A: 系统会检测包含 {} 的URL,并将其存储在占位符URL映射表中。当用户访问具体URL时,系统会匹配占位符模式,使用占位符URL的权限配置。
Q3: 权限缓存多久更新一次?
A: 权限缓存在以下情况下会更新:
- 用户登录时
- 用户登出时
- 角色权限变更时
- Token过期时
Q4: 如何新增一个权限位?
A: 新增权限位需要完成以下步骤:
- 在
RightConstant.java中添加权限常量定义 - 在
baseDict.json文件中添加权限常量配置 - 更新相关菜单的权限配置(修改
cfg_resource_right表) - 更新相关API的
@ApiRight注解 - 更新角色权限配置(修改
user_role_info表的role_authority字段) - 重启
sunri-service-auth服务 - 通知相关用户重新登录以获取新权限
Q5: 为什么不同的功能模块需要使用独立的权限位?
A: 这是权限隔离原则的要求。如果不同的功能模块共用同一个权限位,会导致权限控制粒度过粗,无法满足细粒度的业务需求。例如:
- 问题场景:实时监控和录像回放共用权限位6,无法区分"只能看实时监控"和"只能看录像回放"的用户
- 解决方案:为实时监控分配权限位6,为录像回放分配权限位21,可以支持各种权限组合场景
Q6: 角色权限是如何存储和计算的?
A: 角色权限使用 Long 类型存储,采用位运算方式:
- 存储:
roleAuthority字段存储权限位的组合值,如roleAuthority = 7表示拥有权限位 0、1、2 - 计算:使用
RightBitUtils.extract(roleAuthority)提取权限位集合 - 添加权限:
roleAuthority |= (1 << right)(如添加权限位6:roleAuthority |= 64) - 删除权限:
roleAuthority &= ~(1 << right)(如删除权限位6:roleAuthority &= ~64) - 判断权限:
(roleAuthority & (1 << right)) != 0(如判断是否有权限位6:(roleAuthority & 64) != 0)
Q7: 权限位可以复用吗?
A: 可以,但需要遵循以下原则:
- 相同功能的不同操作可以使用同一权限位(如查看、编辑、删除)
- 不同功能模块应该使用独立的权限位(如实时监控和录像回放)
- 权限粒度适中:避免过细(每个按钮一个权限位)或过粗(整个系统一个权限位)
Q8: 如何实现"只能看实时监控"和"只能看录像回放"的权限控制?
A: 需要为实时监控和录像回放分配独立的权限位:
- 实时监控使用权限位6(REAL_TIME_MONITORING)
- 录像回放使用权限位21(RECORD_PLAYBACK)
- 为"实时监控员"角色配置权限位6
- 为"录像查询员"角色配置权限位21
- 为"监控操作员"角色配置权限位6和21
Q9: 用户修改角色权限后,如何让用户立即生效?
A: 有两种方式:
- 用户重新登录:用户登出后重新登录,系统会重新加载用户权限
- 通知用户重新登录:管理员修改角色权限后,系统会自动通知相关用户重新登录(参考
RoleAuthorityHandle.java:82-96)
Q10: 权限常量是如何写入数据库的?
A: 权限常量在服务启动时自动从 baseDict.json 文件读取并写入数据库的 cfgauthconst 表。具体流程参考"权限常量配置"章节。
性能优化建议
- 使用缓存:用户权限缓存到本地,减少远程调用
- 批量操作:批量查询菜单、权限,减少数据库访问
- 索引优化:在
cfg_resource_right表的resource_id和right字段上建立索引 - 异步处理:权限变更时异步通知用户更新缓存
- 连接池:使用连接池优化数据库连接
安全建议
- Token加密:JWT Token使用SM4加密存储
- Token过期:设置合理的Token过期时间
- 登录限制:启用登录限制策略,防止多设备登录
- 密码加密:用户密码使用加密算法存储
- 审计日志:记录权限变更、登录等操作日志
- HTTPS:生产环境使用HTTPS传输
- 权限最小化:为用户分配最小必要的权限
- 定期审计:定期审计用户权限和角色配置
版本历史
| 版本 | 日期 | 修改内容 | 修改人员 |
|---|---|---|---|
| 1.0.0 | 2026-03-05 | 初始版本,整合权限管理系统文档 | 系统管理员 |
| 1.1.0 | 2026-03-05 | 新增录像回放权限配置说明 | 系统管理员 |
| 1.2.0 | 2026-03-05 | 新增权限常量配置章节 | 系统管理员 |
| 1.3.0 | 2026-03-05 | 新增服务总线架构说明 | 系统管理员 |
参考文档
- 录像回放权限配置说明.md
- 录像回放权限配置脚本.sql
- 服务总线学习.md
- RightConstant.java
- AuthorityResourceController.java
- AuthoritySignConstHandle.java
- ServiceBusBeanAutoDefinitionConfig.java
