From 8737c4b5f9aa79d485cc8a0d134a8e79b03c37ff Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 27 Oct 2026 04:05:01 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E8=A7=92=E8=89=B2=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=BA=A7=E7=BB=84=E7=BB=87/=E5=AE=A2?= =?UTF-8?q?=E5=95=86=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将后台数据权限迁移到角色×菜单配置,运行时按 menuCode 解析;组织树取最宽合并;停用用户侧组织与客商授权过滤。 Co-authored-by: Cursor --- .../system/cache/RoleDataScopeCache.java | 111 +++++++ .../system/feign/IRoleDataScopeClient.java | 74 +++++ .../feign/IRoleDataScopeClientFallback.java | 50 +++ .../system/pojo/dto/RoleMenuDataScopeDTO.java | 59 ++++ .../system/pojo/entity/RoleMenuDataScope.java | 68 +++++ .../pojo/entity/RoleMenuDataScopeDept.java | 65 ++++ .../system/pojo/enums/RoleDataScopeType.java | 84 +++++ .../system/pojo/vo/RoleDataScopeVO.java | 87 ++++++ .../pojo/vo/RoleMenuDataScopeTreeVO.java | 58 ++++ .../system/pojo/vo/RoleMenuDataScopeVO.java | 67 ++++ .../transport/pojo/vo/CommonAddressVO.java | 9 + .../RoleMenuDataScopeController.java | 83 +++++ .../system/feign/RoleDataScopeClient.java | 63 ++++ .../mapper/RoleMenuDataScopeDeptMapper.java | 37 +++ .../mapper/RoleMenuDataScopeMapper.java | 37 +++ .../service/IRoleMenuDataScopeService.java | 78 +++++ .../system/service/impl/DeptServiceImpl.java | 71 ++--- .../impl/RoleMenuDataScopeServiceImpl.java | 288 ++++++++++++++++++ .../system/service/impl/RoleServiceImpl.java | 7 +- .../system/service/impl/UserServiceImpl.java | 46 ++- .../controller/CommonAddressController.java | 12 +- .../transport/mapper/CommonAddressMapper.xml | 9 + .../impl/CommonAddressServiceImpl.java | 10 +- .../service/impl/CommonCargoServiceImpl.java | 10 +- .../service/impl/CommonRouteServiceImpl.java | 16 +- .../impl/CustomerArchiveServiceImpl.java | 38 +-- .../impl/LoadingManageServiceImpl.java | 6 +- .../impl/ProcessConfigServiceImpl.java | 16 +- .../impl/ShippingTemplateServiceImpl.java | 4 +- .../impl/TemporaryCreditLimitServiceImpl.java | 2 +- .../impl/TransportPlanServiceImpl.java | 4 +- .../service/impl/WaybillServiceImpl.java | 26 +- .../support/TransportBusinessSupport.java | 177 +++++++++-- .../blade_role_menu_data_scope_20261027.sql | 28 ++ 34 files changed, 1640 insertions(+), 160 deletions(-) create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/RoleDataScopeCache.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClient.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClientFallback.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/dto/RoleMenuDataScopeDTO.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScope.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScopeDept.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/RoleDataScopeType.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeTreeVO.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeVO.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/controller/RoleMenuDataScopeController.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/feign/RoleDataScopeClient.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeDeptMapper.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeMapper.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java create mode 100644 doc/sql/transport/blade_role_menu_data_scope_20261027.sql diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/RoleDataScopeCache.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/RoleDataScopeCache.java new file mode 100644 index 00000000..10ee1cc8 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/cache/RoleDataScopeCache.java @@ -0,0 +1,111 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.cache; + +import org.springblade.core.cache.utils.CacheUtil; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.SpringUtil; +import org.springblade.core.tool.utils.StringPool; +import org.springblade.system.feign.IRoleDataScopeClient; +import org.springblade.system.pojo.vo.RoleDataScopeVO; + +import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE; + +/** + * 角色数据权限缓存 + * + * @author Chill + */ +public final class RoleDataScopeCache { + + private static final String CACHE_MENU = "role:data-scope:menu:"; + private static final String CACHE_WIDEST = "role:data-scope:widest:"; + + private static IRoleDataScopeClient roleDataScopeClient; + + private RoleDataScopeCache() { + } + + private static IRoleDataScopeClient getClient() { + if (roleDataScopeClient == null) { + roleDataScopeClient = SpringUtil.getBean(IRoleDataScopeClient.class); + } + return roleDataScopeClient; + } + + /** + * 按菜单编码解析当前登录用户的角色数据权限 + */ + public static RoleDataScopeVO resolveByMenuCode(String menuCode) { + if (AuthUtil.isAdministrator()) { + return RoleDataScopeVO.all(); + } + String roleIds = AuthUtil.getUser() == null ? StringPool.EMPTY : Func.toStr(AuthUtil.getUser().getRoleId()); + String deptIds = AuthUtil.getDeptId(); + if (Func.isBlank(roleIds)) { + return RoleDataScopeVO.all(); + } + String cacheKey = roleIds + StringPool.COLON + Func.toStr(menuCode) + StringPool.COLON + Func.toStr(deptIds); + return CacheUtil.get(SYS_CACHE, CACHE_MENU, cacheKey, () -> { + R result = getClient().resolveByMenuCode(roleIds, menuCode, deptIds); + if (result == null || !result.isSuccess() || result.getData() == null) { + return RoleDataScopeVO.all(); + } + return result.getData(); + }); + } + + /** + * 解析当前登录用户全部角色菜单的最宽数据权限 + */ + public static RoleDataScopeVO resolveWidest() { + if (AuthUtil.isAdministrator()) { + return RoleDataScopeVO.all(); + } + String roleIds = AuthUtil.getUser() == null ? StringPool.EMPTY : Func.toStr(AuthUtil.getUser().getRoleId()); + String deptIds = AuthUtil.getDeptId(); + if (Func.isBlank(roleIds)) { + return RoleDataScopeVO.all(); + } + String cacheKey = roleIds + StringPool.COLON + Func.toStr(deptIds); + return CacheUtil.get(SYS_CACHE, CACHE_WIDEST, cacheKey, () -> { + R result = getClient().resolveWidest(roleIds, deptIds); + if (result == null || !result.isSuccess() || result.getData() == null) { + return RoleDataScopeVO.all(); + } + return result.getData(); + }); + } + + /** + * 清理角色数据权限缓存 + */ + public static void clear() { + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + } +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClient.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClient.java new file mode 100644 index 00000000..c988eda7 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClient.java @@ -0,0 +1,74 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.feign; + +import org.springblade.core.launch.constant.AppConstant; +import org.springblade.core.tool.api.R; +import org.springblade.system.pojo.vo.RoleDataScopeVO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * 角色数据权限 Feign + * + * @author Chill + */ +@FeignClient( + value = AppConstant.APPLICATION_SYSTEM_NAME, + fallback = IRoleDataScopeClientFallback.class +) +public interface IRoleDataScopeClient { + + String API_PREFIX = "/feign/client/role-data-scope"; + String RESOLVE_BY_MENU_CODE = API_PREFIX + "/resolve-by-menu-code"; + String RESOLVE_WIDEST = API_PREFIX + "/resolve-widest"; + + /** + * 按菜单编码解析当前用户角色合并后的数据权限 + * + * @param roleIds 用户角色ID,逗号分隔 + * @param menuCode 菜单编码 + * @param deptIds 用户所属组织ID,逗号分隔 + * @return 解析结果 + */ + @GetMapping(RESOLVE_BY_MENU_CODE) + R resolveByMenuCode(@RequestParam("roleIds") String roleIds, + @RequestParam("menuCode") String menuCode, + @RequestParam(value = "deptIds", required = false) String deptIds); + + /** + * 解析当前用户全部角色菜单配置的最宽数据权限(组织树等公共场景) + * + * @param roleIds 用户角色ID,逗号分隔 + * @param deptIds 用户所属组织ID,逗号分隔 + * @return 解析结果 + */ + @GetMapping(RESOLVE_WIDEST) + R resolveWidest(@RequestParam("roleIds") String roleIds, + @RequestParam(value = "deptIds", required = false) String deptIds); + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClientFallback.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClientFallback.java new file mode 100644 index 00000000..e920da2f --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/IRoleDataScopeClientFallback.java @@ -0,0 +1,50 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.feign; + +import org.springblade.core.tool.api.R; +import org.springblade.system.pojo.vo.RoleDataScopeVO; +import org.springframework.stereotype.Component; + +/** + * 角色数据权限 Feign 降级 + * + * @author Chill + */ +@Component +public class IRoleDataScopeClientFallback implements IRoleDataScopeClient { + + @Override + public R resolveByMenuCode(String roleIds, String menuCode, String deptIds) { + return R.data(RoleDataScopeVO.all()); + } + + @Override + public R resolveWidest(String roleIds, String deptIds) { + return R.data(RoleDataScopeVO.all()); + } + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/dto/RoleMenuDataScopeDTO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/dto/RoleMenuDataScopeDTO.java new file mode 100644 index 00000000..ff8a1cba --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/dto/RoleMenuDataScopeDTO.java @@ -0,0 +1,59 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 角色菜单数据权限批量保存 + * + * @author Chill + */ +@Data +@Schema(description = "角色菜单数据权限保存参数") +public class RoleMenuDataScopeDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "角色主键") + private Long roleId; + + @Schema(description = "菜单主键集合") + private List menuIds; + + @Schema(description = "数据权限类型:1所在部门及下级 2全部组织 3本人创建 4自定义组织") + private Integer scopeType; + + @Schema(description = "自定义组织ID集合(scopeType=4 时必填)") + private List deptIds; + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScope.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScope.java new file mode 100644 index 00000000..6f1cf5b0 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScope.java @@ -0,0 +1,68 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.tenant.mp.TenantEntity; + +import java.io.Serial; + +/** + * 角色菜单数据权限 + * + * @author Chill + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("blade_role_menu_data_scope") +@Schema(description = "RoleMenuDataScope对象") +public class RoleMenuDataScope extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 角色主键 + */ + @Schema(description = "角色主键") + private Long roleId; + + /** + * 菜单主键 + */ + @Schema(description = "菜单主键") + private Long menuId; + + /** + * 数据权限类型:1所在部门及下级 2全部组织 3本人创建 4自定义组织 + */ + @Schema(description = "数据权限类型") + private Integer scopeType; + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScopeDept.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScopeDept.java new file mode 100644 index 00000000..5b655fb3 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/RoleMenuDataScopeDept.java @@ -0,0 +1,65 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 角色菜单数据权限自定义组织 + * + * @author Chill + */ +@Data +@TableName("blade_role_menu_data_scope_dept") +@Schema(description = "RoleMenuDataScopeDept对象") +public class RoleMenuDataScopeDept implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "主键") + @TableId(value = "id", type = IdType.ASSIGN_ID) + private Long id; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "角色菜单数据权限主键") + private Long scopeId; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "组织主键") + private Long deptId; + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/RoleDataScopeType.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/RoleDataScopeType.java new file mode 100644 index 00000000..c8e08123 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/enums/RoleDataScopeType.java @@ -0,0 +1,84 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 角色菜单数据权限类型 + * + * @author Chill + */ +@Getter +@AllArgsConstructor +public enum RoleDataScopeType { + + /** + * 所在部门及下级 + */ + DEPT_AND_CHILD(1, "所在部门及下级"), + /** + * 全部组织(默认) + */ + ALL(2, "全部组织"), + /** + * 本人创建 + */ + OWN_CREATE(3, "本人创建"), + /** + * 自定义组织 + */ + CUSTOM(4, "自定义组织"); + + private final int code; + private final String name; + + /** + * 默认权限类型:全部组织 + */ + public static final int DEFAULT_CODE = ALL.code; + + public static RoleDataScopeType of(Integer code) { + if (code == null) { + return ALL; + } + for (RoleDataScopeType type : values()) { + if (type.code == code) { + return type; + } + } + return ALL; + } + + public static String nameOf(Integer code) { + return of(code).getName(); + } + + public boolean matches(Integer code) { + return code != null && this.code == code; + } +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java new file mode 100644 index 00000000..1656f7ae --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleDataScopeVO.java @@ -0,0 +1,87 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.system.pojo.enums.RoleDataScopeType; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +/** + * 当前用户解析后的角色数据权限 + * + * @author Chill + */ +@Data +@Schema(description = "角色数据权限解析结果") +public class RoleDataScopeVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "数据权限类型") + private Integer scopeType; + + @Schema(description = "是否全部组织") + private boolean allDept; + + @Schema(description = "是否仅本人创建") + private boolean ownCreate; + + @Schema(description = "允许的组织ID集合(已展开)") + private List deptIds; + + public static RoleDataScopeVO all() { + RoleDataScopeVO scope = new RoleDataScopeVO(); + scope.setScopeType(RoleDataScopeType.ALL.getCode()); + scope.setAllDept(true); + scope.setOwnCreate(false); + scope.setDeptIds(Collections.emptyList()); + return scope; + } + + public static RoleDataScopeVO ownCreate() { + RoleDataScopeVO scope = new RoleDataScopeVO(); + scope.setScopeType(RoleDataScopeType.OWN_CREATE.getCode()); + scope.setAllDept(false); + scope.setOwnCreate(true); + scope.setDeptIds(Collections.emptyList()); + return scope; + } + + public static RoleDataScopeVO ofDepts(Integer scopeType, List deptIds) { + RoleDataScopeVO scope = new RoleDataScopeVO(); + scope.setScopeType(scopeType == null ? RoleDataScopeType.CUSTOM.getCode() : scopeType); + scope.setAllDept(false); + scope.setOwnCreate(false); + scope.setDeptIds(deptIds == null ? Collections.emptyList() : List.copyOf(deptIds)); + return scope; + } +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeTreeVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeTreeVO.java new file mode 100644 index 00000000..ac2c155d --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeTreeVO.java @@ -0,0 +1,58 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springblade.core.tool.node.TreeNode; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 角色数据权限配置树 + * + * @author Chill + */ +@Data +@Schema(description = "角色数据权限配置树") +public class RoleMenuDataScopeTreeVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "角色名称") + private String roleName; + + @Schema(description = "菜单树") + private List menuTree = new ArrayList<>(); + + @Schema(description = "已配置的菜单数据权限") + private List scopes = new ArrayList<>(); + +} diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeVO.java new file mode 100644 index 00000000..f7081124 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/RoleMenuDataScopeVO.java @@ -0,0 +1,67 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.vo; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 角色菜单数据权限配置项 + * + * @author Chill + */ +@Data +@Schema(description = "角色菜单数据权限配置项") +public class RoleMenuDataScopeVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + @Schema(description = "菜单主键") + private Long menuId; + + @Schema(description = "菜单编码") + private String menuCode; + + @Schema(description = "数据权限类型") + private Integer scopeType; + + @Schema(description = "数据权限类型名称") + private String scopeTypeName; + + @JsonSerialize(contentUsing = ToStringSerializer.class) + @Schema(description = "自定义组织ID集合") + private List deptIds = new ArrayList<>(); + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonAddressVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonAddressVO.java index e0938b4e..6368fae5 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonAddressVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CommonAddressVO.java @@ -29,6 +29,7 @@ import lombok.EqualsAndHashCode; import org.springblade.transport.pojo.entity.CommonAddress; import java.io.Serial; +import java.util.List; /** * 常用地址视图实体类 @@ -63,4 +64,12 @@ public class CommonAddressVO extends CommonAddress { @Schema(description = "是否查看全部组织") private Integer allDept; + @TableField(exist = false) + @Schema(description = "数据权限组织ID集合") + private List deptIds; + + @TableField(exist = false) + @Schema(description = "仅本人创建时的创建人ID") + private Long scopeCreateUser; + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/RoleMenuDataScopeController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RoleMenuDataScopeController.java new file mode 100644 index 00000000..9e6e011f --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/RoleMenuDataScopeController.java @@ -0,0 +1,83 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.controller; + +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.secure.annotation.IsAdmin; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.system.pojo.dto.RoleMenuDataScopeDTO; +import org.springblade.system.pojo.vo.RoleMenuDataScopeTreeVO; +import org.springblade.system.service.IRoleMenuDataScopeService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 角色菜单数据权限配置 + * + * @author Chill + */ +@NonDS +@RestController +@AllArgsConstructor +@IsAdmin +@RequestMapping("/role-menu-data-scope") +@Tag(name = "角色菜单数据权限", description = "角色菜单数据权限") +public class RoleMenuDataScopeController extends BladeController { + + private final IRoleMenuDataScopeService roleMenuDataScopeService; + + /** + * 角色数据权限配置树 + */ + @GetMapping("/tree") + @ApiOperationSupport(order = 1) + @Operation(summary = "角色数据权限配置树", description = "传入roleId") + public R tree(@Parameter(description = "角色主键", required = true) @RequestParam Long roleId) { + return R.data(roleMenuDataScopeService.tree(roleId)); + } + + /** + * 批量保存角色菜单数据权限 + */ + @PostMapping("/submit") + @ApiOperationSupport(order = 2) + @Operation(summary = "批量保存角色菜单数据权限", description = "传入RoleMenuDataScopeDTO") + public R submit(@Valid @RequestBody RoleMenuDataScopeDTO dto) { + return R.status(roleMenuDataScopeService.submit(dto)); + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/feign/RoleDataScopeClient.java b/blade-service/blade-system/src/main/java/org/springblade/system/feign/RoleDataScopeClient.java new file mode 100644 index 00000000..6c91700c --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/feign/RoleDataScopeClient.java @@ -0,0 +1,63 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.feign; + +import io.swagger.v3.oas.annotations.Hidden; +import lombok.AllArgsConstructor; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.pojo.vo.RoleDataScopeVO; +import org.springblade.system.service.IRoleMenuDataScopeService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 角色数据权限 Feign 实现 + * + * @author Chill + */ +@NonDS +@Hidden +@RestController +@AllArgsConstructor +public class RoleDataScopeClient implements IRoleDataScopeClient { + + private final IRoleMenuDataScopeService roleMenuDataScopeService; + + @Override + @GetMapping(RESOLVE_BY_MENU_CODE) + public R resolveByMenuCode(String roleIds, String menuCode, String deptIds) { + return R.data(roleMenuDataScopeService.resolveByMenuCode(Func.toLongList(roleIds), menuCode, Func.toLongList(deptIds))); + } + + @Override + @GetMapping(RESOLVE_WIDEST) + public R resolveWidest(String roleIds, String deptIds) { + return R.data(roleMenuDataScopeService.resolveWidest(Func.toLongList(roleIds), Func.toLongList(deptIds))); + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeDeptMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeDeptMapper.java new file mode 100644 index 00000000..d54d06e7 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeDeptMapper.java @@ -0,0 +1,37 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.system.pojo.entity.RoleMenuDataScopeDept; + +/** + * 角色菜单数据权限自定义组织 Mapper + * + * @author Chill + */ +public interface RoleMenuDataScopeDeptMapper extends BaseMapper { +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeMapper.java b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeMapper.java new file mode 100644 index 00000000..06849aa6 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/RoleMenuDataScopeMapper.java @@ -0,0 +1,37 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.springblade.system.pojo.entity.RoleMenuDataScope; + +/** + * 角色菜单数据权限 Mapper + * + * @author Chill + */ +public interface RoleMenuDataScopeMapper extends BaseMapper { +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java new file mode 100644 index 00000000..93cb33d2 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IRoleMenuDataScopeService.java @@ -0,0 +1,78 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.service; + +import org.springblade.core.mp.base.BaseService; +import org.springblade.system.pojo.dto.RoleMenuDataScopeDTO; +import org.springblade.system.pojo.entity.RoleMenuDataScope; +import org.springblade.system.pojo.vo.RoleDataScopeVO; +import org.springblade.system.pojo.vo.RoleMenuDataScopeTreeVO; + +import java.util.List; + +/** + * 角色菜单数据权限服务 + * + * @author Chill + */ +public interface IRoleMenuDataScopeService extends BaseService { + + /** + * 查询角色数据权限配置树 + * + * @param roleId 角色主键 + * @return 菜单树与已配置权限 + */ + RoleMenuDataScopeTreeVO tree(Long roleId); + + /** + * 批量保存角色菜单数据权限 + * + * @param dto 保存参数 + * @return 是否成功 + */ + boolean submit(RoleMenuDataScopeDTO dto); + + /** + * 按菜单编码合并解析角色数据权限 + * + * @param roleIds 角色ID集合 + * @param menuCode 菜单编码 + * @param userDeptIds 用户所属组织 + * @return 解析结果 + */ + RoleDataScopeVO resolveByMenuCode(List roleIds, String menuCode, List userDeptIds); + + /** + * 合并用户全部角色菜单配置为最宽数据权限 + * + * @param roleIds 角色ID集合 + * @param userDeptIds 用户所属组织 + * @return 解析结果 + */ + RoleDataScopeVO resolveWidest(List roleIds, List userDeptIds); + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java index 551918ed..a211a169 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java @@ -46,15 +46,15 @@ import org.springblade.core.tool.node.ForestNodeMerger; import org.springblade.core.tool.utils.*; import org.springblade.system.cache.SysCache; import org.springblade.system.mapper.DeptMapper; -import org.springblade.system.mapper.UserDataScopeMapper; import org.springblade.system.pojo.entity.Dept; import org.springblade.system.pojo.entity.User; -import org.springblade.system.pojo.entity.UserDataScope; import org.springblade.system.pojo.enums.DeptCategory; import org.springblade.system.pojo.vo.DeptVO; +import org.springblade.system.pojo.vo.RoleDataScopeVO; import org.springblade.system.pojo.vo.UserVO; import org.springblade.system.props.IamSyncProperties; import org.springblade.system.service.IDeptService; +import org.springblade.system.service.IRoleMenuDataScopeService; import org.springblade.system.service.IUserService; import org.springblade.system.wrapper.DeptWrapper; import org.springblade.system.wrapper.UserWrapper; @@ -94,19 +94,9 @@ public class DeptServiceImpl extends ServiceImpl implements ID private static final String IAM_SYNC_TENANT_ID = "000000"; private static final Long IAM_SYNC_PARENT_ID = 1123598813738675201L; private static final HttpClient IAM_HTTP_CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); - /** 数据权限范围:仅所属组织 */ - private static final int DATA_SCOPE_OWN = 1; - /** 数据权限范围:全部组织 */ - private static final int DATA_SCOPE_ALL = 2; - /** 数据权限范围:自定义 */ - private static final int DATA_SCOPE_CUSTOM = 3; - /** 数据层级范围:包含下级 */ - private static final int DATA_LEVEL_INCLUDE_CHILDREN = 1; - /** 数据层级范围:仅本级 */ - private static final int DATA_LEVEL_CURRENT_ONLY = 2; private final IUserService userService; - private final UserDataScopeMapper userDataScopeMapper; + private final IRoleMenuDataScopeService roleMenuDataScopeService; private final IamSyncProperties iamSyncProperties; private final ObjectMapper objectMapper; @@ -237,7 +227,8 @@ public class DeptServiceImpl extends ServiceImpl implements ID /** * 解析当前登录用户的组织树数据权限。 *

- * 超管或「全部组织」返回 allDept=true;其余按所属组织/自定义组织,并结合「包含下级/仅本级」展开。 + * 超管或角色菜单数据权限最宽合并为「全部组织」时不过滤; + * 其余按角色配置的组织并集过滤。仅「本人创建」时按用户所属组织及下级展示组织树。 * * @return 过滤上下文;无法识别用户时按空权限处理 */ @@ -246,55 +237,39 @@ public class DeptServiceImpl extends ServiceImpl implements ID return DeptDataScopeFilter.all(); } Long userId = AuthUtil.getUserId(); - if (Func.isEmpty(userId) || userId <= 0) { + if (Func.isEmpty(userId) || userId <= 0 || AuthUtil.getUser() == null) { return DeptDataScopeFilter.empty(); } + List roleIds = Func.toLongList(AuthUtil.getUser().getRoleId()); + List userDeptIds = Func.toLongList(AuthUtil.getDeptId()); User user = userService.getById(userId); - if (Func.isEmpty(user)) { - return DeptDataScopeFilter.empty(); + if (Func.isNotEmpty(user) && Func.isNotEmpty(user.getDeptId())) { + userDeptIds = Func.toLongList(user.getDeptId()); } - Integer dataScopeRange = user.getDataScopeRange() == null ? DATA_SCOPE_OWN : user.getDataScopeRange(); - Integer dataLevelRange = user.getDataLevelRange() == null ? DATA_LEVEL_INCLUDE_CHILDREN : user.getDataLevelRange(); - if (Objects.equals(dataScopeRange, DATA_SCOPE_ALL)) { + RoleDataScopeVO scope = roleMenuDataScopeService.resolveWidest(roleIds, userDeptIds); + if (scope == null || scope.isAllDept()) { return DeptDataScopeFilter.all(); } - List baseDeptIds; - if (Objects.equals(dataScopeRange, DATA_SCOPE_CUSTOM)) { - baseDeptIds = userDataScopeMapper.selectList(Wrappers.lambdaQuery() - .eq(UserDataScope::getUserId, userId)) - .stream() - .map(UserDataScope::getDeptId) - .filter(Objects::nonNull) - .distinct() - .toList(); - } else { - // 仅所属组织:优先用户档案中的所属组织,其次当前会话组织。 - baseDeptIds = Func.toLongList(user.getDeptId()); - if (Func.isEmpty(baseDeptIds)) { - baseDeptIds = Func.toLongList(AuthUtil.getDeptId()); - } - } - if (Func.isEmpty(baseDeptIds)) { - return DeptDataScopeFilter.empty(); - } - boolean includeChildren = !Objects.equals(dataLevelRange, DATA_LEVEL_CURRENT_ONLY); LinkedHashSet allowedDeptIds = new LinkedHashSet<>(); - for (Long deptId : baseDeptIds) { - if (Func.isEmpty(deptId) || deptId <= 0) { - continue; - } - if (includeChildren) { + if (scope.isOwnCreate()) { + for (Long deptId : userDeptIds) { + if (Func.isEmpty(deptId) || deptId <= 0) { + continue; + } List childIds = SysCache.getDeptChildIds(deptId); if (Func.isNotEmpty(childIds)) { allowedDeptIds.addAll(childIds); } else { allowedDeptIds.add(deptId); } - } else { - allowedDeptIds.add(deptId); } + } else if (Func.isNotEmpty(scope.getDeptIds())) { + allowedDeptIds.addAll(scope.getDeptIds()); } - return new DeptDataScopeFilter(false, includeChildren, allowedDeptIds); + if (Func.isEmpty(allowedDeptIds)) { + return DeptDataScopeFilter.empty(); + } + return new DeptDataScopeFilter(false, true, allowedDeptIds); } /** diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java new file mode 100644 index 00000000..0314afc9 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleMenuDataScopeServiceImpl.java @@ -0,0 +1,288 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springblade.core.cache.utils.CacheUtil; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.node.ForestNodeMerger; +import org.springblade.core.tool.node.TreeNode; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.SysCache; +import org.springblade.system.mapper.MenuMapper; +import org.springblade.system.mapper.RoleMenuDataScopeDeptMapper; +import org.springblade.system.mapper.RoleMenuDataScopeMapper; +import org.springblade.system.pojo.dto.RoleMenuDataScopeDTO; +import org.springblade.system.pojo.entity.Menu; +import org.springblade.system.pojo.entity.Role; +import org.springblade.system.pojo.entity.RoleMenuDataScope; +import org.springblade.system.pojo.entity.RoleMenuDataScopeDept; +import org.springblade.system.pojo.enums.RoleDataScopeType; +import org.springblade.system.pojo.vo.RoleDataScopeVO; +import org.springblade.system.pojo.vo.RoleMenuDataScopeTreeVO; +import org.springblade.system.pojo.vo.RoleMenuDataScopeVO; +import org.springblade.system.service.IRoleMenuDataScopeService; +import org.springblade.system.service.IRoleService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE; + +/** + * 角色菜单数据权限服务实现 + * + * @author Chill + */ +@Service +@RequiredArgsConstructor +public class RoleMenuDataScopeServiceImpl extends BaseServiceImpl + implements IRoleMenuDataScopeService { + + private final RoleMenuDataScopeDeptMapper roleMenuDataScopeDeptMapper; + private final MenuMapper menuMapper; + private final IRoleService roleService; + + @Override + public RoleMenuDataScopeTreeVO tree(Long roleId) { + if (Func.isEmpty(roleId) || roleId <= 0) { + throw new ServiceException("角色ID不能为空"); + } + Role role = roleService.getById(roleId); + if (Func.isEmpty(role)) { + throw new ServiceException("角色不存在"); + } + RoleMenuDataScopeTreeVO treeVO = new RoleMenuDataScopeTreeVO(); + treeVO.setRoleName(role.getRoleName()); + List menuNodes = menuMapper.grantTree(); + treeVO.setMenuTree(ForestNodeMerger.merge(menuNodes)); + + List scopeList = list(Wrappers.lambdaQuery() + .eq(RoleMenuDataScope::getRoleId, roleId) + .eq(RoleMenuDataScope::getIsDeleted, 0)); + if (Func.isEmpty(scopeList)) { + treeVO.setScopes(Collections.emptyList()); + return treeVO; + } + List scopeIds = scopeList.stream().map(RoleMenuDataScope::getId).toList(); + Map> deptMap = roleMenuDataScopeDeptMapper.selectList(Wrappers.lambdaQuery() + .in(RoleMenuDataScopeDept::getScopeId, scopeIds)) + .stream() + .collect(Collectors.groupingBy(RoleMenuDataScopeDept::getScopeId, + Collectors.mapping(RoleMenuDataScopeDept::getDeptId, Collectors.toList()))); + + List menuIds = scopeList.stream().map(RoleMenuDataScope::getMenuId).distinct().toList(); + Map menuCodeMap = menuMapper.selectBatchIds(menuIds).stream() + .collect(Collectors.toMap(Menu::getId, Menu::getCode, (left, right) -> left)); + + List scopeVOList = new ArrayList<>(); + for (RoleMenuDataScope scope : scopeList) { + RoleMenuDataScopeVO scopeVO = new RoleMenuDataScopeVO(); + scopeVO.setMenuId(scope.getMenuId()); + scopeVO.setMenuCode(menuCodeMap.get(scope.getMenuId())); + scopeVO.setScopeType(scope.getScopeType() == null ? RoleDataScopeType.DEFAULT_CODE : scope.getScopeType()); + scopeVO.setScopeTypeName(RoleDataScopeType.nameOf(scopeVO.getScopeType())); + scopeVO.setDeptIds(deptMap.getOrDefault(scope.getId(), Collections.emptyList())); + scopeVOList.add(scopeVO); + } + treeVO.setScopes(scopeVOList); + return treeVO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean submit(RoleMenuDataScopeDTO dto) { + if (dto == null || Func.isEmpty(dto.getRoleId()) || dto.getRoleId() <= 0) { + throw new ServiceException("角色ID不能为空"); + } + if (Func.isEmpty(dto.getMenuIds())) { + throw new ServiceException("请选择要设置的菜单"); + } + RoleDataScopeType scopeType = RoleDataScopeType.of(dto.getScopeType()); + if (scopeType == RoleDataScopeType.CUSTOM && Func.isEmpty(dto.getDeptIds())) { + throw new ServiceException("自定义组织时请勾选组织"); + } + Role role = roleService.getById(dto.getRoleId()); + if (Func.isEmpty(role)) { + throw new ServiceException("角色不存在"); + } + String tenantId = Func.toStrWithEmpty(role.getTenantId(), AuthUtil.getTenantId()); + List menuIds = dto.getMenuIds().stream().filter(Objects::nonNull).distinct().toList(); + List deptIds = scopeType == RoleDataScopeType.CUSTOM + ? dto.getDeptIds().stream().filter(Objects::nonNull).distinct().toList() + : Collections.emptyList(); + + for (Long menuId : menuIds) { + RoleMenuDataScope exists = getOne(Wrappers.lambdaQuery() + .eq(RoleMenuDataScope::getRoleId, dto.getRoleId()) + .eq(RoleMenuDataScope::getMenuId, menuId) + .eq(RoleMenuDataScope::getIsDeleted, 0) + .last("limit 1")); + RoleMenuDataScope scope; + if (Func.isEmpty(exists)) { + scope = new RoleMenuDataScope(); + scope.setTenantId(tenantId); + scope.setRoleId(dto.getRoleId()); + scope.setMenuId(menuId); + scope.setScopeType(scopeType.getCode()); + save(scope); + } else { + scope = exists; + scope.setScopeType(scopeType.getCode()); + updateById(scope); + roleMenuDataScopeDeptMapper.delete(Wrappers.lambdaQuery() + .eq(RoleMenuDataScopeDept::getScopeId, scope.getId())); + } + if (scopeType == RoleDataScopeType.CUSTOM) { + for (Long deptId : deptIds) { + RoleMenuDataScopeDept scopeDept = new RoleMenuDataScopeDept(); + scopeDept.setScopeId(scope.getId()); + scopeDept.setDeptId(deptId); + roleMenuDataScopeDeptMapper.insert(scopeDept); + } + } + } + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + return true; + } + + @Override + public RoleDataScopeVO resolveByMenuCode(List roleIds, String menuCode, List userDeptIds) { + if (Func.isEmpty(roleIds) || Func.isBlank(menuCode)) { + return RoleDataScopeVO.all(); + } + Menu menu = menuMapper.selectOne(Wrappers.

lambdaQuery() + .eq(Menu::getCode, menuCode) + .eq(Menu::getIsDeleted, 0) + .last("limit 1")); + if (Func.isEmpty(menu)) { + return RoleDataScopeVO.all(); + } + List scopeList = list(Wrappers.lambdaQuery() + .in(RoleMenuDataScope::getRoleId, roleIds) + .eq(RoleMenuDataScope::getMenuId, menu.getId()) + .eq(RoleMenuDataScope::getIsDeleted, 0)); + return mergeScopes(scopeList, userDeptIds); + } + + @Override + public RoleDataScopeVO resolveWidest(List roleIds, List userDeptIds) { + if (Func.isEmpty(roleIds)) { + return RoleDataScopeVO.all(); + } + List scopeList = list(Wrappers.lambdaQuery() + .in(RoleMenuDataScope::getRoleId, roleIds) + .eq(RoleMenuDataScope::getIsDeleted, 0)); + return mergeScopes(scopeList, userDeptIds); + } + + private RoleDataScopeVO mergeScopes(List scopeList, List userDeptIds) { + if (Func.isEmpty(scopeList)) { + return RoleDataScopeVO.all(); + } + boolean hasAll = false; + boolean hasDeptScope = false; + boolean hasOwn = false; + LinkedHashSet allowedDeptIds = new LinkedHashSet<>(); + Map> customDeptMap = loadCustomDeptMap(scopeList); + + for (RoleMenuDataScope scope : scopeList) { + RoleDataScopeType type = RoleDataScopeType.of(scope.getScopeType()); + switch (type) { + case ALL -> hasAll = true; + case DEPT_AND_CHILD -> { + hasDeptScope = true; + allowedDeptIds.addAll(expandDeptIds(userDeptIds, true)); + } + case CUSTOM -> { + hasDeptScope = true; + allowedDeptIds.addAll(customDeptMap.getOrDefault(scope.getId(), Collections.emptyList())); + } + case OWN_CREATE -> hasOwn = true; + default -> { + } + } + } + if (hasAll) { + return RoleDataScopeVO.all(); + } + if (hasDeptScope) { + return RoleDataScopeVO.ofDepts(RoleDataScopeType.CUSTOM.getCode(), new ArrayList<>(allowedDeptIds)); + } + if (hasOwn) { + return RoleDataScopeVO.ownCreate(); + } + return RoleDataScopeVO.all(); + } + + private Map> loadCustomDeptMap(List scopeList) { + List customScopeIds = scopeList.stream() + .filter(scope -> RoleDataScopeType.CUSTOM.matches(scope.getScopeType())) + .map(RoleMenuDataScope::getId) + .toList(); + if (Func.isEmpty(customScopeIds)) { + return Collections.emptyMap(); + } + return roleMenuDataScopeDeptMapper.selectList(Wrappers.lambdaQuery() + .in(RoleMenuDataScopeDept::getScopeId, customScopeIds)) + .stream() + .collect(Collectors.groupingBy(RoleMenuDataScopeDept::getScopeId, + Collectors.mapping(RoleMenuDataScopeDept::getDeptId, Collectors.toList()))); + } + + private List expandDeptIds(List baseDeptIds, boolean includeChildren) { + if (Func.isEmpty(baseDeptIds)) { + return Collections.emptyList(); + } + LinkedHashSet result = new LinkedHashSet<>(); + for (Long deptId : baseDeptIds) { + if (Func.isEmpty(deptId) || deptId <= 0) { + continue; + } + if (includeChildren) { + List childIds = SysCache.getDeptChildIds(deptId); + if (Func.isNotEmpty(childIds)) { + result.addAll(childIds); + } else { + result.add(deptId); + } + } else { + result.add(deptId); + } + } + return new ArrayList<>(result); + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleServiceImpl.java index d15d1929..61d64a76 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/RoleServiceImpl.java @@ -138,7 +138,12 @@ public class RoleServiceImpl extends ServiceImpl implements IR public boolean grant(@NotEmpty List roleIds, List menuIds, List dataScopeIds, List apiScopeIds) { // 租户守卫:批量校验目标角色全部归属当前会话租户 TenantGuard.verifyBatch(this, roleIds, ROLE); - return grantRoleMenu(roleIds, menuIds) && grantDataScope(roleIds, dataScopeIds) && grantApiScope(roleIds, apiScopeIds); + // dataScopeIds 为 null 时跳过原生数据权限更新(角色菜单数据权限已独立配置,避免误清) + boolean result = grantRoleMenu(roleIds, menuIds); + if (dataScopeIds != null) { + result = result && grantDataScope(roleIds, dataScopeIds); + } + return result && grantApiScope(roleIds, apiScopeIds); } private boolean grantRoleMenu(List roleIds, List menuIds) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 499575eb..67ef69fb 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -1116,10 +1116,19 @@ public class UserServiceImpl extends BaseServiceImpl implement } private void applyUserDefaults(User user) { - if (user.getPersonCategory() == null) user.setPersonCategory(1); - if (user.getDataScopeRange() == null) user.setDataScopeRange(1); - if (user.getDataLevelRange() == null) user.setDataLevelRange(1); - if (user.getIncludeNewCustomer() == null) user.setIncludeNewCustomer(0); + if (user.getPersonCategory() == null) { + user.setPersonCategory(1); + } + // 组织/客商数据权限已迁移至角色菜单配置,用户侧字段仅保留兼容默认值,不再参与过滤。 + if (user.getDataScopeRange() == null) { + user.setDataScopeRange(2); + } + if (user.getDataLevelRange() == null) { + user.setDataLevelRange(1); + } + if (user.getIncludeNewCustomer() == null) { + user.setIncludeNewCustomer(0); + } } /** @@ -1133,33 +1142,16 @@ public class UserServiceImpl extends BaseServiceImpl implement } private boolean submitUserScopes(User user) { - userDataScopeMapper.delete(Wrappers.lambdaQuery().eq(UserDataScope::getUserId, user.getId())); - userCustomerScopeMapper.delete(Wrappers.lambdaQuery().eq(UserCustomerScope::getUserId, user.getId())); - if (Objects.equals(user.getDataScopeRange(), 3) && user.getDataScopeDeptIds() != null) { - user.getDataScopeDeptIds().forEach(deptId -> { - UserDataScope scope = new UserDataScope(); - scope.setUserId(user.getId()); - scope.setDeptId(deptId); - userDataScopeMapper.insert(scope); - }); - } - if (user.getCustomerIds() != null) { - user.getCustomerIds().forEach(customerId -> { - UserCustomerScope scope = new UserCustomerScope(); - scope.setUserId(user.getId()); - scope.setCustomerId(customerId); - userCustomerScopeMapper.insert(scope); - }); - } + // 用户级组织/客商数据权限已停用,改由角色菜单数据权限配置。 return true; } private User fillUserScopes(User user) { - if (user == null || user.getId() == null) return user; - user.setDataScopeDeptIds(userDataScopeMapper.selectList(Wrappers.lambdaQuery() - .eq(UserDataScope::getUserId, user.getId())).stream().map(UserDataScope::getDeptId).toList()); - user.setCustomerIds(userCustomerScopeMapper.selectList(Wrappers.lambdaQuery() - .eq(UserCustomerScope::getUserId, user.getId())).stream().map(UserCustomerScope::getCustomerId).toList()); + if (user == null || user.getId() == null) { + return user; + } + user.setDataScopeDeptIds(Collections.emptyList()); + user.setCustomerIds(Collections.emptyList()); return user; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java index 67125a3c..c21fc4b6 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CommonAddressController.java @@ -36,6 +36,7 @@ import org.springblade.core.excel.util.ExcelUtil; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.Func; @@ -162,9 +163,16 @@ public class CommonAddressController extends BladeController { if (Func.isNotEmpty(ids)) { queryWrapper.in(CommonAddress::getId, Func.toLongList(ids)); } - List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonAddress.getAllDept(), commonAddress.getDeptId(), "常用地址"); + List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonAddress.getAllDept(), commonAddress.getDeptId(), "常用地址", TransportBusinessSupport.MENU_COMMON_ADDRESS); if (Func.isNotEmpty(deptIdList)) { - queryWrapper.eq(CommonAddress::getDeptId, deptIdList.get(0)); + if (deptIdList.size() == 1) { + queryWrapper.eq(CommonAddress::getDeptId, deptIdList.get(0)); + } else { + queryWrapper.in(CommonAddress::getDeptId, deptIdList); + } + } + if (TransportBusinessSupport.isOwnCreateScope(TransportBusinessSupport.MENU_COMMON_ADDRESS)) { + queryWrapper.eq(CommonAddress::getCreateUser, AuthUtil.getUserId()); } if (Func.isNotEmpty(commonAddress.getAddressName())) { queryWrapper.like(CommonAddress::getAddressName, commonAddress.getAddressName().trim()); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CommonAddressMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CommonAddressMapper.xml index d59bcf2e..054f8a31 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CommonAddressMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CommonAddressMapper.xml @@ -79,6 +79,15 @@ AND dept_id = #{address.deptId} + + AND dept_id IN + + #{item} + + + + AND create_user = #{address.scopeCreateUser} + AND contact_name LIKE #{contactNameLike} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonAddressServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonAddressServiceImpl.java index 4dfe0d51..b8f1e083 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonAddressServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonAddressServiceImpl.java @@ -167,8 +167,14 @@ public class CommonAddressServiceImpl extends BaseServiceImpl deptIdList = TransportBusinessSupport.resolveDeptFilterIds(address.getAllDept(), address.getDeptId(), "常用地址"); - address.setDeptId(Func.isEmpty(deptIdList) ? null : deptIdList.get(0)); + List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(address.getAllDept(), address.getDeptId(), "常用地址", TransportBusinessSupport.MENU_COMMON_ADDRESS); + address.setDeptId(null); + address.setDeptIds(Func.isEmpty(deptIdList) ? null : deptIdList); + if (TransportBusinessSupport.isOwnCreateScope(TransportBusinessSupport.MENU_COMMON_ADDRESS)) { + address.setScopeCreateUser(AuthUtil.getUserId()); + } else { + address.setScopeCreateUser(null); + } address.setAddressName(trimToNull(address.getAddressName())); address.setAddressType(trimToNull(address.getAddressType())); address.setDetailAddress(trimToNull(address.getDetailAddress())); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java index dcb7be5d..5b739199 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CommonCargoServiceImpl.java @@ -28,6 +28,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springblade.common.excel.ImportFailureException; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; +import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.cache.UserCache; @@ -93,7 +94,7 @@ public class CommonCargoServiceImpl extends BaseServiceImpl deleteIdList = new ArrayList<>(); for (CommonCargo commonCargo : listByIds(idList)) { - TransportBusinessSupport.assertCurrentDept(commonCargo.getDeptId(), "常用货物"); + TransportBusinessSupport.assertCurrentDept(commonCargo.getDeptId(), "常用货物", TransportBusinessSupport.MENU_COMMON_CARGO); if (shouldSkipDelete(commonCargo)) { result.setSkippedCount(result.getSkippedCount() + 1); result.getSkippedCodes().add(commonCargo.getCargoCode()); @@ -214,7 +215,7 @@ public class CommonCargoServiceImpl extends BaseServiceImpl buildQuery(CommonCargoVO commonCargo) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(CommonCargo::getIsDeleted, 0); - List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonCargo.getAllDept(), commonCargo.getDeptId(), "常用货物"); + List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonCargo.getAllDept(), commonCargo.getDeptId(), "常用货物", TransportBusinessSupport.MENU_COMMON_CARGO); if (Func.isNotEmpty(deptIdList)) { if (deptIdList.size() == 1) { queryWrapper.eq(CommonCargo::getDeptId, deptIdList.get(0)); @@ -222,6 +223,9 @@ public class CommonCargoServiceImpl extends BaseServiceImpl deleteIdList = new ArrayList<>(); for (CommonRoute commonRoute : listByIds(idList)) { - TransportBusinessSupport.assertCurrentDept(commonRoute.getDeptId(), "常用线路"); + TransportBusinessSupport.assertCurrentDept(commonRoute.getDeptId(), "常用线路", TransportBusinessSupport.MENU_COMMON_ROUTE); if (shouldSkipDelete(commonRoute)) { result.setSkippedCount(result.getSkippedCount() + 1); result.getSkippedCodes().add(commonRoute.getRouteCode()); @@ -158,9 +159,16 @@ public class CommonRouteServiceImpl extends BaseServiceImpl buildQuery(CommonRouteVO commonRoute) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(CommonRoute::getIsDeleted, 0); - List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonRoute.getAllDept(), commonRoute.getDeptId(), "常用线路"); + List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonRoute.getAllDept(), commonRoute.getDeptId(), "常用线路", TransportBusinessSupport.MENU_COMMON_ROUTE); if (Func.isNotEmpty(deptIdList)) { - queryWrapper.eq(CommonRoute::getDeptId, deptIdList.get(0)); + if (deptIdList.size() == 1) { + queryWrapper.eq(CommonRoute::getDeptId, deptIdList.get(0)); + } else { + queryWrapper.in(CommonRoute::getDeptId, deptIdList); + } + } + if (TransportBusinessSupport.isOwnCreateScope(TransportBusinessSupport.MENU_COMMON_ROUTE)) { + queryWrapper.eq(CommonRoute::getCreateUser, AuthUtil.getUserId()); } if (Func.isNotEmpty(commonRoute.getRouteCode())) { queryWrapper.eq(CommonRoute::getRouteCode, commonRoute.getRouteCode()); @@ -336,7 +344,7 @@ public class CommonRouteServiceImpl extends BaseServiceImpl userIds = new ArrayList<>(baseMapper.selectIncludeNewCustomerUserIds(tenantId)); - Long currentUserId = AuthUtil.getUserId(); - if (Func.isNotEmpty(currentUserId) && !userIds.contains(currentUserId)) { - userIds.add(currentUserId); - } - userIds.forEach(userId -> { - Long scopeCount = userCustomerScopeMapper.selectCount(Wrappers.lambdaQuery() - .eq(UserCustomerScope::getUserId, userId) - .eq(UserCustomerScope::getCustomerId, customer.getId())); - if (scopeCount != null && scopeCount > 0) { - return; - } - UserCustomerScope scope = new UserCustomerScope(); - scope.setUserId(userId); - scope.setCustomerId(customer.getId()); - userCustomerScopeMapper.insert(scope); - }); - } private String nextCustomerCode() { CustomerArchive latest = list(Wrappers.lambdaQuery() @@ -1843,19 +1818,14 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl deleteIdList = new ArrayList<>(); List releaseWaybillIds = new ArrayList<>(); for (LoadingManage loadingManage : listByIds(idList)) { - TransportBusinessSupport.assertCurrentDept(loadingManage.getDeptId(), "配载管理"); + TransportBusinessSupport.assertCurrentDept(loadingManage.getDeptId(), "配载管理", TransportBusinessSupport.MENU_LOADING_MANAGE); if (!Objects.equals(loadingManage.getBusinessStatus(), STATUS_DRAFT)) { result.setSkippedCount(result.getSkippedCount() + 1); result.getSkippedCodes().add(loadingManage.getLoadingNo()); @@ -862,7 +862,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl deleteIdList = new ArrayList<>(); for (ProcessConfig processConfig : listByIds(idList)) { - TransportBusinessSupport.assertCurrentDept(processConfig.getDeptId(), "过程配置"); + TransportBusinessSupport.assertCurrentDept(processConfig.getDeptId(), "过程配置", TransportBusinessSupport.MENU_PROCESS_CONFIG); if (shouldSkipDelete(processConfig)) { result.setSkippedCount(result.getSkippedCount() + 1); result.getSkippedCodes().add(processConfig.getConfigCode()); @@ -253,9 +254,16 @@ public class ProcessConfigServiceImpl extends BaseServiceImpl buildQuery(ProcessConfigVO processConfig) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(ProcessConfig::getIsDeleted, 0); - List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(processConfig.getAllDept(), processConfig.getDeptId(), "过程配置"); + List deptIdList = TransportBusinessSupport.resolveDeptFilterIds(processConfig.getAllDept(), processConfig.getDeptId(), "过程配置", TransportBusinessSupport.MENU_PROCESS_CONFIG); if (Func.isNotEmpty(deptIdList)) { - queryWrapper.eq(ProcessConfig::getDeptId, deptIdList.get(0)); + if (deptIdList.size() == 1) { + queryWrapper.eq(ProcessConfig::getDeptId, deptIdList.get(0)); + } else { + queryWrapper.in(ProcessConfig::getDeptId, deptIdList); + } + } + if (TransportBusinessSupport.isOwnCreateScope(TransportBusinessSupport.MENU_PROCESS_CONFIG)) { + queryWrapper.eq(ProcessConfig::getCreateUser, AuthUtil.getUserId()); } if (Func.isNotEmpty(processConfig.getConfigCode())) { queryWrapper.eq(ProcessConfig::getConfigCode, processConfig.getConfigCode()); @@ -323,7 +331,7 @@ public class ProcessConfigServiceImpl extends BaseServiceImpl deleteIdList = new ArrayList<>(); for (ShippingTemplate shippingTemplate : listByIds(idList)) { - TransportBusinessSupport.assertCurrentDept(shippingTemplate.getDeptId(), "发货模板"); + TransportBusinessSupport.assertCurrentDept(shippingTemplate.getDeptId(), "发货模板", TransportBusinessSupport.MENU_SHIPPING_TEMPLATE); if (shouldSkipDelete(shippingTemplate)) { result.setSkippedCount(result.getSkippedCount() + 1); result.getSkippedCodes().add(shippingTemplate.getTemplateCode()); @@ -479,7 +479,7 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl deleteIdList = new ArrayList<>(); for (TransportPlan transportPlan : listByIds(idList)) { - TransportBusinessSupport.assertCurrentDept(transportPlan.getDeptId(), "运输计划"); + TransportBusinessSupport.assertCurrentDept(transportPlan.getDeptId(), "运输计划", TransportBusinessSupport.MENU_TRANSPORT_PLAN); if (shouldSkipDelete(transportPlan)) { result.setSkippedCount(result.getSkippedCount() + 1); result.getSkippedCodes().add(transportPlan.getPlanNo()); @@ -847,7 +847,7 @@ public class TransportPlanServiceImpl extends BaseServiceImpl BusinessRemoveResultVO result = new BusinessRemoveResultVO(); List deleteIdList = new ArrayList<>(); for (Waybill waybill : listByIds(idList)) { - TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理"); + TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理", TransportBusinessSupport.MENU_WAYBILL_MANAGE); if (shouldSkipDelete(waybill)) { result.setSkippedCount(result.getSkippedCount() + 1); result.getSkippedCodes().add(waybill.getWaybillNo()); @@ -1324,9 +1324,16 @@ public class WaybillServiceImpl extends BaseServiceImpl if (Func.isNotEmpty(idList)) { queryWrapper = Wrappers.lambdaQuery().eq(Waybill::getIsDeleted, 0); List deptIdList = TransportBusinessSupport.resolveDeptFilterIds( - waybill.getAllDept(), waybill.getDeptId(), "运单管理"); + waybill.getAllDept(), waybill.getDeptId(), "运单管理", TransportBusinessSupport.MENU_WAYBILL_MANAGE); if (Func.isNotEmpty(deptIdList)) { - queryWrapper.eq(Waybill::getDeptId, deptIdList.get(0)); + if (deptIdList.size() == 1) { + queryWrapper.eq(Waybill::getDeptId, deptIdList.get(0)); + } else { + queryWrapper.in(Waybill::getDeptId, deptIdList); + } + } + if (TransportBusinessSupport.isOwnCreateScope(TransportBusinessSupport.MENU_WAYBILL_MANAGE)) { + queryWrapper.eq(Waybill::getCreateUser, AuthUtil.getUserId()); } queryWrapper.in(Waybill::getId, idList).orderByDesc(Waybill::getCreateTime); } else { @@ -1788,9 +1795,16 @@ public class WaybillServiceImpl extends BaseServiceImpl LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery().eq(Waybill::getIsDeleted, 0); // 与常用线路等模块一致:超管默认不按组织过滤;普通用户 allDept!=1 时强制当前组织 List deptIdList = TransportBusinessSupport.resolveDeptFilterIds( - waybill.getAllDept(), waybill.getDeptId(), "运单管理"); + waybill.getAllDept(), waybill.getDeptId(), "运单管理", TransportBusinessSupport.MENU_WAYBILL_MANAGE); if (Func.isNotEmpty(deptIdList)) { - queryWrapper.eq(Waybill::getDeptId, deptIdList.get(0)); + if (deptIdList.size() == 1) { + queryWrapper.eq(Waybill::getDeptId, deptIdList.get(0)); + } else { + queryWrapper.in(Waybill::getDeptId, deptIdList); + } + } + if (TransportBusinessSupport.isOwnCreateScope(TransportBusinessSupport.MENU_WAYBILL_MANAGE)) { + queryWrapper.eq(Waybill::getCreateUser, AuthUtil.getUserId()); } if (Func.isNotEmpty(waybill.getWaybillNo())) { queryWrapper.eq(Waybill::getWaybillNo, waybill.getWaybillNo()); @@ -2210,7 +2224,7 @@ public class WaybillServiceImpl extends BaseServiceImpl throw new ServiceException("运单管理不存在"); } if (checkDept) { - TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理"); + TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理", TransportBusinessSupport.MENU_WAYBILL_MANAGE); } return waybill; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/TransportBusinessSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/TransportBusinessSupport.java index 1a17390d..db8d6b16 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/TransportBusinessSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/TransportBusinessSupport.java @@ -27,10 +27,10 @@ import org.springblade.core.log.exception.ServiceException; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.constant.RoleConstant; import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.RoleDataScopeCache; import org.springblade.system.cache.SysCache; -import org.springblade.system.cache.UserCache; import org.springblade.system.pojo.entity.Dept; -import org.springblade.system.pojo.entity.User; +import org.springblade.system.pojo.vo.RoleDataScopeVO; import java.math.BigDecimal; import java.time.LocalDate; @@ -52,13 +52,30 @@ public final class TransportBusinessSupport { public static final String DATA_SOURCE_EXTERNAL = "外部系统"; private static final Pattern PHONE_PATTERN = Pattern.compile("^(1\\d{10}|0\\d{2,3}-?\\d{7,8})$"); - /** 用户档案「数据权限范围」的取值:全部组织,与组织树过滤口径一致 */ - private static final int DATA_SCOPE_ALL = 2; private static final BigDecimal MIN_LONGITUDE = new BigDecimal("-180"); private static final BigDecimal MAX_LONGITUDE = new BigDecimal("180"); private static final BigDecimal MIN_LATITUDE = new BigDecimal("-90"); private static final BigDecimal MAX_LATITUDE = new BigDecimal("90"); + /** 运单管理菜单编码 */ + public static final String MENU_WAYBILL_MANAGE = "waybill_manage"; + /** 配载管理菜单编码 */ + public static final String MENU_LOADING_MANAGE = "loading_manage"; + /** 运输计划菜单编码 */ + public static final String MENU_TRANSPORT_PLAN = "transport_plan"; + /** 常用线路菜单编码 */ + public static final String MENU_COMMON_ROUTE = "common_route"; + /** 常用货物菜单编码 */ + public static final String MENU_COMMON_CARGO = "common_cargo"; + /** 常用地址菜单编码 */ + public static final String MENU_COMMON_ADDRESS = "common_address"; + /** 过程配置菜单编码 */ + public static final String MENU_PROCESS_CONFIG = "process_config"; + /** 发货模板菜单编码 */ + public static final String MENU_SHIPPING_TEMPLATE = "shipping_template"; + /** 临时额度管理菜单编码 */ + public static final String MENU_TEMPORARY_CREDIT_LIMIT = "temporary_credit_limit"; + private TransportBusinessSupport() { } @@ -81,23 +98,43 @@ public final class TransportBusinessSupport { } /** - * 是否被授予跨组织数据权限:超级管理员,或用户档案「数据权限范围」为全部组织。 - *

- * 「数据权限范围」在用户管理里配置,组织树已按它过滤(DeptServiceImpl 的组织树数据权限); - * 运输业务的列表过滤与写入校验必须与组织树同一口径。 + * 解析指定菜单下的角色数据权限(超管视为全部组织)。 + */ + public static RoleDataScopeVO resolveRoleDataScope(String menuCode) { + if (isAdministrator()) { + return RoleDataScopeVO.all(); + } + RoleDataScopeVO scope = RoleDataScopeCache.resolveByMenuCode(menuCode); + return scope == null ? RoleDataScopeVO.all() : scope; + } + + /** + * 是否被授予跨组织数据权限:超级管理员,或角色在该菜单下配置为全部组织。 * + * @param menuCode 菜单编码 * @return true-不限组织 */ + public static boolean hasAllDeptScope(String menuCode) { + return resolveRoleDataScope(menuCode).isAllDept(); + } + + /** + * 兼容旧调用:无菜单上下文时按全部角色菜单配置最宽合并。 + */ public static boolean hasAllDeptScope() { if (isAdministrator()) { return true; } - Long userId = AuthUtil.getUserId(); - if (Func.isEmpty(userId) || userId <= 0) { - return false; - } - User user = UserCache.getUser(userId); - return Objects.equals(user == null ? null : user.getDataScopeRange(), DATA_SCOPE_ALL); + RoleDataScopeVO scope = RoleDataScopeCache.resolveWidest(); + return scope == null || scope.isAllDept(); + } + + /** + * 是否仅本人创建数据权限 + */ + public static boolean isOwnCreateScope(String menuCode) { + RoleDataScopeVO scope = resolveRoleDataScope(menuCode); + return scope.isOwnCreate(); } public static Long currentDeptId(String moduleName) { @@ -118,18 +155,52 @@ public final class TransportBusinessSupport { /** * 校验目标业务数据属于当前用户可操作的组织。 - *

- * 超管与「数据权限范围=全部组织」的用户不限组织;其余用户仍严格要求与当前会话组织相等。 + */ + public static void assertCurrentDept(Long deptId, String moduleName, String menuCode) { + RoleDataScopeVO scope = resolveRoleDataScope(menuCode); + if (scope.isAllDept() || scope.isOwnCreate()) { + return; + } + List allowedDeptIds = scope.getDeptIds(); + if (Func.isEmpty(allowedDeptIds)) { + if (!Objects.equals(deptId, currentDeptId(moduleName))) { + throw new ServiceException("无权操作其他组织" + moduleName); + } + return; + } + if (!allowedDeptIds.contains(deptId)) { + throw new ServiceException("无权操作其他组织" + moduleName); + } + } + + /** + * 兼容旧调用:按最宽合并校验 */ public static void assertCurrentDept(Long deptId, String moduleName) { if (hasAllDeptScope()) { return; } + RoleDataScopeVO scope = RoleDataScopeCache.resolveWidest(); + if (scope != null && scope.isOwnCreate()) { + return; + } + if (scope != null && Func.isNotEmpty(scope.getDeptIds())) { + if (!scope.getDeptIds().contains(deptId)) { + throw new ServiceException("无权操作其他组织" + moduleName); + } + return; + } if (!Objects.equals(deptId, currentDeptId(moduleName))) { throw new ServiceException("无权操作其他组织" + moduleName); } } + public static void validateAllDept(Integer allDept, String moduleName, String menuCode) { + if (Objects.equals(allDept, 1) && !hasAllDeptScope(menuCode)) { + throw new ServiceException("无权查看全部组织" + moduleName); + } + } + public static void validateAllDept(Integer allDept, String moduleName) { if (Objects.equals(allDept, 1) && !hasAllDeptScope()) { throw new ServiceException("无权查看全部组织" + moduleName); @@ -138,15 +209,58 @@ public final class TransportBusinessSupport { /** * 解析列表组织筛选 ID。 - *

- * 普通用户 allDept!=1 时强制当前组织;超管与「全部组织」用户默认不按组织过滤, - * 仅在 allDept=1 且传入 deptId 时精确匹配该组织。 * * @param allDept 是否查看全部组织 * @param deptId 筛选组织 ID * @param moduleName 模块名称 + * @param menuCode 菜单编码 * @return 需要过滤的组织 ID 集合;空集合表示不过滤组织 */ + public static List resolveDeptFilterIds(Integer allDept, Long deptId, String moduleName, String menuCode) { + RoleDataScopeVO scope = resolveRoleDataScope(menuCode); + if (scope.isAllDept()) { + validateAllDept(allDept, moduleName, menuCode); + if (Objects.equals(allDept, 1) && Func.isNotEmpty(deptId) && deptId > 0) { + return Collections.singletonList(deptId); + } + return Collections.emptyList(); + } + if (scope.isOwnCreate()) { + // 本人创建不按组织收口;若前端传了组织筛选则精确匹配 + if (Objects.equals(allDept, 1) && Func.isNotEmpty(deptId) && deptId > 0) { + return Collections.singletonList(deptId); + } + return Collections.emptyList(); + } + List allowedDeptIds = scope.getDeptIds(); + if (Func.isEmpty(allowedDeptIds)) { + if (!Objects.equals(allDept, 1)) { + return Collections.singletonList(currentDeptId(moduleName)); + } + if (Func.isEmpty(deptId) || deptId <= 0) { + return Collections.emptyList(); + } + return Collections.singletonList(deptId); + } + if (Objects.equals(allDept, 1)) { + if (Func.isEmpty(deptId) || deptId <= 0) { + return allowedDeptIds; + } + if (!allowedDeptIds.contains(deptId)) { + throw new ServiceException("无权查看该组织" + moduleName); + } + return Collections.singletonList(deptId); + } + Long currentDeptId = Func.firstLong(AuthUtil.getDeptId()); + if (Func.isNotEmpty(currentDeptId) && allowedDeptIds.contains(currentDeptId)) { + return Collections.singletonList(currentDeptId); + } + return allowedDeptIds; + } + + /** + * 兼容旧调用:按最宽合并解析组织筛选 + */ public static List resolveDeptFilterIds(Integer allDept, Long deptId, String moduleName) { validateAllDept(allDept, moduleName); if (hasAllDeptScope()) { @@ -155,14 +269,35 @@ public final class TransportBusinessSupport { } return Collections.emptyList(); } + RoleDataScopeVO scope = RoleDataScopeCache.resolveWidest(); + if (scope != null && scope.isOwnCreate()) { + if (Objects.equals(allDept, 1) && Func.isNotEmpty(deptId) && deptId > 0) { + return Collections.singletonList(deptId); + } + return Collections.emptyList(); + } + if (scope != null && Func.isNotEmpty(scope.getDeptIds())) { + if (Objects.equals(allDept, 1)) { + if (Func.isEmpty(deptId) || deptId <= 0) { + return scope.getDeptIds(); + } + if (!scope.getDeptIds().contains(deptId)) { + throw new ServiceException("无权查看该组织" + moduleName); + } + return Collections.singletonList(deptId); + } + Long currentDeptId = Func.firstLong(AuthUtil.getDeptId()); + if (Func.isNotEmpty(currentDeptId) && scope.getDeptIds().contains(currentDeptId)) { + return Collections.singletonList(currentDeptId); + } + return scope.getDeptIds(); + } if (!Objects.equals(allDept, 1)) { return Collections.singletonList(currentDeptId(moduleName)); } - // Jackson 空值可能被反序列化为 -1,这里与空值同等对待,避免误过滤。 if (Func.isEmpty(deptId) || deptId <= 0) { return Collections.emptyList(); } - // 列表组织筛选项为单选,按所选组织精确过滤,不自动展开下级。 return Collections.singletonList(deptId); } diff --git a/doc/sql/transport/blade_role_menu_data_scope_20261027.sql b/doc/sql/transport/blade_role_menu_data_scope_20261027.sql new file mode 100644 index 00000000..113546aa --- /dev/null +++ b/doc/sql/transport/blade_role_menu_data_scope_20261027.sql @@ -0,0 +1,28 @@ +-- 角色菜单数据权限:按角色×菜单配置组织数据范围(默认全部组织) +CREATE TABLE IF NOT EXISTS `blade_role_menu_data_scope` ( + `id` bigint NOT NULL COMMENT '主键', + `tenant_id` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '000000' COMMENT '租户ID', + `role_id` bigint NOT NULL COMMENT '角色主键', + `menu_id` bigint NOT NULL COMMENT '菜单主键', + `scope_type` tinyint NOT NULL DEFAULT 2 COMMENT '数据权限:1所在部门及下级 2全部组织 3本人创建 4自定义组织', + `create_user` bigint NULL DEFAULT NULL COMMENT '创建人', + `create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_user` bigint NULL DEFAULT NULL COMMENT '修改人', + `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间', + `status` int NULL DEFAULT 1 COMMENT '状态', + `is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_role_menu_data_scope` (`role_id`, `menu_id`) USING BTREE, + KEY `idx_role_menu_data_scope_menu` (`menu_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色菜单数据权限'; + +-- 角色菜单数据权限-自定义组织明细 +CREATE TABLE IF NOT EXISTS `blade_role_menu_data_scope_dept` ( + `id` bigint NOT NULL COMMENT '主键', + `scope_id` bigint NOT NULL COMMENT '角色菜单数据权限主键', + `dept_id` bigint NOT NULL COMMENT '组织主键', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_role_menu_data_scope_dept` (`scope_id`, `dept_id`) USING BTREE, + KEY `idx_role_menu_data_scope_dept_dept` (`dept_id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='角色菜单数据权限自定义组织';