--- icon: mdi:shield-account date: 2026-03-05 category: - 工作 - 项目总结 tag: - 权限管理 - RBAC - 系统设计 title: 权限管理系统文档 --- 权限管理系统文档 # 权限管理系统文档 > 本文档整合了权限管理系统的完整说明,包括系统架构、配置方法、实现细节和运维指南。 --- ## 📚 目录 1. [系统概述](#系统概述) 2. [核心概念](#核心概念) 3. [数据库表结构](#数据库表结构) 4. [权限架构设计](#权限架构设计) 5. [权限验证流程](#权限验证流程) 6. [菜单权限管理](#菜单权限管理) 7. [API权限管理](#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<系统资源配置相关操作权限 | | 运维管理 | 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` **请求参数**: ```json { "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` **返回数据**: ```json { "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` **请求参数**: ```json { "username": "admin", "password": "123456", "captcha": "1234", "captchaId": "uuid" } ``` **返回数据**: ```json { "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` ```java class UserAuthority { String token; // JWT Token long expireTime; // 过期时间(时间戳) Set userRights; // 权限位集合 String remoteIp; // 远程IP地址 } ``` ### 2. 缓存操作 **添加缓存**: ```java AuthorityResourceHandle.addUserAuth(username, userAuthority); ``` **获取缓存**: ```java UserAuthority auth = AuthorityResourceHandle.getUserAuth(username); ``` **删除缓存**: ```java 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 文件结构**: ```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:数据库查询** ```sql -- 查询所有权限常量 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 注解处理流程 1. **扫描**:`ServiceBusBeanAutoDefinitionConfig.definitionConsumer()` 方法扫描带有 `@BusService` 注解的接口 2. **验证**:检查注解有效性、服务名称、排除列表等 3. **过滤**:确保接口属于指定包名且未重复注册 #### 1.3 CGLIB 动态代理机制 **代理对象创建位置**:`ServiceBusBeanAutoDefinitionConfig.java:409-445` ```java 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 核心实现**: ```java @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 **属性自动注入机制**: ```java definition.getPropertyValues().add("interfaceClass", c); definition.getPropertyValues().add("serviceInfo", serviceInfo); definition.getPropertyValues().add("clientManager", clientManager); ``` Spring 框架内部通过反射机制自动调用对应的 setter 方法。 **Bean 实例化**: 当应用通过 `@Autowired` 或 `context.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 问题描述 在之前的权限设计中,**实时监控**和**录像回放**共用同一个权限位(权限位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` ```java // 录像回放: 操作员,实时监控->录像回放相关界面的浏览、查询权限 public static final int RECORD_PLAYBACK = 21; ``` ### 4.2 baseDict.json 配置 **文件**:`/home/liumangmang/IdeaProjects/PR7050/V1.00/resource/local_conf_template/baseDict.json` ```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**: ```sql SELECT * FROM cfgauthconst WHERE right = 21; ``` ### 6.2 cfgresourceright 表 **中文配置文件**:`dbupdate_cfg_zh_CN.xml` ```xml insert into cfgresourceright values(7, 21); ``` **英文配置文件**:`dbupdate_cfg_en_US.xml` ```xml insert into cfgresourceright values(11, 21); ``` ### 6.3 角色权限配置示例 **操作员**:增加录像回放权限 ```sql UPDATE user_role_info SET role_authority = role_authority | 2097152 WHERE role_name = '操作员'; ``` **监控操作员**:拥有实时监控和录像回放权限 ```sql UPDATE user_role_info SET role_authority = role_authority | 2097216 WHERE role_name = '监控操作员'; ``` **录像查询员**:只拥有录像回放权限 ```sql 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.xml` 和 `dbupdate_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**: ```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 代码验证 ```bash # 搜索所有使用RECORD_PLAYBACK权限的API grep -r "RECORD_PLAYBACK" --include="*.java" platapp/ ``` ### 11.2 数据库验证 ```sql -- 查看权限常量 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)` - **参数**: ```json { "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` - **权限**:无需特殊权限 - **参数**: ```json { "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)` (如添加权限位6:`roleAuthority |= 64`) - **删除权限**:`roleAuthority &= ~(1 << right)` (如删除权限位6:`roleAuthority &= ~64`) - **判断权限**:`(roleAuthority & (1 << right)) != 0` (如判断是否有权限位6:`(roleAuthority & 64) != 0`) ### Q7: 权限位可以复用吗? A: 可以,但需要遵循以下原则: - **相同功能的不同操作**可以使用同一权限位(如查看、编辑、删除) - **不同功能模块**应该使用独立的权限位(如实时监控和录像回放) - **权限粒度适中**:避免过细(每个按钮一个权限位)或过粗(整个系统一个权限位) ### Q8: 如何实现"只能看实时监控"和"只能看录像回放"的权限控制? A: 需要为实时监控和录像回放分配独立的权限位: 1. 实时监控使用权限位6(REAL_TIME_MONITORING) 2. 录像回放使用权限位21(RECORD_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_id` 和 `right` 字段上建立索引 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