Files
MyBlog/src/work/project-summary/权限管理系统文档.md
T
liumangmang 83dc5bf7c6 docs(project-summary): 新增项目总结文档及内容
- 添加了“项目总结”侧边栏项,支持目录折叠,方便访问汇总内容
- 新增Cursor进阶指南文档,详解Agent模式、Composer与快捷键等高级功能
- 编写权限管理系统文档,包含系统设计、权限架构及详细使用说明
- 新添服务总线学习文档,解析@BusService注解与CGLIB动态代理机制
- 录制回放权限配置说明,新增录像回放权限位及权限隔离策略
- 提供数据库配置脚本及实施步骤,确保权限配置准确高效
2026-03-05 11:54:52 +08:00

41 KiB
Raw Blame History

icon, date, category, tag, title
icon date category tag title
mdi:shield-account 2026-03-05
工作
项目总结
权限管理
RBAC
系统设计
权限管理系统文档

权限管理系统文档

权限管理系统文档

本文档整合了权限管理系统的完整说明,包括系统架构、配置方法、实现细节和运维指南。


📚 目录

  1. 系统概述
  2. 核心概念
  3. 数据库表结构
  4. 权限架构设计
  5. 权限验证流程
  6. 菜单权限管理
  7. API权限管理
  8. 用户登录流程
  9. 权限缓存机制
  10. 权限常量配置
  11. 服务总线架构
  12. 录像回放权限配置
  13. 管理接口说明
  14. 常见问题
  15. 性能优化建议
  16. 安全建议

系统概述

本系统采用基于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和2164 | 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/ APIAPI

2. 权限验证架构

前端请求
  ↓
Spring Security / Token验证
  ↓
权限拦截器(AuthorityCacheValidateHandle
  ↓
本地权限缓存(AuthorityResourceHandle
  ↓ 权限不存在
远程服务验证(@BusService → AuthorityExpandService
  ↓
权限验证通过/拒绝

3. 权限缓存机制

本地缓存

  • 存储位置:AuthorityResourceHandle.userRightMap
  • 缓存键:用户名
  • 缓存内容:UserAuthorityToken、过期时间、权限位集合)

远程验证

  • 验证服务: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]
}

处理逻辑

  1. 根据nodeId查询菜单ID
  2. 删除菜单原有的所有权限绑定
  3. 批量插入新的权限绑定
  4. 更新子菜单的权限(自动继承)
  5. 更新父菜单的权限(自动传播)

关键代码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 ID
  • right: 权限位

