From 6ea653ca43b22be0ee42e2baebfbbdc6ece1684e Mon Sep 17 00:00:00 2001 From: chenbowen Date: Mon, 26 Jan 2026 16:37:45 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E4=B8=8D=E5=86=8D=E9=99=90=E5=88=B6=20?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=A0=BC=E5=BC=8F=E4=B8=BA=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E5=AD=97=E6=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/admin/auth/vo/AuthLoginReqVO.java | 5 ++--- .../system/controller/admin/auth/vo/AuthTestLoginReqVO.java | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java index 2e4e2813..61d840cf 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthLoginReqVO.java @@ -6,7 +6,6 @@ import com.zt.plat.module.system.enums.social.SocialTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -22,8 +21,8 @@ public class AuthLoginReqVO extends CaptchaVerificationReqVO { @Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ztyuanma") @NotEmpty(message = "登录账号不能为空") - @Length(min = 4, max = 16, message = "账号长度为 4-16 位") - @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") + @Length(min = 1, max = 16, message = "账号长度为 1-16 位") +// @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") private String username; @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthTestLoginReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthTestLoginReqVO.java index 5e9ab950..b43587ee 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthTestLoginReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/auth/vo/AuthTestLoginReqVO.java @@ -2,7 +2,6 @@ package com.zt.plat.module.system.controller.admin.auth.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -19,7 +18,7 @@ public class AuthTestLoginReqVO { @Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "ztyuanma") @NotEmpty(message = "登录账号不能为空") @Length(min = 4, max = 16, message = "账号长度为 4-16 位") - @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") +// @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") private String username; @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") From 2227271d084ae4eed2bded24222e59abc750f381 Mon Sep 17 00:00:00 2001 From: wuzongyong <13203449218@163.com> Date: Wed, 28 Jan 2026 09:13:23 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(permission):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8F=9C=E5=8D=95=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增菜单数据规则表和角色菜单数据规则关联表 - 实现菜单数据权限切面和处理器 - 添加数据规则条件和变量枚举 - 实现变量替换工具类和规则构建逻辑 - 在权限分配中集成菜单数据规则关联功能 - 优化部门ID解析逻辑,支持从用户信息中获取默认部门 - 添加菜单组件查询方法和公司访问上下文拦截器改进 --- docs/菜单数据权限使用文档.md | 617 ++++++++++++++++++ sql/dm/20260126菜单数据规则表.sql | 73 +++ sql/dm/ruoyi-vue-pro-dm8.sql | 71 ++ .../ZtDataPermissionAutoConfiguration.java | 19 + .../annotation/PermissionData.java | 29 + .../aop/MenuDataPermissionAspect.java | 89 +++ .../MenuDataPermissionConfiguration.java | 36 + .../context/MenuDataRuleContextHolder.java | 41 ++ .../dal/mapper/MenuDataPermissionMapper.java | 51 ++ .../handler/MenuDataPermissionHandler.java | 41 ++ .../model/MenuDataRuleDTO.java | 33 + .../service/MenuDataRuleLoader.java | 31 + .../service/impl/MenuDataRuleLoaderImpl.java | 45 ++ .../util/DataRuleVariableUtils.java | 92 +++ .../util/MenuDataPermissionRule.java | 156 +++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../web/CompanyVisitContextInterceptor.java | 22 +- .../permission/DataRuleConditionEnum.java | 60 ++ .../permission/DataRuleVariableEnum.java | 53 ++ .../permission/MenuDataRuleController.java | 77 +++ .../permission/PermissionController.java | 7 + .../admin/permission/RoleController.java | 2 + .../vo/menudatarule/MenuDataRuleRespVO.java | 46 ++ .../menudatarule/MenuDataRuleSaveReqVO.java | 53 ++ .../PermissionAssignRoleMenuItemReqVO.java | 5 + .../permission/MenuDataRuleConvert.java | 26 + .../dataobject/permission/MenuDataRuleDO.java | 67 ++ .../permission/RoleMenuDataRuleDO.java | 42 ++ .../mysql/permission/MenuDataRuleMapper.java | 59 ++ .../dal/mysql/permission/MenuMapper.java | 4 + .../permission/RoleMenuDataRuleMapper.java | 43 ++ .../oauth2/OAuth2TokenServiceImpl.java | 3 + .../permission/MenuDataRuleService.java | 72 ++ .../PageComponentMappingService.java | 56 ++ .../service/permission/PermissionService.java | 17 + .../permission/PermissionServiceImpl.java | 41 ++ .../impl/MenuDataRuleServiceImpl.java | 109 ++++ 37 files changed, 2288 insertions(+), 1 deletion(-) create mode 100644 docs/菜单数据权限使用文档.md create mode 100644 sql/dm/20260126菜单数据规则表.sql create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/annotation/PermissionData.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/aop/MenuDataPermissionAspect.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/context/MenuDataRuleContextHolder.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/dal/mapper/MenuDataPermissionMapper.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/handler/MenuDataPermissionHandler.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/model/MenuDataRuleDTO.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/MenuDataRuleLoader.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/impl/MenuDataRuleLoaderImpl.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/DataRuleVariableUtils.java create mode 100644 zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/MenuDataPermissionRule.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleConditionEnum.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleVariableEnum.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/MenuDataRuleController.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleRespVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleSaveReqVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/permission/MenuDataRuleConvert.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/MenuDataRuleDO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/RoleMenuDataRuleDO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuDataRuleMapper.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/RoleMenuDataRuleMapper.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/MenuDataRuleService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PageComponentMappingService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/impl/MenuDataRuleServiceImpl.java diff --git a/docs/菜单数据权限使用文档.md b/docs/菜单数据权限使用文档.md new file mode 100644 index 00000000..fe539ea9 --- /dev/null +++ b/docs/菜单数据权限使用文档.md @@ -0,0 +1,617 @@ +# 菜单数据权限使用文档 + +## 📖 目录 + +1. [功能介绍](#功能介绍) +2. [架构说明](#架构说明) +3. [开发指南](#开发指南) +4. [配置指南](#配置指南) +5. [注意事项](#注意事项) +6. [完整示例](#完整示例) +7. [常见问题](#常见问题) + +--- + +## 功能介绍 + +### 什么是菜单数据权限? + +菜单数据权限是一种**基于菜单的动态数据过滤机制**,允许管理员为不同的角色配置不同的数据查询规则,实现细粒度的数据权限控制。 + +### 核心特性 + +- ✅ **动态配置**:无需修改代码,通过页面配置即可实现数据过滤 +- ✅ **基于角色**:不同角色可以看到不同的数据 +- ✅ **灵活规则**:支持多种条件(等于、大于、IN、LIKE等) +- ✅ **变量支持**:支持动态变量(如当前用户ID、部门ID等) +- ✅ **SQL级别**:在SQL层面过滤,性能高效 + +### 使用场景 + +1. **部门数据隔离**:用户只能查看自己部门的数据 +2. **创建人过滤**:用户只能查看自己创建的数据 +3. **状态过滤**:某些角色只能查看特定状态的数据 +4. **自定义规则**:根据业务需求配置任意过滤条件 + +--- + +## 架构说明 + +### 模块结构 + +``` +zt-framework/ +└── zt-spring-boot-starter-biz-data-permission/ # 框架模块 + └── menudatapermission/ + ├── annotation/ # @PermissionData 注解 + ├── aop/ # AOP 切面 + ├── context/ # ThreadLocal 上下文 + ├── handler/ # MyBatis 拦截器 + ├── model/ # DTO 模型 + └── util/ # 工具类 + +zt-module-system/ +└── zt-module-system-server/ # 业务模块 + └── framework/permission/ + └── MenuDataRuleLoaderImpl.java # 规则加载器实现 +``` + +### 工作流程 + +``` +1. 用户访问页面(如:角色管理) + ↓ +2. Controller 方法上有 @PermissionData 注解 + ↓ +3. AOP 切面拦截,根据 pageComponent 查询菜单ID + ↓ +4. 加载该菜单下用户角色关联的数据规则 + ↓ +5. 将规则存入 ThreadLocal + ↓ +6. MyBatis 执行查询时,拦截器读取规则 + ↓ +7. 构建 SQL WHERE 条件并添加到查询中 + ↓ +8. 返回过滤后的数据 + ↓ +9. finally 块清理 ThreadLocal +``` + +--- + +## 前置条件 + +### ✅ 检查 Maven 依赖 + +在使用菜单数据权限功能前,**必须确保业务模块已引入框架依赖**。 + +#### 1. 检查依赖是否存在 + +打开业务模块的 `pom.xml` 文件(如 `zt-module-xxx-server/pom.xml`),检查是否包含以下依赖: + +```xml + + com.zt.plat + zt-spring-boot-starter-biz-data-permission + +``` + +#### 2. 如何检查? + +**方法1:查看 pom.xml** +```bash +# 在项目根目录执行 +grep -r "zt-spring-boot-starter-biz-data-permission" zt-module-xxx/zt-module-xxx-server/pom.xml +``` + +**方法2:在 IDE 中查看** +- IDEA:打开 `pom.xml`,搜索 `zt-spring-boot-starter-biz-data-permission` +- 或者查看 Maven 依赖树 + +#### 3. 如果没有依赖,如何添加? + +在业务模块的 `pom.xml` 中添加: + +```xml + + + + + + com.zt.plat + zt-spring-boot-starter-biz-data-permission + + +``` + +#### 4. 已包含该依赖的模块 + +以下模块已默认包含该依赖,可直接使用: +- ✅ `zt-module-system-server` +- ⚠️ 其他业务模块需自行检查 + +--- + +## 开发指南 + +### 步骤1:在 Controller 方法上添加注解 + +在需要数据过滤的查询方法上添加 `@PermissionData` 注解: + +```java +import com.zt.plat.framework.datapermission.core.menudatapermission.annotation.PermissionData; + +@RestController +@RequestMapping("/system/role") +public class RoleController { + + @GetMapping("/page") + @PermissionData(pageComponent = "system/role/index") // 指定页面组件路径 + public CommonResult> getRolePage(RolePageReqVO pageReqVO) { + PageResult pageResult = roleService.getRolePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, RoleRespVO.class)); + } +} +``` + +### 步骤2:确定 pageComponent 值 + +`pageComponent` 是前端页面组件的路径,用于关联菜单: + +- **格式**:`模块名/功能名/页面名` +- **示例**: + - `system/role/index` - 角色管理页面 + - `system/user/index` - 用户管理页面 + - `system/dept/index` - 部门管理页面 + +**如何查找 pageComponent?** + +1. 打开前端项目,找到对应的 Vue 文件路径 +2. 例如:`src/views/system/role/index.vue` +3. pageComponent 就是:`system/role/index` + +### 步骤3:在菜单表中配置 component 字段 + +确保数据库 `system_menu` 表中,该菜单的 `component` 字段值与 `pageComponent` 一致: + +```sql +-- 示例:角色管理菜单 +UPDATE system_menu +SET component = 'system/role/index' +WHERE id = 角色管理菜单ID; +``` + +### 注解参数说明 + +```java +@PermissionData( + pageComponent = "system/role/index", // 必填:页面组件路径 + enable = true // 可选:是否启用,默认 true +) +``` + +**何时设置 `enable = false`?** + +当某个方法不需要数据权限过滤时(如管理员查看所有数据): + +```java +@GetMapping("/all") +@PermissionData(pageComponent = "system/role/index", enable = false) +public CommonResult> getAllRoles() { + // 返回所有角色,不受数据权限限制 +} +``` + +--- + +## 配置指南 + +### 步骤1:配置菜单 + +1. 登录系统,进入 **系统管理 > 菜单管理** +2. 找到需要配置数据权限的菜单(如:角色管理) +3. 确认该菜单的 **组件路径** 字段与代码中的 `pageComponent` 一致 + +### 步骤2:配置数据规则 + +1. 在菜单列表中,点击对应菜单的 **"数据规则"** 按钮 +2. 点击 **"新增规则"** 按钮 +3. 填写规则信息: + +#### 规则字段说明 + +| 字段 | 说明 | 示例 | +|------|------|------| +| **规则名称** | 规则的描述性名称 | `只看自己创建的角色` | +| **规则字段** | 数据库表的字段名 | `creator`、`dept_id`、`status` | +| **规则条件** | 比较条件 | `=`、`IN`、`LIKE` 等 | +| **规则值** | 比较的值,支持变量 | `#{userId}`、`#{deptId}` | +| **状态** | 启用/禁用 | 启用 | +| **排序** | 规则执行顺序 | 0 | + +#### 支持的规则条件 + +| 条件 | 说明 | 示例 | +|------|------|------| +| `=` | 等于 | `creator = #{userId}` | +| `!=` | 不等于 | `status != 0` | +| `>` | 大于 | `create_time > '2024-01-01'` | +| `<` | 小于 | `sort < 100` | +| `>=` | 大于等于 | `age >= 18` | +| `<=` | 小于等于 | `price <= 1000` | +| `IN` | 包含 | `dept_id IN (#{deptIds})` | +| `NOT_IN` | 不包含 | `status NOT_IN (0,1)` | +| `LIKE` | 模糊匹配 | `name LIKE '张'` | +| `NOT_LIKE` | 不匹配 | `name NOT_LIKE '测试'` | +| `IS_NULL` | 为空 | `deleted_time IS_NULL` | +| `IS_NOT_NULL` | 不为空 | `phone IS_NOT_NULL` | +| `BETWEEN` | 区间 | `age BETWEEN 18,60` | +| `NOT_BETWEEN` | 不在区间 | `score NOT_BETWEEN 0,60` | +| `SQL_RULE` | 自定义SQL | `(dept_id = 1 OR creator = 2)` | + +#### 支持的变量 + +| 变量 | 说明 | 示例 | +|------|------|------| +| `#{userId}` | 当前用户ID | `creator = #{userId}` | +| `#{username}` | 当前用户名 | `create_by = #{username}` | +| `#{deptId}` | 当前部门ID | `dept_id = #{deptId}` | +| `#{companyId}` | 当前公司ID | `company_id = #{companyId}` | +| `#{tenantId}` | 当前租户ID | `tenant_id = #{tenantId}` | +| `#{deptIds}` | 用户所有部门ID | `dept_id IN (#{deptIds})` | +| `#{companyIds}` | 用户所有公司ID | `company_id IN (#{companyIds})` | +| `#{postIds}` | 用户所有岗位ID | `post_id IN (#{postIds})` | + +#### SQL_RULE 类型详解 + +`SQL_RULE` 是一种特殊的规则类型,允许你直接编写自定义 SQL 表达式,实现更复杂的数据过滤逻辑。 + +**什么时候使用 SQL_RULE?** + +- ✅ 需要 `OR` 逻辑连接多个条件 +- ✅ 需要组合多个字段的复杂判断 +- ✅ 需要嵌套条件或括号分组 +- ✅ 标准规则条件无法满足业务需求 + +**配置方法** + +| 字段 | 配置值 | +|------|--------| +| 规则条件 | 选择 `SQL_RULE` | +| 规则值 | 直接填写 SQL WHERE 条件表达式 | +| 规则字段 | 可以留空(不使用) | + +**配置示例** + +1. **OR 逻辑 - 查看自己创建的或自己部门的数据** + +``` +规则条件:SQL_RULE +规则值:(creator = #{userId} OR dept_id = #{deptId}) +``` + +生成的 SQL: +```sql +WHERE (creator = '123' OR dept_id = '456') +``` + +2. **多字段组合 - 特定部门的已启用数据** + +``` +规则条件:SQL_RULE +规则值:(dept_id IN (#{deptIds}) AND status = 1) +``` + +生成的 SQL: +```sql +WHERE (dept_id IN ('100', '101', '102') AND status = 1) +``` + +3. **复杂嵌套条件 - 管理员或本部门负责人** + +``` +规则条件:SQL_RULE +规则值:(role_type = 'admin' OR (dept_id = #{deptId} AND is_leader = 1)) +``` + +生成的 SQL: +```sql +WHERE (role_type = 'admin' OR (dept_id = '456' AND is_leader = 1)) +``` + +4. **时间范围 + 状态过滤** + +``` +规则条件:SQL_RULE +规则值:(create_time >= '2024-01-01' AND status IN (1, 2)) +``` + +生成的 SQL: +```sql +WHERE (create_time >= '2024-01-01' AND status IN (1, 2)) +``` + +**工作原理** + +当规则条件为 `SQL_RULE` 时: + +1. 系统会忽略"规则字段"和"规则条件" +2. 直接使用"规则值"中的 SQL 表达式 +3. 先替换表达式中的变量(如 `#{userId}`) +4. 将替换后的表达式直接添加到 SQL WHERE 子句中 + +代码实现(MenuDataPermissionRule.java:64-67): +```java +// 处理 SQL_RULE 类型(自定义 SQL) +if ("SQL_RULE".equals(ruleConditions)) { + return actualValue; // 直接返回替换变量后的 SQL 表达式 +} +``` + +**⚠️ 重要警告** + +1. **SQL 注入风险** + - SQL_RULE 直接拼接到 SQL 中,存在注入风险 + - ✅ **安全做法**:只使用预定义变量(`#{userId}` 等) + - ❌ **危险做法**:不要在规则值中拼接用户输入的内容 + +2. **字段名必须正确** + - SQL_RULE 中的字段名必须与数据库表字段完全一致 + - 错误的字段名会导致 SQL 查询报错 + - 建议先在数据库中测试 SQL 语句 + +3. **括号很重要** + - 建议始终用括号包裹整个表达式:`(condition1 OR condition2)` + - 避免与其他规则或系统条件产生优先级问题 + +4. **变量替换** + - 变量会被替换为带引号的字符串值 + - 例如:`#{userId}` → `'123'` + - 数据库会自动处理类型转换 + +**SQL_RULE vs 普通规则对比** + +| 特性 | 普通规则 | SQL_RULE | +|------|---------|----------| +| 配置难度 | 简单,选择即可 | 需要 SQL 知识 | +| 灵活性 | 有限,单一条件 | 非常灵活,任意表达式 | +| 安全性 | 高,系统控制 | 需要注意 SQL 注入 | +| OR 逻辑 | ❌ 不支持 | ✅ 支持 | +| 嵌套条件 | ❌ 不支持 | ✅ 支持 | +| 多规则组合 | AND 连接 | 单个规则内实现 | +| 错误提示 | 友好 | SQL 错误信息 | + +**最佳实践** + +1. **优先使用普通规则**:能用普通规则解决的,不要用 SQL_RULE +2. **测试后再上线**:在测试环境验证 SQL 语句正确性 +3. **添加注释**:在规则名称中说明 SQL_RULE 的用途 +4. **定期审查**:定期检查 SQL_RULE 规则,删除不再使用的 +5. **权限控制**:限制能配置 SQL_RULE 的管理员权限 + +### 步骤3:关联角色 + +1. 配置完规则后,进入 **系统管理 > 角色管理** +2. 编辑需要应用规则的角色 +3. 在 **"数据权限"** 标签页中,勾选对应的菜单数据规则 +4. 保存角色配置 + +### 步骤4:测试验证 + +1. 使用该角色的用户登录系统 +2. 访问配置了数据规则的页面 +3. 验证数据是否按规则过滤 + +--- + +## 注意事项 + +### ⚠️ 重要提醒 + +#### 1. 规则字段必须与数据库表字段一致 + +**错误示例**: +``` +规则字段:dept_id_xxx +数据库字段:dept_id +结果:SQL 查询报错! +``` + +**正确做法**: +- 在配置规则前,先确认数据库表结构 +- 字段名必须完全一致(包括大小写) +- 前端已添加警告提示,请仔细阅读 + +#### 2. 只在菜单/页面级别配置规则 + +- ✅ **菜单/页面**(type=2):需要配置数据规则 +- ❌ **目录**(type=1):不需要配置 +- ❌ **按钮**(type=3):不需要配置 + +前端已自动隐藏目录和按钮的"数据规则"按钮。 + +#### 3. 多个规则使用 AND 连接 + +如果为同一个菜单配置了多个规则,它们会用 `AND` 连接: + +```sql +-- 规则1:creator = #{userId} +-- 规则2:status = 1 +-- 最终SQL: +WHERE creator = '当前用户ID' AND status = 1 +``` + +#### 4. 变量不存在时的处理 + +如果配置了不存在的变量(如 `#{unknownVar}`),系统会: +- 记录警告日志 +- 将变量替换为空字符串 +- 可能导致查询结果为空 + +**建议**:使用前端下拉框选择变量,避免手动输入错误。 + +#### 5. 性能考虑 + +- 数据规则在 SQL 层面过滤,性能较好 +- 但过多的规则会增加 SQL 复杂度 +- 建议每个菜单不超过 5 条规则 + +#### 6. 禁用数据权限的场景 + +某些查询不应该受数据权限限制,需要添加 `@DataPermission(enable = false)`: + +```java +// 示例:查询所有根级部门(不受数据权限限制) +@Override +@DataPermission(enable = false) +public List getTopLevelDeptList() { + // ... +} +``` + +--- + +## 完整示例 + +### 场景:角色管理 - 只看自己创建的角色 + +#### 1. 后端代码 + +```java +@RestController +@RequestMapping("/system/role") +public class RoleController { + + @Resource + private RoleService roleService; + + @GetMapping("/page") + @Operation(summary = "获得角色分页") + @PreAuthorize("@ss.hasPermission('system:role:query')") + @PermissionData(pageComponent = "system/role/index") // 添加数据权限注解 + public CommonResult> getRolePage(RolePageReqVO pageReqVO) { + PageResult pageResult = roleService.getRolePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, RoleRespVO.class)); + } +} +``` + +#### 2. 菜单配置 + +确保 `system_menu` 表中角色管理菜单的配置: + +```sql +SELECT id, name, component +FROM system_menu +WHERE name = '角色管理'; + +-- 结果: +-- id: 101 +-- name: 角色管理 +-- component: system/role/index ✅ 与代码中的 pageComponent 一致 +``` + +#### 3. 数据规则配置 + +在页面上配置规则: + +| 字段 | 值 | +|------|------| +| 规则名称 | 只看自己创建的角色 | +| 规则字段 | `creator` | +| 规则条件 | `=` | +| 规则值 | `#{userId}` | +| 状态 | 启用 | +| 排序 | 0 | + +#### 4. 角色关联 + +1. 进入角色管理,编辑"普通用户"角色 +2. 在"数据权限"标签页,勾选"只看自己创建的角色"规则 +3. 保存 + +#### 5. 生成的 SQL + +当普通用户(ID=123)查询角色列表时,实际执行的 SQL: + +```sql +SELECT * FROM system_role +WHERE deleted = 0 + AND tenant_id = 1 + AND creator = '123' -- 自动添加的数据权限条件 +ORDER BY sort ASC; +``` + +--- + +## 常见问题 + +### Q1: 为什么配置了规则但不生效? + +**可能原因**: + +1. ✅ 检查 Controller 方法是否添加了 `@PermissionData` 注解 +2. ✅ 检查 `pageComponent` 是否与菜单的 `component` 字段一致 +3. ✅ 检查规则是否启用(状态=启用) +4. ✅ 检查角色是否关联了该规则 +5. ✅ 检查用户是否拥有该角色 + +### Q2: 如何查看实际执行的 SQL? + +在 `application.yaml` 中开启 SQL 日志: + +```yaml +# 方式1:MyBatis Plus SQL 日志 +mybatis-plus: + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +# 方式2:Logback 日志 +logging: + level: + com.zt.plat.module.system.dal.mysql: debug +``` + +### Q3: 多个规则如何组合? + +多个规则使用 `AND` 连接,如果需要 `OR` 逻辑,使用 `SQL_RULE` 类型: + +``` +规则条件:SQL_RULE +规则值:(dept_id = #{deptId} OR creator = #{userId}) +``` + +### Q4: 如何配置"查看本部门及下级部门"的规则? + +这需要在业务层实现,菜单数据权限只支持简单的字段过滤。建议使用原有的部门数据权限功能。 + +### Q5: 规则字段配置错误会怎样? + +会导致 SQL 查询报错,因为数据库找不到该字段。前端已添加警告提示,请仔细检查。 + +--- + +## 总结 + +菜单数据权限提供了一种灵活、高效的数据过滤机制: + +✅ **开发简单**:只需添加一个注解 +✅ **配置灵活**:通过页面配置,无需修改代码 +✅ **性能高效**:SQL 层面过滤,不影响性能 +✅ **易于维护**:规则集中管理,便于调整 + +**最佳实践**: +1. 优先使用预定义变量,避免手动输入 +2. 规则字段必须与数据库表字段一致 +3. 合理使用规则,避免过度复杂 +4. 定期review规则配置,删除无用规则 + +--- + +## 技术支持 + +如有问题,请联系: +- 开发团队:ZT +- 文档版本:v1.0 +- 更新日期:2026-01-27 diff --git a/sql/dm/20260126菜单数据规则表.sql b/sql/dm/20260126菜单数据规则表.sql new file mode 100644 index 00000000..7fb4e0b3 --- /dev/null +++ b/sql/dm/20260126菜单数据规则表.sql @@ -0,0 +1,73 @@ +-- ---------------------------- +-- Table structure for system_menu_data_rule +-- ---------------------------- +CREATE TABLE system_menu_data_rule ( + id bigint NOT NULL PRIMARY KEY, + menu_id bigint NOT NULL, + rule_name varchar(100) NOT NULL, + rule_column varchar(100) DEFAULT NULL NULL, + rule_conditions varchar(20) NOT NULL, + rule_value varchar(500) NOT NULL, + status smallint DEFAULT 1 NOT NULL, + sort int DEFAULT 0 NOT NULL, + remark varchar(500) DEFAULT NULL NULL, + creator varchar(64) DEFAULT '' NULL, + create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater varchar(64) DEFAULT '' NULL, + update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted bit DEFAULT '0' NOT NULL, + tenant_id bigint DEFAULT 0 NOT NULL +); + +-- CREATE INDEX idx_menu_data_rule_menu ON system_menu_data_rule (menu_id); +-- CREATE INDEX idx_menu_data_rule_tenant ON system_menu_data_rule (tenant_id); + +COMMENT ON COLUMN system_menu_data_rule.id IS '规则ID'; +COMMENT ON COLUMN system_menu_data_rule.menu_id IS '菜单ID'; +COMMENT ON COLUMN system_menu_data_rule.rule_name IS '规则名称'; +COMMENT ON COLUMN system_menu_data_rule.rule_column IS '规则字段(数据库列名)'; +COMMENT ON COLUMN system_menu_data_rule.rule_conditions IS '规则条件(=、>、<、IN、LIKE等)'; +COMMENT ON COLUMN system_menu_data_rule.rule_value IS '规则值(支持变量如#{userId}、#{deptId})'; +COMMENT ON COLUMN system_menu_data_rule.status IS '状态(0=禁用 1=启用)'; +COMMENT ON COLUMN system_menu_data_rule.sort IS '排序'; +COMMENT ON COLUMN system_menu_data_rule.remark IS '备注'; +COMMENT ON COLUMN system_menu_data_rule.creator IS '创建者'; +COMMENT ON COLUMN system_menu_data_rule.create_time IS '创建时间'; +COMMENT ON COLUMN system_menu_data_rule.updater IS '更新者'; +COMMENT ON COLUMN system_menu_data_rule.update_time IS '更新时间'; +COMMENT ON COLUMN system_menu_data_rule.deleted IS '是否删除'; +COMMENT ON COLUMN system_menu_data_rule.tenant_id IS '租户编号'; +COMMENT ON TABLE system_menu_data_rule IS '菜单数据规则表'; + +-- ---------------------------- +-- Table structure for system_role_menu_data_rule +-- ---------------------------- +CREATE TABLE system_role_menu_data_rule ( + id bigint NOT NULL PRIMARY KEY, + role_id bigint NOT NULL, + menu_id bigint NOT NULL, + data_rule_id bigint NOT NULL, + creator varchar(64) DEFAULT '' NULL, + create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater varchar(64) DEFAULT '' NULL, + update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted bit DEFAULT '0' NOT NULL, + tenant_id bigint DEFAULT 0 NOT NULL +); + +-- CREATE INDEX idx_rmdr_role ON system_role_menu_data_rule (role_id); +-- CREATE INDEX idx_rmdr_menu ON system_role_menu_data_rule (menu_id); +-- CREATE INDEX idx_rmdr_tenant ON system_role_menu_data_rule (tenant_id); +-- CREATE INDEX idx_rmdr_role_menu_rule ON system_role_menu_data_rule (role_id, menu_id, data_rule_id); + +COMMENT ON COLUMN system_role_menu_data_rule.id IS '自增主键'; +COMMENT ON COLUMN system_role_menu_data_rule.role_id IS '角色ID'; +COMMENT ON COLUMN system_role_menu_data_rule.menu_id IS '菜单ID'; +COMMENT ON COLUMN system_role_menu_data_rule.data_rule_id IS '数据规则ID'; +COMMENT ON COLUMN system_role_menu_data_rule.creator IS '创建者'; +COMMENT ON COLUMN system_role_menu_data_rule.create_time IS '创建时间'; +COMMENT ON COLUMN system_role_menu_data_rule.updater IS '更新者'; +COMMENT ON COLUMN system_role_menu_data_rule.update_time IS '更新时间'; +COMMENT ON COLUMN system_role_menu_data_rule.deleted IS '是否删除'; +COMMENT ON COLUMN system_role_menu_data_rule.tenant_id IS '租户编号'; +COMMENT ON TABLE system_role_menu_data_rule IS '角色菜单数据规则关联表'; diff --git a/sql/dm/ruoyi-vue-pro-dm8.sql b/sql/dm/ruoyi-vue-pro-dm8.sql index 1a0eba7e..83fc4b8f 100644 --- a/sql/dm/ruoyi-vue-pro-dm8.sql +++ b/sql/dm/ruoyi-vue-pro-dm8.sql @@ -4313,3 +4313,74 @@ VALUES (5022, 2, '日期格式', 'DATE', 'system_sequence_detail_rule_type', 0, 'success', '', '日期格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0), (5023, 3, '数字格式', 'NUMBER', 'system_sequence_detail_rule_type', 0, 'info', '', '数字格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0), (5024, 4, '自定义格式', 'CUSTOM', 'system_sequence_detail_rule_type', 0, 'warning', '', '自定义格式规则', 'admin', SYSDATE, 'admin', SYSDATE, 0); + +/* +增加菜单规则(system_menu_data_rule)和规则角色关联表(system_role_menu_data_rule),同增量脚本:sql/dm/20260126菜单数据规则表.sql +*/ +-- ---------------------------- +-- Table structure for system_menu_data_rule +-- ---------------------------- +CREATE TABLE system_menu_data_rule ( + id bigint NOT NULL PRIMARY KEY, + menu_id bigint NOT NULL, + rule_name varchar(100) NOT NULL, + rule_column varchar(100) DEFAULT NULL NULL, + rule_conditions varchar(20) NOT NULL, + rule_value varchar(500) NOT NULL, + status smallint DEFAULT 1 NOT NULL, + sort int DEFAULT 0 NOT NULL, + remark varchar(500) DEFAULT NULL NULL, + creator varchar(64) DEFAULT '' NULL, + create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater varchar(64) DEFAULT '' NULL, + update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted bit DEFAULT '0' NOT NULL, + tenant_id bigint DEFAULT 0 NOT NULL +); + + +COMMENT ON COLUMN system_menu_data_rule.id IS '规则ID'; +COMMENT ON COLUMN system_menu_data_rule.menu_id IS '菜单ID'; +COMMENT ON COLUMN system_menu_data_rule.rule_name IS '规则名称'; +COMMENT ON COLUMN system_menu_data_rule.rule_column IS '规则字段(数据库列名)'; +COMMENT ON COLUMN system_menu_data_rule.rule_conditions IS '规则条件(=、>、<、IN、LIKE等)'; +COMMENT ON COLUMN system_menu_data_rule.rule_value IS '规则值(支持变量如#{userId}、#{deptId})'; +COMMENT ON COLUMN system_menu_data_rule.status IS '状态(0=禁用 1=启用)'; +COMMENT ON COLUMN system_menu_data_rule.sort IS '排序'; +COMMENT ON COLUMN system_menu_data_rule.remark IS '备注'; +COMMENT ON COLUMN system_menu_data_rule.creator IS '创建者'; +COMMENT ON COLUMN system_menu_data_rule.create_time IS '创建时间'; +COMMENT ON COLUMN system_menu_data_rule.updater IS '更新者'; +COMMENT ON COLUMN system_menu_data_rule.update_time IS '更新时间'; +COMMENT ON COLUMN system_menu_data_rule.deleted IS '是否删除'; +COMMENT ON COLUMN system_menu_data_rule.tenant_id IS '租户编号'; +COMMENT ON TABLE system_menu_data_rule IS '菜单数据规则表'; + +-- ---------------------------- +-- Table structure for system_role_menu_data_rule +-- ---------------------------- +CREATE TABLE system_role_menu_data_rule ( + id bigint NOT NULL PRIMARY KEY, + role_id bigint NOT NULL, + menu_id bigint NOT NULL, + data_rule_id bigint NOT NULL, + creator varchar(64) DEFAULT '' NULL, + create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater varchar(64) DEFAULT '' NULL, + update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted bit DEFAULT '0' NOT NULL, + tenant_id bigint DEFAULT 0 NOT NULL +); + + +COMMENT ON COLUMN system_role_menu_data_rule.id IS '自增主键'; +COMMENT ON COLUMN system_role_menu_data_rule.role_id IS '角色ID'; +COMMENT ON COLUMN system_role_menu_data_rule.menu_id IS '菜单ID'; +COMMENT ON COLUMN system_role_menu_data_rule.data_rule_id IS '数据规则ID'; +COMMENT ON COLUMN system_role_menu_data_rule.creator IS '创建者'; +COMMENT ON COLUMN system_role_menu_data_rule.create_time IS '创建时间'; +COMMENT ON COLUMN system_role_menu_data_rule.updater IS '更新者'; +COMMENT ON COLUMN system_role_menu_data_rule.update_time IS '更新时间'; +COMMENT ON COLUMN system_role_menu_data_rule.deleted IS '是否删除'; +COMMENT ON COLUMN system_role_menu_data_rule.tenant_id IS '租户编号'; +COMMENT ON TABLE system_role_menu_data_rule IS '角色菜单数据规则关联表'; diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java index 1c7d9e91..6c95cc7c 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/config/ZtDataPermissionAutoConfiguration.java @@ -4,12 +4,15 @@ import com.zt.plat.framework.datapermission.core.aop.CompanyDataPermissionIgnore import com.zt.plat.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor; import com.zt.plat.framework.datapermission.core.aop.DeptDataPermissionIgnoreAspect; import com.zt.plat.framework.datapermission.core.db.DataPermissionRuleHandler; +import com.zt.plat.framework.datapermission.core.menudatapermission.aop.MenuDataPermissionAspect; +import com.zt.plat.framework.datapermission.core.menudatapermission.handler.MenuDataPermissionHandler; import com.zt.plat.framework.datapermission.core.rule.DataPermissionRule; import com.zt.plat.framework.datapermission.core.rule.DataPermissionRuleFactory; import com.zt.plat.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl; import com.zt.plat.framework.mybatis.core.util.MyBatisUtils; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.context.annotation.Bean; @@ -21,6 +24,7 @@ import java.util.List; * @author ZT */ @AutoConfiguration +@MapperScan("com.zt.plat.framework.datapermission.core.menudatapermission.dal.mapper") public class ZtDataPermissionAutoConfiguration { @Bean @@ -40,6 +44,21 @@ public class ZtDataPermissionAutoConfiguration { return handler; } + @Bean + public MenuDataPermissionHandler menuDataPermissionHandler(MybatisPlusInterceptor interceptor) { + // 创建菜单数据权限处理器 + MenuDataPermissionHandler handler = new MenuDataPermissionHandler(); + DataPermissionInterceptor inner = new DataPermissionInterceptor(handler); + // 添加到 interceptor 中,放在部门数据权限之后 + MyBatisUtils.addInterceptor(interceptor, inner, 1); + return handler; + } + + @Bean + public MenuDataPermissionAspect menuDataPermissionAspect() { + return new MenuDataPermissionAspect(); + } + @Bean public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() { return new DataPermissionAnnotationAdvisor(); diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/annotation/PermissionData.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/annotation/PermissionData.java new file mode 100644 index 00000000..4eed8a18 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/annotation/PermissionData.java @@ -0,0 +1,29 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.annotation; + +import java.lang.annotation.*; + +/** + * 数据权限注解 + * 标注在Controller方法上,表示该方法需要应用菜单数据规则 + * + * 参考JeecgBoot的实现方式 + * + * @author ZT + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PermissionData { + + /** + * 页面组件路径 + * 用于匹配菜单表中的component字段 + * 例如:system/role 对应角色管理菜单 + */ + String pageComponent(); + + /** + * 是否启用 + */ + boolean enable() default true; +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/aop/MenuDataPermissionAspect.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/aop/MenuDataPermissionAspect.java new file mode 100644 index 00000000..acb87fb8 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/aop/MenuDataPermissionAspect.java @@ -0,0 +1,89 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.aop; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.framework.datapermission.core.menudatapermission.annotation.PermissionData; +import com.zt.plat.framework.datapermission.core.menudatapermission.context.MenuDataRuleContextHolder; +import com.zt.plat.framework.datapermission.core.menudatapermission.model.MenuDataRuleDTO; +import com.zt.plat.framework.datapermission.core.menudatapermission.service.MenuDataRuleLoader; +import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * 菜单数据权限切面 + * 拦截 @PermissionData 注解的方法,加载并应用菜单数据规则 + * + * @author ZT + */ +@Aspect +@Component +@Slf4j +public class MenuDataPermissionAspect { + + @Resource + private MenuDataRuleLoader menuDataRuleLoader; + + @Around("@annotation(com.zt.plat.framework.datapermission.core.menudatapermission.annotation.PermissionData)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + // 获取方法签名 + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + PermissionData annotation = method.getAnnotation(PermissionData.class); + + // 如果未启用,直接执行 + if (!annotation.enable()) { + return joinPoint.proceed(); + } + + try { + // 获取当前用户ID + Long userId = SecurityFrameworkUtils.getLoginUserId(); + if (userId == null) { + log.debug("[MenuDataPermissionAspect][未登录,跳过菜单数据权限]"); + return joinPoint.proceed(); + } + + // 从注解获取pageComponent + String pageComponent = annotation.pageComponent(); + if (pageComponent == null || pageComponent.isEmpty()) { + log.warn("[MenuDataPermissionAspect][未指定pageComponent,跳过菜单数据权限]"); + return joinPoint.proceed(); + } + + // 根据pageComponent查询菜单ID + Long menuId = menuDataRuleLoader.getMenuIdByPageComponent(pageComponent); + if (menuId == null) { + log.warn("[MenuDataPermissionAspect][未找到匹配的菜单: {},跳过菜单数据权限]", pageComponent); + return joinPoint.proceed(); + } + + log.debug("[MenuDataPermissionAspect][pageComponent: {} 对应菜单ID: {}]", pageComponent, menuId); + + // 加载用户的菜单数据规则 + List rules = menuDataRuleLoader.getUserMenuDataRules(userId, menuId); + + if (CollUtil.isEmpty(rules)) { + log.debug("[MenuDataPermissionAspect][用户 {} 在菜单 {} 下无数据规则]", userId, menuId); + } else { + log.debug("[MenuDataPermissionAspect][用户 {} 在菜单 {} 下加载了 {} 条数据规则]", + userId, menuId, rules.size()); + // 将规则存入 ThreadLocal + MenuDataRuleContextHolder.setRules(rules); + } + + // 执行目标方法 + return joinPoint.proceed(); + } finally { + // 清理 ThreadLocal + MenuDataRuleContextHolder.clear(); + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java new file mode 100644 index 00000000..172fba3d --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/config/MenuDataPermissionConfiguration.java @@ -0,0 +1,36 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.config; + +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor; +import com.zt.plat.framework.datapermission.core.menudatapermission.handler.MenuDataPermissionHandler; +import com.zt.plat.framework.mybatis.core.util.MyBatisUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 菜单数据权限配置类 + * + * @author ZT + */ +@Configuration +@ComponentScan("com.zt.plat.framework.datapermission.core.menudatapermission") +public class MenuDataPermissionConfiguration { + + @Bean + @ConditionalOnBean(MybatisPlusInterceptor.class) + public MenuDataPermissionHandler menuDataPermissionHandler(MybatisPlusInterceptor interceptor) { + // 创建菜单数据权限处理器 + MenuDataPermissionHandler handler = new MenuDataPermissionHandler(); + + // 创建 DataPermissionInterceptor 拦截器 + DataPermissionInterceptor inner = new DataPermissionInterceptor(handler); + + // 添加到 interceptor 中 + // 添加在索引1的位置,在部门数据权限之后,但在分页插件之前 + MyBatisUtils.addInterceptor(interceptor, inner, 1); + + return handler; + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/context/MenuDataRuleContextHolder.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/context/MenuDataRuleContextHolder.java new file mode 100644 index 00000000..4f2abaa8 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/context/MenuDataRuleContextHolder.java @@ -0,0 +1,41 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.context; + +import com.zt.plat.framework.datapermission.core.menudatapermission.model.MenuDataRuleDTO; + +import java.util.List; + +/** + * 菜单数据规则上下文持有者 + * 使用 ThreadLocal 存储当前请求的菜单数据规则 + * + * @author ZT + */ +public class MenuDataRuleContextHolder { + + private static final ThreadLocal> CONTEXT = new ThreadLocal<>(); + + /** + * 设置当前请求的菜单数据规则 + * + * @param rules 规则列表 + */ + public static void setRules(List rules) { + CONTEXT.set(rules); + } + + /** + * 获取当前请求的菜单数据规则 + * + * @return 规则列表 + */ + public static List getRules() { + return CONTEXT.get(); + } + + /** + * 清除当前请求的菜单数据规则 + */ + public static void clear() { + CONTEXT.remove(); + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/dal/mapper/MenuDataPermissionMapper.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/dal/mapper/MenuDataPermissionMapper.java new file mode 100644 index 00000000..0c46c8b7 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/dal/mapper/MenuDataPermissionMapper.java @@ -0,0 +1,51 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.dal.mapper; + +import com.zt.plat.framework.datapermission.core.menudatapermission.model.MenuDataRuleDTO; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * 菜单数据权限 Mapper + * 用于查询菜单和菜单数据规则 + * + * @author ZT + */ +@Mapper +public interface MenuDataPermissionMapper { + + /** + * 根据页面组件路径获取菜单ID + * + * @param component 页面组件路径,如:system/role/index + * @return 菜单ID,如果未找到返回null + */ + @Select("SELECT id FROM system_menu WHERE component = #{component} AND deleted = 0 LIMIT 1") + Long selectMenuIdByComponent(@Param("component") String component); + + /** + * 获取用户在指定菜单下的有效数据规则 + * + * @param userId 用户ID + * @param menuId 菜单ID + * @return 数据规则列表 + */ + @TenantIgnore + @Select("") + List selectUserMenuDataRules(@Param("userId") Long userId, + @Param("menuId") Long menuId); +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/handler/MenuDataPermissionHandler.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/handler/MenuDataPermissionHandler.java new file mode 100644 index 00000000..c72a2b0e --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/handler/MenuDataPermissionHandler.java @@ -0,0 +1,41 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.handler; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler; +import com.zt.plat.framework.datapermission.core.menudatapermission.util.MenuDataPermissionRule; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Table; + +/** + * 菜单数据权限处理器 + * 基于 MyBatis Plus 的数据权限插件,应用菜单数据规则到 SQL 查询 + * + * @author ZT + */ +@Slf4j +public class MenuDataPermissionHandler implements MultiDataPermissionHandler { + + @Override + public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) { + try { + // 从 ThreadLocal 获取菜单数据规则并构建 SQL 条件 + String sqlCondition = MenuDataPermissionRule.buildSqlCondition(); + + if (StrUtil.isBlank(sqlCondition)) { + return null; + } + + // 将 SQL 字符串解析为 Expression 对象 + Expression expression = CCJSqlParserUtil.parseCondExpression(sqlCondition); + log.debug("[MenuDataPermissionHandler][表: {}, 添加条件: {}]", table.getName(), sqlCondition); + return expression; + + } catch (JSQLParserException e) { + log.error("[MenuDataPermissionHandler][解析 SQL 条件失败]", e); + return null; + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/model/MenuDataRuleDTO.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/model/MenuDataRuleDTO.java new file mode 100644 index 00000000..2e6ef64b --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/model/MenuDataRuleDTO.java @@ -0,0 +1,33 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.model; + +import lombok.Data; + +/** + * 菜单数据规则 DTO + * 用于在框架层传递菜单数据规则信息,避免依赖业务模块的数据库实体 + * + * @author ZT + */ +@Data +public class MenuDataRuleDTO { + + /** + * 规则字段(数据库列名) + */ + private String ruleColumn; + + /** + * 规则条件(=、>、<、IN、LIKE等) + */ + private String ruleConditions; + + /** + * 规则值(支持变量如#{userId}、#{deptId}) + */ + private String ruleValue; + + /** + * 状态(0=禁用 1=启用) + */ + private Integer status; +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/MenuDataRuleLoader.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/MenuDataRuleLoader.java new file mode 100644 index 00000000..2d1efe69 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/MenuDataRuleLoader.java @@ -0,0 +1,31 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.service; + +import com.zt.plat.framework.datapermission.core.menudatapermission.model.MenuDataRuleDTO; + +import java.util.List; + +/** + * 菜单数据规则加载器接口 + * 负责加载菜单和菜单数据规则 + * + * @author ZT + */ +public interface MenuDataRuleLoader { + + /** + * 根据页面组件路径获取菜单ID + * + * @param pageComponent 页面组件路径,如:system/role/index + * @return 菜单ID,如果未找到返回null + */ + Long getMenuIdByPageComponent(String pageComponent); + + /** + * 获取用户在指定菜单下的数据规则 + * + * @param userId 用户ID + * @param menuId 菜单ID + * @return 数据规则列表 + */ + List getUserMenuDataRules(Long userId, Long menuId); +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/impl/MenuDataRuleLoaderImpl.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/impl/MenuDataRuleLoaderImpl.java new file mode 100644 index 00000000..57311c64 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/service/impl/MenuDataRuleLoaderImpl.java @@ -0,0 +1,45 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.service.impl; + +import com.zt.plat.framework.datapermission.core.menudatapermission.dal.mapper.MenuDataPermissionMapper; +import com.zt.plat.framework.datapermission.core.menudatapermission.model.MenuDataRuleDTO; +import com.zt.plat.framework.datapermission.core.menudatapermission.service.MenuDataRuleLoader; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 菜单数据规则加载器默认实现 + * 直接从数据库加载菜单数据规则 + * + * @author ZT + */ +@Component +@Slf4j +public class MenuDataRuleLoaderImpl implements MenuDataRuleLoader { + + @Resource + private MenuDataPermissionMapper menuDataPermissionMapper; + + @Override + public Long getMenuIdByPageComponent(String pageComponent) { + try { + return menuDataPermissionMapper.selectMenuIdByComponent(pageComponent); + } catch (Exception e) { + log.error("[MenuDataRuleLoaderImpl][根据pageComponent查询菜单ID失败: {}]", pageComponent, e); + return null; + } + } + + @Override + public List getUserMenuDataRules(Long userId, Long menuId) { + try { + return menuDataPermissionMapper.selectUserMenuDataRules(userId, menuId); + } catch (Exception e) { + log.error("[MenuDataRuleLoaderImpl][查询用户菜单数据规则失败: userId={}, menuId={}]", + userId, menuId, e); + return List.of(); + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/DataRuleVariableUtils.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/DataRuleVariableUtils.java new file mode 100644 index 00000000..b368db61 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/DataRuleVariableUtils.java @@ -0,0 +1,92 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.util; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.security.core.LoginUser; +import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils; +import lombok.extern.slf4j.Slf4j; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 数据规则变量替换工具类 + * + * @author ZT + */ +@Slf4j +public class DataRuleVariableUtils { + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("#\\{([^}]+)}"); + + /** + * 替换规则值中的变量 + * + * @param ruleValue 规则值,如 "#{userId}" 或 "#{deptId}" + * @return 替换后的值 + */ + public static String replaceVariables(String ruleValue) { + if (StrUtil.isBlank(ruleValue)) { + return ruleValue; + } + + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); + if (loginUser == null) { + return ruleValue; + } + + Matcher matcher = VARIABLE_PATTERN.matcher(ruleValue); + StringBuffer result = new StringBuffer(); + + while (matcher.find()) { + String variable = matcher.group(1); + String replacement = getVariableValue(variable, loginUser); + matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); + } + matcher.appendTail(result); + + return result.toString(); + } + + /** + * 获取变量对应的值 + * + * @param variable 变量名,如 "userId", "deptId" + * @param loginUser 当前登录用户 + * @return 变量值 + */ + private static String getVariableValue(String variable, LoginUser loginUser) { + if (loginUser == null) { + return ""; + } + + switch (variable) { + case "userId": + return loginUser.getId() != null ? loginUser.getId().toString() : ""; + case "username": + return loginUser.getInfo() != null ? + loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_USERNAME, "") : ""; + case "deptId": + return loginUser.getVisitDeptId() != null ? + loginUser.getVisitDeptId().toString() : ""; + case "companyId": + return loginUser.getVisitCompanyId() != null ? + loginUser.getVisitCompanyId().toString() : ""; + case "tenantId": + return loginUser.getTenantId() != null ? + loginUser.getTenantId().toString() : ""; + case "deptIds": + return loginUser.getInfo() != null ? + loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_DEPT_IDS, "") : ""; + case "companyIds": + return loginUser.getInfo() != null ? + loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_COMPANY_IDS, "") : ""; + case "postIds": + return loginUser.getInfo() != null ? + loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS, "") : ""; + default: + // 未知变量,记录警告并返回空字符串 + log.warn("[DataRuleVariableUtils][未知的变量: {},请检查数据规则配置]", variable); + return ""; + } + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/MenuDataPermissionRule.java b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/MenuDataPermissionRule.java new file mode 100644 index 00000000..ef62d916 --- /dev/null +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/java/com/zt/plat/framework/datapermission/core/menudatapermission/util/MenuDataPermissionRule.java @@ -0,0 +1,156 @@ +package com.zt.plat.framework.datapermission.core.menudatapermission.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.zt.plat.framework.datapermission.core.menudatapermission.context.MenuDataRuleContextHolder; +import com.zt.plat.framework.datapermission.core.menudatapermission.model.MenuDataRuleDTO; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 菜单数据权限规则 + * 用于构建 SQL WHERE 条件 + * + * @author ZT + */ +@Slf4j +public class MenuDataPermissionRule { + + /** + * 构建 SQL WHERE 条件 + * + * @return SQL 条件字符串,如 "dept_id = 1 AND status = 1" + */ + public static String buildSqlCondition() { + List rules = MenuDataRuleContextHolder.getRules(); + if (CollUtil.isEmpty(rules)) { + return null; + } + + List conditions = rules.stream() + .filter(rule -> rule.getStatus() == 1) // 只处理启用的规则 + .map(MenuDataPermissionRule::buildSingleCondition) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toList()); + + if (CollUtil.isEmpty(conditions)) { + return null; + } + + // 多个规则用 AND 连接 + return "(" + String.join(" AND ", conditions) + ")"; + } + + /** + * 构建单个规则的 SQL 条件 + * + * @param rule 规则 + * @return SQL 条件字符串 + */ + private static String buildSingleCondition(MenuDataRuleDTO rule) { + String ruleColumn = rule.getRuleColumn(); + String ruleConditions = rule.getRuleConditions(); + String ruleValue = rule.getRuleValue(); + + // 替换变量 + String actualValue = DataRuleVariableUtils.replaceVariables(ruleValue); + + if (StrUtil.isBlank(ruleColumn) || StrUtil.isBlank(ruleConditions)) { + return null; + } + + // 处理 SQL_RULE 类型(自定义 SQL) + if ("SQL_RULE".equals(ruleConditions)) { + return actualValue; + } + + // 处理 IS_NULL 和 IS_NOT_NULL + if ("IS_NULL".equals(ruleConditions)) { + return ruleColumn + " IS NULL"; + } + if ("IS_NOT_NULL".equals(ruleConditions)) { + return ruleColumn + " IS NOT NULL"; + } + + // 其他条件需要有值 + if (StrUtil.isBlank(actualValue)) { + return null; + } + + // 构建条件 + switch (ruleConditions) { + case "=": + return ruleColumn + " = " + formatValue(actualValue); + case "!=": + return ruleColumn + " != " + formatValue(actualValue); + case ">": + return ruleColumn + " > " + formatValue(actualValue); + case "<": + return ruleColumn + " < " + formatValue(actualValue); + case ">=": + return ruleColumn + " >= " + formatValue(actualValue); + case "<=": + return ruleColumn + " <= " + formatValue(actualValue); + case "IN": + return ruleColumn + " IN (" + formatInValues(actualValue) + ")"; + case "NOT_IN": + return ruleColumn + " NOT IN (" + formatInValues(actualValue) + ")"; + case "LIKE": + return ruleColumn + " LIKE '%" + escapeSql(actualValue) + "%'"; + case "NOT_LIKE": + return ruleColumn + " NOT LIKE '%" + escapeSql(actualValue) + "%'"; + case "BETWEEN": + return buildBetweenCondition(ruleColumn, actualValue); + case "NOT_BETWEEN": + return "NOT " + buildBetweenCondition(ruleColumn, actualValue); + default: + log.warn("[buildSingleCondition][未知的规则条件: {}]", ruleConditions); + return null; + } + } + + /** + * 格式化值(添加引号) + * 统一给所有值加引号,数据库会自动处理类型转换 + */ + private static String formatValue(String value) { + // 统一添加单引号,避免达梦数据库等对字符串类型字段的类型转换错误 + return "'" + escapeSql(value) + "'"; + } + + /** + * 格式化 IN 条件的值 + */ + private static String formatInValues(String value) { + String[] values = value.split(","); + return java.util.Arrays.stream(values) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .map(MenuDataPermissionRule::formatValue) + .collect(Collectors.joining(", ")); + } + + /** + * 构建 BETWEEN 条件 + */ + private static String buildBetweenCondition(String column, String value) { + String[] values = value.split(","); + if (values.length != 2) { + log.warn("[buildBetweenCondition][BETWEEN 条件需要两个值,用逗号分隔: {}]", value); + return null; + } + return column + " BETWEEN " + formatValue(values[0].trim()) + " AND " + formatValue(values[1].trim()); + } + + /** + * SQL 转义,防止 SQL 注入 + */ + private static String escapeSql(String value) { + if (StrUtil.isBlank(value)) { + return value; + } + return value.replace("'", "''"); + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index df6ed09f..85d1ca70 100644 --- a/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/zt-framework/zt-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -2,3 +2,4 @@ com.zt.plat.framework.datapermission.config.ZtDataPermissionAutoConfiguration com.zt.plat.framework.datapermission.config.ZtDeptDataPermissionAutoConfiguration com.zt.plat.framework.datapermission.config.ZtBusinessDataPermissionAutoConfiguration com.zt.plat.framework.datapermission.config.ZtDataPermissionRpcAutoConfiguration +com.zt.plat.framework.datapermission.core.menudatapermission.config.MenuDataPermissionConfiguration diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java index f4688a21..99336abf 100644 --- a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java @@ -11,6 +11,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; +import java.util.Map; + /** * @author chenbowen */ @@ -45,13 +47,31 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor { } Long deptId = WebFrameworkUtils.getDeptId(request); - // 部门信息同样遵循“请求头 -> 请求属性 -> 登录缓存”的回退顺序 + // 部门信息同样遵循"请求头 -> 请求属性 -> 登录缓存"的回退顺序 if (deptId == null || deptId <= 0L) { Long attrDeptId = resolveLong(request.getAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_ID)); if (attrDeptId != null && attrDeptId > 0L) { deptId = attrDeptId; } else if (loginUser != null && loginUser.getVisitDeptId() != null && loginUser.getVisitDeptId() > 0L) { deptId = loginUser.getVisitDeptId(); + } else if (loginUser != null) { + // 如果以上都没有,尝试从用户info中获取第一个部门作为默认值 + Map info = loginUser.getInfo(); + if (info != null) { + String deptIdsStr = info.get(LoginUser.INFO_KEY_DEPT_IDS); + if (deptIdsStr != null && !deptIdsStr.isEmpty() && !"[]".equals(deptIdsStr)) { + try { + // 解析JSON数组,取第一个部门ID + deptIdsStr = deptIdsStr.trim(); + if (deptIdsStr.startsWith("[") && deptIdsStr.length() > 2) { + String firstId = deptIdsStr.substring(1, deptIdsStr.indexOf(']')).split(",")[0].trim(); + deptId = Long.parseLong(firstId); + } + } catch (Exception e) { + log.warn("[CompanyVisitContextInterceptor][解析用户默认部门失败: {}]", deptIdsStr, e); + } + } + } } } diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleConditionEnum.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleConditionEnum.java new file mode 100644 index 00000000..9064c4d6 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleConditionEnum.java @@ -0,0 +1,60 @@ +package com.zt.plat.module.system.enums.permission; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 数据规则条件枚举 + * + * 用于菜单数据规则的条件类型 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum DataRuleConditionEnum { + + EQ("=", "等于"), + NE("!=", "不等于"), + GT(">", "大于"), + GE(">=", "大于等于"), + LT("<", "小于"), + LE("<=", "小于等于"), + IN("IN", "包含"), + NOT_IN("NOT_IN", "不包含"), + LIKE("LIKE", "模糊匹配"), + LEFT_LIKE("LEFT_LIKE", "左模糊"), + RIGHT_LIKE("RIGHT_LIKE", "右模糊"), + NOT_LIKE("NOT_LIKE", "不匹配"), + IS_NULL("IS_NULL", "为空"), + IS_NOT_NULL("IS_NOT_NULL", "不为空"), + SQL_RULE("SQL_RULE", "自定义SQL"); + + /** + * 条件符号 + */ + private final String condition; + + /** + * 条件描述 + */ + private final String description; + + /** + * 根据条件符号查找枚举 + * + * @param condition 条件符号 + * @return 枚举值 + */ + public static DataRuleConditionEnum findByCondition(String condition) { + if (condition == null) { + return null; + } + for (DataRuleConditionEnum value : values()) { + if (value.condition.equals(condition)) { + return value; + } + } + return null; + } +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleVariableEnum.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleVariableEnum.java new file mode 100644 index 00000000..1c5881cd --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/permission/DataRuleVariableEnum.java @@ -0,0 +1,53 @@ +package com.zt.plat.module.system.enums.permission; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 数据规则变量枚举 + * + * 用于菜单数据规则的变量替换 + * + * @author ZT + */ +@Getter +@AllArgsConstructor +public enum DataRuleVariableEnum { + + USER_ID("#{userId}", "当前用户ID"), + USERNAME("#{username}", "当前用户名"), + DEPT_ID("#{deptId}", "当前用户部门ID"), + DEPT_IDS("#{deptIds}", "当前用户所有部门ID"), + ORG_CODE("#{orgCode}", "当前用户组织编码"), + TENANT_ID("#{tenantId}", "当前租户ID"), + CURRENT_DATE("#{currentDate}", "当前日期"), + CURRENT_TIME("#{currentTime}", "当前时间"); + + /** + * 变量名 + */ + private final String variable; + + /** + * 变量描述 + */ + private final String description; + + /** + * 根据变量名查找枚举 + * + * @param variable 变量名 + * @return 枚举值 + */ + public static DataRuleVariableEnum findByVariable(String variable) { + if (variable == null) { + return null; + } + for (DataRuleVariableEnum value : values()) { + if (value.variable.equals(variable)) { + return value; + } + } + return null; + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/MenuDataRuleController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/MenuDataRuleController.java new file mode 100644 index 00000000..be27f6c4 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/MenuDataRuleController.java @@ -0,0 +1,77 @@ +package com.zt.plat.module.system.controller.admin.permission; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleRespVO; +import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO; +import com.zt.plat.module.system.convert.permission.MenuDataRuleConvert; +import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO; +import com.zt.plat.module.system.service.permission.MenuDataRuleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +/** + * 菜单数据规则 Controller + * + * @author ZT + */ +@Tag(name = "管理后台 - 菜单数据规则") +@RestController +@RequestMapping("/system/menu-data-rule") +@Validated +public class MenuDataRuleController { + + @Resource + private MenuDataRuleService menuDataRuleService; + + @PostMapping("/create") + @Operation(summary = "创建菜单数据规则") + @PreAuthorize("@ss.hasPermission('system:menu:update')") + public CommonResult createMenuDataRule(@Valid @RequestBody MenuDataRuleSaveReqVO createReqVO) { + return success(menuDataRuleService.createMenuDataRule(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新菜单数据规则") + @PreAuthorize("@ss.hasPermission('system:menu:update')") + public CommonResult updateMenuDataRule(@Valid @RequestBody MenuDataRuleSaveReqVO updateReqVO) { + menuDataRuleService.updateMenuDataRule(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除菜单数据规则") + @Parameter(name = "id", description = "规则ID", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:menu:update')") + public CommonResult deleteMenuDataRule(@RequestParam("id") Long id) { + menuDataRuleService.deleteMenuDataRule(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得菜单数据规则") + @Parameter(name = "id", description = "规则ID", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:menu:query')") + public CommonResult getMenuDataRule(@RequestParam("id") Long id) { + MenuDataRuleDO rule = menuDataRuleService.getMenuDataRule(id); + return success(MenuDataRuleConvert.INSTANCE.convert(rule)); + } + + @GetMapping("/list") + @Operation(summary = "获得菜单的所有数据规则") + @Parameter(name = "menuId", description = "菜单ID", required = true, example = "1") + @PreAuthorize("@ss.hasPermission('system:menu:query')") + public CommonResult> getMenuDataRuleList(@RequestParam("menuId") Long menuId) { + List list = menuDataRuleService.getMenuDataRuleListByMenuId(menuId); + return success(MenuDataRuleConvert.INSTANCE.convertList(list)); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/PermissionController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/PermissionController.java index d84bc342..cd1d2728 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/PermissionController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/PermissionController.java @@ -66,6 +66,9 @@ public class PermissionController { PermissionAssignRoleMenuItemReqVO reqVO = new PermissionAssignRoleMenuItemReqVO(); reqVO.setId(menu.getMenuId()); reqVO.setShowMenu(menu.getShowMenu()); + // 获取该角色在该菜单下的数据规则ID列表 + Set dataRuleIds = permissionService.getRoleMenuDataRules(roleId, menu.getMenuId()); + reqVO.setDataRuleIds(dataRuleIds != null ? new ArrayList<>(dataRuleIds) : null); return reqVO; }).collect(Collectors.toSet()); return success(result); @@ -83,6 +86,10 @@ public class PermissionController { // 更新菜单的显示状态 permissionService.updateMenuDisplay(reqVO.getRoleId(), reqVO.getMenus()); + + // 保存菜单数据规则关联 + permissionService.assignRoleMenuDataRules(reqVO.getRoleId(), reqVO.getMenus()); + return success(true); } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/RoleController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/RoleController.java index 74845b6c..86186e3b 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/RoleController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/RoleController.java @@ -11,6 +11,7 @@ import com.zt.plat.module.system.controller.admin.permission.vo.role.RolePageReq import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleRespVO; import com.zt.plat.module.system.controller.admin.permission.vo.role.RoleSaveReqVO; import com.zt.plat.module.system.dal.dataobject.permission.RoleDO; +import com.zt.plat.framework.datapermission.core.menudatapermission.annotation.PermissionData; import com.zt.plat.module.system.service.permission.RoleService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -78,6 +79,7 @@ public class RoleController { @GetMapping("/page") @Operation(summary = "获得角色分页") @PreAuthorize("@ss.hasPermission('system:role:query')") + @PermissionData(pageComponent = "system/role/index") public CommonResult> getRolePage(RolePageReqVO pageReqVO) { PageResult pageResult = roleService.getRolePage(pageReqVO); // 获取所有父级角色信息 diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleRespVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleRespVO.java new file mode 100644 index 00000000..4b624dea --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleRespVO.java @@ -0,0 +1,46 @@ +package com.zt.plat.module.system.controller.admin.permission.vo.menudatarule; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 菜单数据规则 Response VO + * + * @author ZT + */ +@Schema(description = "管理后台 - 菜单数据规则 Response VO") +@Data +public class MenuDataRuleRespVO { + + @Schema(description = "规则ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long menuId; + + @Schema(description = "规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "仅看本部门数据") + private String ruleName; + + @Schema(description = "规则字段", example = "dept_id") + private String ruleColumn; + + @Schema(description = "规则条件", requiredMode = Schema.RequiredMode.REQUIRED, example = "=") + private String ruleConditions; + + @Schema(description = "规则值", requiredMode = Schema.RequiredMode.REQUIRED, example = "#{deptId}") + private String ruleValue; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer status; + + @Schema(description = "排序", example = "1") + private Integer sort; + + @Schema(description = "备注", example = "限制只能查看本部门数据") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleSaveReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleSaveReqVO.java new file mode 100644 index 00000000..d3044877 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/menudatarule/MenuDataRuleSaveReqVO.java @@ -0,0 +1,53 @@ +package com.zt.plat.module.system.controller.admin.permission.vo.menudatarule; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * 菜单数据规则创建/修改 Request VO + * + * @author ZT + */ +@Schema(description = "管理后台 - 菜单数据规则创建/修改 Request VO") +@Data +public class MenuDataRuleSaveReqVO { + + @Schema(description = "规则ID", example = "1024") + private Long id; + + @Schema(description = "菜单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "菜单ID不能为空") + private Long menuId; + + @Schema(description = "规则名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "仅看本部门数据") + @NotBlank(message = "规则名称不能为空") + @Size(max = 100, message = "规则名称长度不能超过 100 个字符") + private String ruleName; + + @Schema(description = "规则字段", example = "dept_id") + @Size(max = 100, message = "规则字段长度不能超过 100 个字符") + private String ruleColumn; + + @Schema(description = "规则条件", requiredMode = Schema.RequiredMode.REQUIRED, example = "=") + @NotBlank(message = "规则条件不能为空") + @Size(max = 20, message = "规则条件长度不能超过 20 个字符") + private String ruleConditions; + + @Schema(description = "规则值", requiredMode = Schema.RequiredMode.REQUIRED, example = "#{deptId}") + @NotBlank(message = "规则值不能为空") + @Size(max = 500, message = "规则值长度不能超过 500 个字符") + private String ruleValue; + + @Schema(description = "状态", example = "1") + private Integer status; + + @Schema(description = "排序", example = "1") + private Integer sort; + + @Schema(description = "备注", example = "限制只能查看本部门数据") + @Size(max = 500, message = "备注长度不能超过 500 个字符") + private String remark; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuItemReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuItemReqVO.java index 0d038266..576274b9 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuItemReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuItemReqVO.java @@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.List; + @Schema(description = "管理后台 - 赋予角色菜单--菜单列表 Request VO") @Data @@ -19,4 +21,7 @@ public class PermissionAssignRoleMenuItemReqVO { @Schema(description = "是否显示菜单按钮是否点击过(避免大量更新数据,只更新点击过的)") private Boolean showMenuChanged = false; + @Schema(description = "菜单数据规则ID列表", example = "[1, 2, 3]") + private List dataRuleIds; + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/permission/MenuDataRuleConvert.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/permission/MenuDataRuleConvert.java new file mode 100644 index 00000000..1841074b --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/convert/permission/MenuDataRuleConvert.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.system.convert.permission; + +import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleRespVO; +import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 菜单数据规则 Convert + * + * @author ZT + */ +@Mapper +public interface MenuDataRuleConvert { + + MenuDataRuleConvert INSTANCE = Mappers.getMapper(MenuDataRuleConvert.class); + + MenuDataRuleDO convert(MenuDataRuleSaveReqVO bean); + + MenuDataRuleRespVO convert(MenuDataRuleDO bean); + + List convertList(List list); +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/MenuDataRuleDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/MenuDataRuleDO.java new file mode 100644 index 00000000..f8843af7 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/MenuDataRuleDO.java @@ -0,0 +1,67 @@ +package com.zt.plat.module.system.dal.dataobject.permission; + +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 菜单数据规则 DO + * + * @author ZT + */ +@TableName("system_menu_data_rule") +@KeySequence("system_menu_data_rule_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class MenuDataRuleDO extends TenantBaseDO { + + /** + * 规则ID + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 菜单ID + */ + private Long menuId; + + /** + * 规则名称 + */ + private String ruleName; + + /** + * 规则字段(数据库列名) + */ + private String ruleColumn; + + /** + * 规则条件(=、>、<、IN、LIKE等) + */ + private String ruleConditions; + + /** + * 规则值(支持变量如#{userId}、#{deptId}) + */ + private String ruleValue; + + /** + * 状态(0=禁用 1=启用) + */ + private Integer status; + + /** + * 排序 + */ + private Integer sort; + + /** + * 备注 + */ + private String remark; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/RoleMenuDataRuleDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/RoleMenuDataRuleDO.java new file mode 100644 index 00000000..f5d9ae2f --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/permission/RoleMenuDataRuleDO.java @@ -0,0 +1,42 @@ +package com.zt.plat.module.system.dal.dataobject.permission; + +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 角色菜单数据规则关联 DO + * + * @author ZT + */ +@TableName("system_role_menu_data_rule") +@KeySequence("system_role_menu_data_rule_seq") +@Data +@EqualsAndHashCode(callSuper = true) +public class RoleMenuDataRuleDO extends TenantBaseDO { + + /** + * 自增主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 角色ID + */ + private Long roleId; + + /** + * 菜单ID + */ + private Long menuId; + + /** + * 数据规则ID + */ + private Long dataRuleId; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuDataRuleMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuDataRuleMapper.java new file mode 100644 index 00000000..2f8aa889 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuDataRuleMapper.java @@ -0,0 +1,59 @@ +package com.zt.plat.module.system.dal.mysql.permission; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; + +import java.util.Collection; +import java.util.List; + +/** + * 菜单数据规则 Mapper + * + * @author ZT + */ +@Mapper +public interface MenuDataRuleMapper extends BaseMapperX { + + /** + * 根据菜单ID查询规则列表 + * + * @param menuId 菜单ID + * @return 规则列表 + */ + default List selectListByMenuId(Long menuId) { + return selectList(MenuDataRuleDO::getMenuId, menuId); + } + + /** + * 根据角色和菜单查询规则ID列表 + * + * @param roleIds 角色ID集合 + * @param menuId 菜单ID + * @return 规则ID列表 + */ + @Select("") + List selectRuleIdsByRoleAndMenu(@Param("roleIds") Collection roleIds, + @Param("menuId") Long menuId); + + /** + * 批量查询菜单的规则 + * + * @param menuIds 菜单ID集合 + * @return 规则列表 + */ + default List selectListByMenuIds(Collection menuIds) { + return selectList("menu_id", menuIds); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuMapper.java index 60a4af8b..5f9b4a3d 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/MenuMapper.java @@ -33,4 +33,8 @@ public interface MenuMapper extends BaseMapperX { return selectOne(MenuDO::getComponentName, componentName); } + default MenuDO selectByComponent(String component) { + return selectOne(MenuDO::getComponent, component); + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/RoleMenuDataRuleMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/RoleMenuDataRuleMapper.java new file mode 100644 index 00000000..25d7e101 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/permission/RoleMenuDataRuleMapper.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.system.dal.mysql.permission; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 角色菜单数据规则关联 Mapper + * + * @author ZT + */ +@Mapper +public interface RoleMenuDataRuleMapper extends BaseMapperX { + + /** + * 根据角色ID和菜单ID查询规则关联 + * + * @param roleId 角色ID + * @param menuId 菜单ID + * @return 规则关联列表 + */ + default List selectListByRoleAndMenu(Long roleId, Long menuId) { + return selectList(new LambdaQueryWrapper() + .eq(RoleMenuDataRuleDO::getRoleId, roleId) + .eq(RoleMenuDataRuleDO::getMenuId, menuId)); + } + + /** + * 根据角色ID和菜单ID删除规则关联 + * + * @param roleId 角色ID + * @param menuId 菜单ID + */ + default void deleteByRoleAndMenu(Long roleId, Long menuId) { + delete(new LambdaQueryWrapper() + .eq(RoleMenuDataRuleDO::getRoleId, roleId) + .eq(RoleMenuDataRuleDO::getMenuId, menuId)); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java index eee1404e..e1c858a3 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -204,6 +204,9 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { .put(LoginUser.INFO_KEY_USERNAME, user.getUsername()) .put(LoginUser.INFO_KEY_PHONE, user.getMobile()) .put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds())) + .put(LoginUser.INFO_KEY_DEPT_IDS, CollUtil.isEmpty(user.getDeptIds()) ? "[]" : JsonUtils.toJsonString(user.getDeptIds())) + .put(LoginUser.INFO_KEY_COMPANY_IDS, CollUtil.isEmpty(user.getCompanyIds()) ? "[]" : JsonUtils.toJsonString(user.getCompanyIds())) + .put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, CollUtil.isEmpty(user.getCompanyDeptInfos()) ? "[]" : JsonUtils.toJsonString(user.getCompanyDeptInfos())) .build(); } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { // 注意:目前 Member 暂时不读取,可以按需实现 diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/MenuDataRuleService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/MenuDataRuleService.java new file mode 100644 index 00000000..e49d10d8 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/MenuDataRuleService.java @@ -0,0 +1,72 @@ +package com.zt.plat.module.system.service.permission; + +import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO; + +import jakarta.validation.Valid; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 菜单数据规则 Service 接口 + * + * @author ZT + */ +public interface MenuDataRuleService { + + /** + * 创建菜单数据规则 + * + * @param createReqVO 创建信息 + * @return 规则ID + */ + Long createMenuDataRule(@Valid MenuDataRuleSaveReqVO createReqVO); + + /** + * 更新菜单数据规则 + * + * @param updateReqVO 更新信息 + */ + void updateMenuDataRule(@Valid MenuDataRuleSaveReqVO updateReqVO); + + /** + * 删除菜单数据规则 + * + * @param id 规则ID + */ + void deleteMenuDataRule(Long id); + + /** + * 获取菜单数据规则 + * + * @param id 规则ID + * @return 规则信息 + */ + MenuDataRuleDO getMenuDataRule(Long id); + + /** + * 获取菜单的所有数据规则 + * + * @param menuId 菜单ID + * @return 规则列表 + */ + List getMenuDataRuleListByMenuId(Long menuId); + + /** + * 获取用户在指定菜单下的有效数据规则 + * + * @param userId 用户ID + * @param menuId 菜单ID + * @return 规则列表 + */ + List getUserMenuDataRules(Long userId, Long menuId); + + /** + * 批量获取菜单的数据规则(带缓存) + * + * @param menuIds 菜单ID列表 + * @return 菜单ID -> 规则列表的映射 + */ + Map> getMenuDataRuleMap(Collection menuIds); +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PageComponentMappingService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PageComponentMappingService.java new file mode 100644 index 00000000..f62f9bc1 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PageComponentMappingService.java @@ -0,0 +1,56 @@ +package com.zt.plat.module.system.service.permission; + +import cn.hutool.core.util.StrUtil; +import com.zt.plat.module.system.dal.dataobject.permission.MenuDO; +import com.zt.plat.module.system.dal.mysql.permission.MenuMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 页面组件映射服务 + * 根据pageComponent查询对应的菜单ID + * + * @author ZT + */ +@Service +@Slf4j +public class PageComponentMappingService { + + @Resource + private MenuMapper menuMapper; + + /** + * 根据页面组件路径获取菜单ID + * + * @param pageComponent 页面组件路径,如:system/role/index + * @return 菜单ID,如果未找到返回null + */ + public Long getMenuIdByPageComponent(String pageComponent) { + if (StrUtil.isBlank(pageComponent)) { + return null; + } + + log.debug("[getMenuIdByPageComponent][查询pageComponent: {}]", pageComponent); + + // 使用精确匹配查询菜单 + MenuDO menu = menuMapper.selectByComponent(pageComponent); + + if (menu != null) { + log.debug("[getMenuIdByPageComponent][找到匹配菜单: ID={}, Name={}, Component={}]", + menu.getId(), menu.getName(), menu.getComponent()); + // 兼容达梦数据库,ID可能是Integer类型,需要转换为Long + Object id = menu.getId(); + if (id instanceof Number) { + return ((Number) id).longValue(); + } + return (Long) id; + } + + log.warn("[getMenuIdByPageComponent][未找到匹配的菜单: {}]", pageComponent); + return null; + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java index 7a23c24e..6d570ca7 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java @@ -86,6 +86,23 @@ public interface PermissionService { */ Set getMenuRoleIdListByMenuIdFromCache(Long menuId); + /** + * 批量设置角色-菜单-规则关联 + * + * @param roleId 角色编号 + * @param menuDataRules 菜单和规则的映射关系 + */ + void assignRoleMenuDataRules(Long roleId, Collection menuDataRules); + + /** + * 获取角色在指定菜单下已选择的数据规则ID列表 + * + * @param roleId 角色编号 + * @param menuId 菜单编号 + * @return 数据规则ID列表 + */ + Set getRoleMenuDataRules(Long roleId, Long menuId); + // ========== 用户-角色的相关方法 ========== /** diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java index ce302e9e..96820ed6 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java @@ -76,6 +76,8 @@ public class PermissionServiceImpl implements PermissionService { private RoleMenuMapper roleMenuMapper; @Resource private UserRoleMapper userRoleMapper; + @Resource + private com.zt.plat.module.system.dal.mysql.permission.RoleMenuDataRuleMapper roleMenuDataRuleMapper; private RoleService roleService; @Resource @@ -221,6 +223,45 @@ public class PermissionServiceImpl implements PermissionService { } } + @Override + @Transactional(rollbackFor = Exception.class) + public void assignRoleMenuDataRules(Long roleId, Collection menuDataRules) { + if (CollUtil.isEmpty(menuDataRules)) { + return; + } + + // 遍历每个菜单,更新其数据规则关联 + for (PermissionAssignRoleMenuItemReqVO menuDataRule : menuDataRules) { + Long menuId = menuDataRule.getId(); + List dataRuleIds = menuDataRule.getDataRuleIds(); + + // 删除该角色在该菜单下的旧规则关联 + roleMenuDataRuleMapper.deleteByRoleAndMenu(roleId, menuId); + + // 如果有新规则,则插入 + if (CollUtil.isNotEmpty(dataRuleIds)) { + List entities = + dataRuleIds.stream().map(ruleId -> { + com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO entity = + new com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO(); + entity.setRoleId(roleId); + entity.setMenuId(menuId); + entity.setDataRuleId(ruleId); + return entity; + }).collect(Collectors.toList()); + roleMenuDataRuleMapper.insertBatch(entities); + } + } + } + + @Override + public Set getRoleMenuDataRules(Long roleId, Long menuId) { + List list = + roleMenuDataRuleMapper.selectListByRoleAndMenu(roleId, menuId); + return CollectionUtils.convertSet(list, + com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDataRuleDO::getDataRuleId); + } + @Override @Transactional(rollbackFor = Exception.class) @Caching(evict = { diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/impl/MenuDataRuleServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/impl/MenuDataRuleServiceImpl.java new file mode 100644 index 00000000..da8cbef8 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/impl/MenuDataRuleServiceImpl.java @@ -0,0 +1,109 @@ +package com.zt.plat.module.system.service.permission.impl; + +import cn.hutool.core.collection.CollUtil; +import com.zt.plat.module.system.controller.admin.permission.vo.menudatarule.MenuDataRuleSaveReqVO; +import com.zt.plat.module.system.convert.permission.MenuDataRuleConvert; +import com.zt.plat.module.system.dal.dataobject.permission.MenuDataRuleDO; +import com.zt.plat.module.system.dal.mysql.permission.MenuDataRuleMapper; +import com.zt.plat.module.system.service.permission.MenuDataRuleService; +import com.zt.plat.module.system.service.permission.PermissionService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import jakarta.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.system.enums.ErrorCodeConstants.*; + +/** + * 菜单数据规则 Service 实现类 + * + * @author ZT + */ +@Service +@Validated +@Slf4j +public class MenuDataRuleServiceImpl implements MenuDataRuleService { + + @Resource + private MenuDataRuleMapper menuDataRuleMapper; + + @Resource + private PermissionService permissionService; + + @Override + @CacheEvict(value = "menuDataRule", key = "#createReqVO.menuId") + public Long createMenuDataRule(MenuDataRuleSaveReqVO createReqVO) { + MenuDataRuleDO rule = MenuDataRuleConvert.INSTANCE.convert(createReqVO); + menuDataRuleMapper.insert(rule); + return rule.getId(); + } + + @Override + @CacheEvict(value = "menuDataRule", key = "#updateReqVO.menuId") + public void updateMenuDataRule(MenuDataRuleSaveReqVO updateReqVO) { + validateMenuDataRuleExists(updateReqVO.getId()); + MenuDataRuleDO updateObj = MenuDataRuleConvert.INSTANCE.convert(updateReqVO); + menuDataRuleMapper.updateById(updateObj); + } + + @Override + public void deleteMenuDataRule(Long id) { + MenuDataRuleDO rule = validateMenuDataRuleExists(id); + menuDataRuleMapper.deleteById(id); + } + + @Override + public MenuDataRuleDO getMenuDataRule(Long id) { + return menuDataRuleMapper.selectById(id); + } + + @Override + @Cacheable(value = "menuDataRule", key = "#menuId") + public List getMenuDataRuleListByMenuId(Long menuId) { + return menuDataRuleMapper.selectListByMenuId(menuId); + } + + @Override + public List getUserMenuDataRules(Long userId, Long menuId) { + Set roleIds = permissionService.getUserRoleIdListByUserId(userId); + if (CollUtil.isEmpty(roleIds)) { + return Collections.emptyList(); + } + + List allRules = getMenuDataRuleListByMenuId(menuId); + if (CollUtil.isEmpty(allRules)) { + return Collections.emptyList(); + } + + List ruleIds = menuDataRuleMapper.selectRuleIdsByRoleAndMenu(roleIds, menuId); + + // 如果角色没有关联任何规则,返回空列表(不应用任何过滤) + if (CollUtil.isEmpty(ruleIds)) { + return Collections.emptyList(); + } + + return allRules.stream() + .filter(rule -> ruleIds.contains(rule.getId()) && rule.getStatus() == 1) + .collect(Collectors.toList()); + } + + @Override + public Map> getMenuDataRuleMap(Collection menuIds) { + List rules = menuDataRuleMapper.selectListByMenuIds(menuIds); + return rules.stream().collect(Collectors.groupingBy(MenuDataRuleDO::getMenuId)); + } + + private MenuDataRuleDO validateMenuDataRuleExists(Long id) { + MenuDataRuleDO rule = menuDataRuleMapper.selectById(id); + if (rule == null) { + throw exception(MENU_NOT_EXISTS); + } + return rule; + } +} From 1f00961c1b905715ffca0538977bcf541aa5832c Mon Sep 17 00:00:00 2001 From: wuzongyong <13203449218@163.com> Date: Wed, 28 Jan 2026 11:44:53 +0800 Subject: [PATCH 3/4] =?UTF-8?q?refactor(tenant):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E9=83=A8=E9=97=A8ID=E5=9B=9E=E9=80=80=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除了从登录用户info中解析部门ID的复杂逻辑 - 移除了对LoginUser.INFO_KEY_DEPT_IDS和相关公司信息的存储 - 简化了部门ID的获取流程,只保留请求头、请求属性和登录缓存的回退顺序 - 减少了不必要的JSON解析操作,提高性能 - 清理了相关的异常处理代码 --- .../web/CompanyVisitContextInterceptor.java | 19 ------------------- .../oauth2/OAuth2TokenServiceImpl.java | 3 --- 2 files changed, 22 deletions(-) diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java index 99336abf..56302390 100644 --- a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/web/CompanyVisitContextInterceptor.java @@ -47,31 +47,12 @@ public class CompanyVisitContextInterceptor implements HandlerInterceptor { } Long deptId = WebFrameworkUtils.getDeptId(request); - // 部门信息同样遵循"请求头 -> 请求属性 -> 登录缓存"的回退顺序 if (deptId == null || deptId <= 0L) { Long attrDeptId = resolveLong(request.getAttribute(WebFrameworkUtils.HEADER_VISIT_DEPT_ID)); if (attrDeptId != null && attrDeptId > 0L) { deptId = attrDeptId; } else if (loginUser != null && loginUser.getVisitDeptId() != null && loginUser.getVisitDeptId() > 0L) { deptId = loginUser.getVisitDeptId(); - } else if (loginUser != null) { - // 如果以上都没有,尝试从用户info中获取第一个部门作为默认值 - Map info = loginUser.getInfo(); - if (info != null) { - String deptIdsStr = info.get(LoginUser.INFO_KEY_DEPT_IDS); - if (deptIdsStr != null && !deptIdsStr.isEmpty() && !"[]".equals(deptIdsStr)) { - try { - // 解析JSON数组,取第一个部门ID - deptIdsStr = deptIdsStr.trim(); - if (deptIdsStr.startsWith("[") && deptIdsStr.length() > 2) { - String firstId = deptIdsStr.substring(1, deptIdsStr.indexOf(']')).split(",")[0].trim(); - deptId = Long.parseLong(firstId); - } - } catch (Exception e) { - log.warn("[CompanyVisitContextInterceptor][解析用户默认部门失败: {}]", deptIdsStr, e); - } - } - } } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java index e1c858a3..eee1404e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -204,9 +204,6 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { .put(LoginUser.INFO_KEY_USERNAME, user.getUsername()) .put(LoginUser.INFO_KEY_PHONE, user.getMobile()) .put(LoginUser.INFO_KEY_POST_IDS, CollUtil.isEmpty(user.getPostIds()) ? "[]" : JsonUtils.toJsonString(user.getPostIds())) - .put(LoginUser.INFO_KEY_DEPT_IDS, CollUtil.isEmpty(user.getDeptIds()) ? "[]" : JsonUtils.toJsonString(user.getDeptIds())) - .put(LoginUser.INFO_KEY_COMPANY_IDS, CollUtil.isEmpty(user.getCompanyIds()) ? "[]" : JsonUtils.toJsonString(user.getCompanyIds())) - .put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, CollUtil.isEmpty(user.getCompanyDeptInfos()) ? "[]" : JsonUtils.toJsonString(user.getCompanyDeptInfos())) .build(); } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { // 注意:目前 Member 暂时不读取,可以按需实现 From c4ef8701eff9deda7ec2df7f3daf78f256b38ce4 Mon Sep 17 00:00:00 2001 From: chenbowen Date: Wed, 28 Jan 2026 14:20:34 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=B8=8D=E5=86=8D=E9=99=90=E5=88=B6?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=97=B6=E8=B4=A6=E5=8F=B7=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E4=B8=BA=E6=95=B0=E5=AD=97=E4=BB=A5=E5=8F=8A=E5=AD=97=E6=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/user/vo/user/UserSaveReqVO.java | 7 +++++-- .../plat/module/system/service/dept/DeptServiceImpl.java | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index 842ee71c..7d11c31f 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -10,7 +10,10 @@ import com.zt.plat.module.system.framework.operatelog.core.DeptParseFunction; import com.zt.plat.module.system.framework.operatelog.core.PostParseFunction; import com.zt.plat.module.system.framework.operatelog.core.SexParseFunction; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.*; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.Data; import org.hibernate.validator.constraints.Length; @@ -28,7 +31,7 @@ public class UserSaveReqVO { @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "zt") @NotBlank(message = "用户账号不能为空") - @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户账号由 数字、字母 组成") +// @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户账号由 数字、字母 组成") @Size(min = 1, max = 30, message = "用户账号长度为 1-30 个字符") @DiffLogField(name = "用户账号") private String username; diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java index 52d15ac5..81fc35bf 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/DeptServiceImpl.java @@ -16,13 +16,12 @@ import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; import com.zt.plat.module.system.dal.dataobject.userdept.UserDeptDO; import com.zt.plat.module.system.dal.mysql.dept.DeptMapper; import com.zt.plat.module.system.dal.mysql.userdept.UserDeptMapper; -import com.zt.plat.module.system.service.dept.DeptExternalCodeService; import com.zt.plat.module.system.dal.redis.RedisKeyConstants; import com.zt.plat.module.system.enums.dept.DeptSourceEnum; import com.zt.plat.module.system.service.permission.PermissionService; -import org.apache.seata.spring.annotation.GlobalTransactional; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.apache.seata.spring.annotation.GlobalTransactional; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @@ -762,6 +761,7 @@ public class DeptServiceImpl implements DeptService { return deptIds; } + @DataPermission(enable = false) private Long resolveNearestCompanyId(Long deptId, Map deptCache) { DeptDO current = loadDept(deptId, deptCache); while (current != null) {