处理逻辑

  1. 根据apiId查询API信息
  2. 更新API权限到数据库(cfg_api表)
  3. 更新内存中的API权限缓存(APIAuthorityHandle

关键代码AuthorityResourceController.java:184-197

2. API权限移除

接口POST /api/authority/resource/remove/api/right

请求参数

  • apiId: API ID
  • right: 权限位

处理逻辑

  1. 根据apiId查询API信息
  2. 删除API权限(从数据库和内存缓存中)

关键代码AuthorityResourceController.java:200-214

3. API权限重置

接口POST /api/authority/resource/reset/api/authority

处理逻辑

  1. 清空内存中的所有API权限缓存
  2. 从数据库重新加载API权限到内存缓存
  3. 扫描所有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

处理逻辑

  1. 验证Token有效性
  2. 清除用户权限缓存:AuthorityResourceHandle.deleteUserAuth(username)
  3. 记录登出日志

关键代码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. 缓存清理时机

  1. 用户登出:清除当前用户权限缓存
  2. 角色权限变更:清除所有受影响用户的权限缓存
  3. Token过期:自动清理过期缓存
  4. 手动清理:管理员可以通过管理接口清理缓存

权限常量配置

1. 权限常量配置文件

权限常量的配置存储在 baseDict.json 文件中,该文件在服务启动时被读取并写入到数据库的 cfgauthconst 表。

配置文件路径优先级

  1. 配置文件路径(最高优先级)

    • 配置项:sunri-configuration.file-path
    • 来源:Spring Boot 配置文件(application.yml/properties
    • 示例:如果配置了 sunri-configuration.file-path=/custom/path,则读取 /custom/path/baseDict.json
  2. 环境变量 CYGPRODUCT

    • 环境变量:CYGPRODUCT
    • 路径格式:/home/sunri/.{CYGPRODUCT}_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
  3. 默认路径(最低优先级)

    • 默认路径:/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. 重要注意事项

⚠️ 权限常量写入机制

  1. 自动覆盖:每次服务启动时,系统会先清空 cfgauthconst 表,然后从 baseDict.json 文件重新写入所有权限常量

  2. 不要直接修改数据库

    • 不要直接修改 cfgauthconst 表,因为服务重启后会被覆盖
    • 应该修改 baseDict.json 配置文件,然后重启服务
  3. 需要重启的服务

    • sunri-service-auth(认证服务)
    • 重启后权限常量会重新加载到数据库
  4. 文件不存在处理

    • 如果 baseDict.json 文件不存在,系统会从数据库 cfgauthconst 表读取权限常量到内存缓存
    • 记录错误日志:"默认路径基础字典配置文件未找到"
  5. 配置文件位置

    • 模板文件:/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;

方法2API接口查询

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 注解处理流程

  1. 扫描ServiceBusBeanAutoDefinitionConfig.definitionConsumer() 方法扫描带有 @BusService 注解的接口
  2. 验证:检查注解有效性、服务名称、排除列表等
  3. 过滤:确保接口属于指定包名且未重复注册

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 实例化 当应用通过 @Autowiredcontext.getBean() 获取接口实例时:

  1. Spring 发现该 Bean 由 BusClientFactoryBean 创建
  2. 调用 BusClientFactoryBean.getObject()
  3. CGLIB 创建动态代理对象
  4. 返回代理对象给应用

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 问题描述

在之前的权限设计中,实时监控录像回放共用同一个权限位(权限位6REAL_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 代码修改(已完成)

  1. RightConstant.java 中添加 RECORD_PLAYBACK = 21 常量
  2. 更新录像回放相关API的权限注解
  3. 创建数据库配置脚本

7.2 数据库配置(需要手动执行)

  1. 备份数据库
  2. 执行数据库升级脚本(dbupdate_cfg_zh_CN.xmldbupdate_cfg_en_US.xml
  3. 根据实际菜单数据,确认录像回放菜单的ID或nodeid
  4. 更新角色权限配置
  5. 验证配置是否正确

7.3 系统部署

  1. 编译代码
  2. 部署应用
  3. 执行数据库配置脚本
  4. 相关用户重新登录以获取新权限

8. 权限位计算

  • 权限位21的值: 2^21 = 2,097,152
  • 实时监控权限位6的值: 2^6 = 64
  • 同时拥有实时监控和录像回放权限: 64 + 2,097,152 = 2,097,216

9. 注意事项

  1. 数据库备份: 执行数据库脚本前,请务必备份数据库
  2. 菜单确认: 脚本中的菜单ID需要根据实际情况进行确认和修改
  3. 角色权限: 角色权限配置需要根据实际业务需求进行调整
  4. 用户登录: 修改角色权限后,相关用户需要重新登录才能生效
  5. 权限测试: 部署后请进行全面的权限测试,确保权限控制正确

10. 回滚方案

如需回滚,请执行以下操作:

  1. 恢复代码版本
  2. 执行回滚SQL脚本
  3. 重新部署应用
  4. 相关用户重新登录

回滚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. 创建只拥有录像回放权限的用户角色
  2. 使用该角色登录系统
  3. 验证只能访问录像回放功能,不能访问实时监控功能
  4. 创建只拥有实时监控权限的用户角色
  5. 使用该角色登录系统
  6. 验证只能访问实时监控功能,不能访问录像回放功能

管理接口说明

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: 新增权限位需要完成以下步骤:

  1. RightConstant.java 中添加权限常量定义
  2. baseDict.json 文件中添加权限常量配置
  3. 更新相关菜单的权限配置(修改 cfg_resource_right 表)
  4. 更新相关API的 @ApiRight 注解
  5. 更新角色权限配置(修改 user_role_info 表的 role_authority 字段)
  6. 重启 sunri-service-auth 服务
  7. 通知相关用户重新登录以获取新权限

Q5: 为什么不同的功能模块需要使用独立的权限位?

A: 这是权限隔离原则的要求。如果不同的功能模块共用同一个权限位,会导致权限控制粒度过粗,无法满足细粒度的业务需求。例如:

  • 问题场景:实时监控和录像回放共用权限位6,无法区分"只能看实时监控"和"只能看录像回放"的用户
  • 解决方案:为实时监控分配权限位6,为录像回放分配权限位21,可以支持各种权限组合场景

Q6: 角色权限是如何存储和计算的?

A: 角色权限使用 Long 类型存储,采用位运算方式:

  • 存储roleAuthority 字段存储权限位的组合值,如 roleAuthority = 7 表示拥有权限位 0、1、2
  • 计算:使用 RightBitUtils.extract(roleAuthority) 提取权限位集合
  • 添加权限roleAuthority |= (1 << right) (如添加权限位6roleAuthority |= 64
  • 删除权限roleAuthority &= ~(1 << right) (如删除权限位6roleAuthority &= ~64
  • 判断权限(roleAuthority & (1 << right)) != 0 (如判断是否有权限位6(roleAuthority & 64) != 0

Q7: 权限位可以复用吗?

A: 可以,但需要遵循以下原则:

  • 相同功能的不同操作可以使用同一权限位(如查看、编辑、删除)
  • 不同功能模块应该使用独立的权限位(如实时监控和录像回放)
  • 权限粒度适中:避免过细(每个按钮一个权限位)或过粗(整个系统一个权限位)

Q8: 如何实现"只能看实时监控"和"只能看录像回放"的权限控制?

A: 需要为实时监控和录像回放分配独立的权限位:

  1. 实时监控使用权限位6REAL_TIME_MONITORING
  2. 录像回放使用权限位21RECORD_PLAYBACK
  3. 为"实时监控员"角色配置权限位6
  4. 为"录像查询员"角色配置权限位21
  5. 为"监控操作员"角色配置权限位6和21

Q9: 用户修改角色权限后,如何让用户立即生效?

A: 有两种方式:

  1. 用户重新登录:用户登出后重新登录,系统会重新加载用户权限
  2. 通知用户重新登录:管理员修改角色权限后,系统会自动通知相关用户重新登录(参考 RoleAuthorityHandle.java:82-96

Q10: 权限常量是如何写入数据库的?

A: 权限常量在服务启动时自动从 baseDict.json 文件读取并写入数据库的 cfgauthconst 表。具体流程参考"权限常量配置"章节。


性能优化建议

  1. 使用缓存:用户权限缓存到本地,减少远程调用
  2. 批量操作:批量查询菜单、权限,减少数据库访问
  3. 索引优化:在 cfg_resource_right 表的 resource_idright 字段上建立索引
  4. 异步处理:权限变更时异步通知用户更新缓存
  5. 连接池:使用连接池优化数据库连接

安全建议

  1. Token加密JWT Token使用SM4加密存储
  2. Token过期:设置合理的Token过期时间
  3. 登录限制:启用登录限制策略,防止多设备登录
  4. 密码加密:用户密码使用加密算法存储
  5. 审计日志:记录权限变更、登录等操作日志
  6. HTTPS:生产环境使用HTTPS传输
  7. 权限最小化:为用户分配最小必要的权限
  8. 定期审计:定期审计用户权限和角色配置

版本历史

版本 日期 修改内容 修改人员
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