✨ 角色菜单数据权限替代用户级组织/客商权限
将后台数据权限迁移到角色×菜单配置,运行时按 menuCode 解析;组织树取最宽合并;停用用户侧组织与客商授权过滤。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleDataScopeVO> 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<RoleDataScopeVO> 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);
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleDataScopeVO> 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<RoleDataScopeVO> resolveWidest(@RequestParam("roleIds") String roleIds,
|
||||
@RequestParam(value = "deptIds", required = false) String deptIds);
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleDataScopeVO> resolveByMenuCode(String roleIds, String menuCode, String deptIds) {
|
||||
return R.data(RoleDataScopeVO.all());
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<RoleDataScopeVO> resolveWidest(String roleIds, String deptIds) {
|
||||
return R.data(RoleDataScopeVO.all());
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<Long> menuIds;
|
||||
|
||||
@Schema(description = "数据权限类型:1所在部门及下级 2全部组织 3本人创建 4自定义组织")
|
||||
private Integer scopeType;
|
||||
|
||||
@Schema(description = "自定义组织ID集合(scopeType=4 时必填)")
|
||||
private List<Long> deptIds;
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<Long> 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<Long> 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;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<TreeNode> menuTree = new ArrayList<>();
|
||||
|
||||
@Schema(description = "已配置的菜单数据权限")
|
||||
private List<RoleMenuDataScopeVO> scopes = new ArrayList<>();
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<Long> deptIds = new ArrayList<>();
|
||||
|
||||
}
|
||||
+9
@@ -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<Long> deptIds;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "仅本人创建时的创建人ID")
|
||||
private Long scopeCreateUser;
|
||||
|
||||
}
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleMenuDataScopeTreeVO> 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));
|
||||
}
|
||||
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleDataScopeVO> 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<RoleDataScopeVO> resolveWidest(String roleIds, String deptIds) {
|
||||
return R.data(roleMenuDataScopeService.resolveWidest(Func.toLongList(roleIds), Func.toLongList(deptIds)));
|
||||
}
|
||||
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleMenuDataScopeDept> {
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleMenuDataScope> {
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleMenuDataScope> {
|
||||
|
||||
/**
|
||||
* 查询角色数据权限配置树
|
||||
*
|
||||
* @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<Long> roleIds, String menuCode, List<Long> userDeptIds);
|
||||
|
||||
/**
|
||||
* 合并用户全部角色菜单配置为最宽数据权限
|
||||
*
|
||||
* @param roleIds 角色ID集合
|
||||
* @param userDeptIds 用户所属组织
|
||||
* @return 解析结果
|
||||
*/
|
||||
RoleDataScopeVO resolveWidest(List<Long> roleIds, List<Long> userDeptIds);
|
||||
|
||||
}
|
||||
+23
-48
@@ -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<DeptMapper, Dept> 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<DeptMapper, Dept> implements ID
|
||||
/**
|
||||
* 解析当前登录用户的组织树数据权限。
|
||||
* <p>
|
||||
* 超管或「全部组织」返回 allDept=true;其余按所属组织/自定义组织,并结合「包含下级/仅本级」展开。
|
||||
* 超管或角色菜单数据权限最宽合并为「全部组织」时不过滤;
|
||||
* 其余按角色配置的组织并集过滤。仅「本人创建」时按用户所属组织及下级展示组织树。
|
||||
*
|
||||
* @return 过滤上下文;无法识别用户时按空权限处理
|
||||
*/
|
||||
@@ -246,55 +237,39 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> 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<Long> roleIds = Func.toLongList(AuthUtil.getUser().getRoleId());
|
||||
List<Long> 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<Long> baseDeptIds;
|
||||
if (Objects.equals(dataScopeRange, DATA_SCOPE_CUSTOM)) {
|
||||
baseDeptIds = userDataScopeMapper.selectList(Wrappers.<UserDataScope>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<Long> 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<Long> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+288
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* 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<RoleMenuDataScopeMapper, RoleMenuDataScope>
|
||||
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<TreeNode> menuNodes = menuMapper.grantTree();
|
||||
treeVO.setMenuTree(ForestNodeMerger.merge(menuNodes));
|
||||
|
||||
List<RoleMenuDataScope> scopeList = list(Wrappers.<RoleMenuDataScope>lambdaQuery()
|
||||
.eq(RoleMenuDataScope::getRoleId, roleId)
|
||||
.eq(RoleMenuDataScope::getIsDeleted, 0));
|
||||
if (Func.isEmpty(scopeList)) {
|
||||
treeVO.setScopes(Collections.emptyList());
|
||||
return treeVO;
|
||||
}
|
||||
List<Long> scopeIds = scopeList.stream().map(RoleMenuDataScope::getId).toList();
|
||||
Map<Long, List<Long>> deptMap = roleMenuDataScopeDeptMapper.selectList(Wrappers.<RoleMenuDataScopeDept>lambdaQuery()
|
||||
.in(RoleMenuDataScopeDept::getScopeId, scopeIds))
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(RoleMenuDataScopeDept::getScopeId,
|
||||
Collectors.mapping(RoleMenuDataScopeDept::getDeptId, Collectors.toList())));
|
||||
|
||||
List<Long> menuIds = scopeList.stream().map(RoleMenuDataScope::getMenuId).distinct().toList();
|
||||
Map<Long, String> menuCodeMap = menuMapper.selectBatchIds(menuIds).stream()
|
||||
.collect(Collectors.toMap(Menu::getId, Menu::getCode, (left, right) -> left));
|
||||
|
||||
List<RoleMenuDataScopeVO> 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<Long> menuIds = dto.getMenuIds().stream().filter(Objects::nonNull).distinct().toList();
|
||||
List<Long> deptIds = scopeType == RoleDataScopeType.CUSTOM
|
||||
? dto.getDeptIds().stream().filter(Objects::nonNull).distinct().toList()
|
||||
: Collections.emptyList();
|
||||
|
||||
for (Long menuId : menuIds) {
|
||||
RoleMenuDataScope exists = getOne(Wrappers.<RoleMenuDataScope>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.<RoleMenuDataScopeDept>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<Long> roleIds, String menuCode, List<Long> userDeptIds) {
|
||||
if (Func.isEmpty(roleIds) || Func.isBlank(menuCode)) {
|
||||
return RoleDataScopeVO.all();
|
||||
}
|
||||
Menu menu = menuMapper.selectOne(Wrappers.<Menu>lambdaQuery()
|
||||
.eq(Menu::getCode, menuCode)
|
||||
.eq(Menu::getIsDeleted, 0)
|
||||
.last("limit 1"));
|
||||
if (Func.isEmpty(menu)) {
|
||||
return RoleDataScopeVO.all();
|
||||
}
|
||||
List<RoleMenuDataScope> scopeList = list(Wrappers.<RoleMenuDataScope>lambdaQuery()
|
||||
.in(RoleMenuDataScope::getRoleId, roleIds)
|
||||
.eq(RoleMenuDataScope::getMenuId, menu.getId())
|
||||
.eq(RoleMenuDataScope::getIsDeleted, 0));
|
||||
return mergeScopes(scopeList, userDeptIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleDataScopeVO resolveWidest(List<Long> roleIds, List<Long> userDeptIds) {
|
||||
if (Func.isEmpty(roleIds)) {
|
||||
return RoleDataScopeVO.all();
|
||||
}
|
||||
List<RoleMenuDataScope> scopeList = list(Wrappers.<RoleMenuDataScope>lambdaQuery()
|
||||
.in(RoleMenuDataScope::getRoleId, roleIds)
|
||||
.eq(RoleMenuDataScope::getIsDeleted, 0));
|
||||
return mergeScopes(scopeList, userDeptIds);
|
||||
}
|
||||
|
||||
private RoleDataScopeVO mergeScopes(List<RoleMenuDataScope> scopeList, List<Long> userDeptIds) {
|
||||
if (Func.isEmpty(scopeList)) {
|
||||
return RoleDataScopeVO.all();
|
||||
}
|
||||
boolean hasAll = false;
|
||||
boolean hasDeptScope = false;
|
||||
boolean hasOwn = false;
|
||||
LinkedHashSet<Long> allowedDeptIds = new LinkedHashSet<>();
|
||||
Map<Long, List<Long>> 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<Long, List<Long>> loadCustomDeptMap(List<RoleMenuDataScope> scopeList) {
|
||||
List<Long> 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.<RoleMenuDataScopeDept>lambdaQuery()
|
||||
.in(RoleMenuDataScopeDept::getScopeId, customScopeIds))
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(RoleMenuDataScopeDept::getScopeId,
|
||||
Collectors.mapping(RoleMenuDataScopeDept::getDeptId, Collectors.toList())));
|
||||
}
|
||||
|
||||
private List<Long> expandDeptIds(List<Long> baseDeptIds, boolean includeChildren) {
|
||||
if (Func.isEmpty(baseDeptIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LinkedHashSet<Long> result = new LinkedHashSet<>();
|
||||
for (Long deptId : baseDeptIds) {
|
||||
if (Func.isEmpty(deptId) || deptId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (includeChildren) {
|
||||
List<Long> childIds = SysCache.getDeptChildIds(deptId);
|
||||
if (Func.isNotEmpty(childIds)) {
|
||||
result.addAll(childIds);
|
||||
} else {
|
||||
result.add(deptId);
|
||||
}
|
||||
} else {
|
||||
result.add(deptId);
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(result);
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -138,7 +138,12 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR
|
||||
public boolean grant(@NotEmpty List<Long> roleIds, List<Long> menuIds, List<Long> dataScopeIds, List<Long> 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<Long> roleIds, List<Long> menuIds) {
|
||||
|
||||
+19
-27
@@ -1116,10 +1116,19 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> 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<UserMapper, User> implement
|
||||
}
|
||||
|
||||
private boolean submitUserScopes(User user) {
|
||||
userDataScopeMapper.delete(Wrappers.<UserDataScope>lambdaQuery().eq(UserDataScope::getUserId, user.getId()));
|
||||
userCustomerScopeMapper.delete(Wrappers.<UserCustomerScope>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.<UserDataScope>lambdaQuery()
|
||||
.eq(UserDataScope::getUserId, user.getId())).stream().map(UserDataScope::getDeptId).toList());
|
||||
user.setCustomerIds(userCustomerScopeMapper.selectList(Wrappers.<UserCustomerScope>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;
|
||||
}
|
||||
|
||||
|
||||
+10
-2
@@ -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<Long> deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonAddress.getAllDept(), commonAddress.getDeptId(), "常用地址");
|
||||
List<Long> 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());
|
||||
|
||||
+9
@@ -79,6 +79,15 @@
|
||||
<if test="address.deptId != null">
|
||||
AND dept_id = #{address.deptId}
|
||||
</if>
|
||||
<if test="address.deptIds != null and address.deptIds.size() > 0">
|
||||
AND dept_id IN
|
||||
<foreach collection="address.deptIds" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
<if test="address.scopeCreateUser != null">
|
||||
AND create_user = #{address.scopeCreateUser}
|
||||
</if>
|
||||
<if test="address.contactName != null and address.contactName != ''">
|
||||
<bind name="contactNameLike" value="'%' + address.contactName + '%'"/>
|
||||
AND contact_name LIKE #{contactNameLike}
|
||||
|
||||
+8
-2
@@ -167,8 +167,14 @@ public class CommonAddressServiceImpl extends BaseServiceImpl<CommonAddressMappe
|
||||
}
|
||||
|
||||
private void prepareQuery(CommonAddressVO address) {
|
||||
List<Long> deptIdList = TransportBusinessSupport.resolveDeptFilterIds(address.getAllDept(), address.getDeptId(), "常用地址");
|
||||
address.setDeptId(Func.isEmpty(deptIdList) ? null : deptIdList.get(0));
|
||||
List<Long> 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()));
|
||||
|
||||
+7
-3
@@ -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<CommonCargoMapper, C
|
||||
BusinessRemoveResultVO result = new BusinessRemoveResultVO();
|
||||
List<Long> 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<CommonCargoMapper, C
|
||||
|
||||
private LambdaQueryWrapper<CommonCargo> buildQuery(CommonCargoVO commonCargo) {
|
||||
LambdaQueryWrapper<CommonCargo> queryWrapper = Wrappers.<CommonCargo>lambdaQuery().eq(CommonCargo::getIsDeleted, 0);
|
||||
List<Long> deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonCargo.getAllDept(), commonCargo.getDeptId(), "常用货物");
|
||||
List<Long> 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<CommonCargoMapper, C
|
||||
queryWrapper.in(CommonCargo::getDeptId, deptIdList);
|
||||
}
|
||||
}
|
||||
if (TransportBusinessSupport.isOwnCreateScope(TransportBusinessSupport.MENU_COMMON_CARGO)) {
|
||||
queryWrapper.eq(CommonCargo::getCreateUser, AuthUtil.getUserId());
|
||||
}
|
||||
if (Func.isNotEmpty(commonCargo.getFirstCargoTypeId())) {
|
||||
queryWrapper.eq(CommonCargo::getFirstCargoTypeId, commonCargo.getFirstCargoTypeId());
|
||||
}
|
||||
@@ -355,7 +359,7 @@ public class CommonCargoServiceImpl extends BaseServiceImpl<CommonCargoMapper, C
|
||||
throw new ServiceException("常用货物不存在");
|
||||
}
|
||||
if (checkDept) {
|
||||
TransportBusinessSupport.assertCurrentDept(commonCargo.getDeptId(), "常用货物");
|
||||
TransportBusinessSupport.assertCurrentDept(commonCargo.getDeptId(), "常用货物", TransportBusinessSupport.MENU_COMMON_CARGO);
|
||||
}
|
||||
return commonCargo;
|
||||
}
|
||||
|
||||
+12
-4
@@ -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.SysCache;
|
||||
@@ -96,7 +97,7 @@ public class CommonRouteServiceImpl extends BaseServiceImpl<CommonRouteMapper, C
|
||||
BusinessRemoveResultVO result = new BusinessRemoveResultVO();
|
||||
List<Long> 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<CommonRouteMapper, C
|
||||
|
||||
private LambdaQueryWrapper<CommonRoute> buildQuery(CommonRouteVO commonRoute) {
|
||||
LambdaQueryWrapper<CommonRoute> queryWrapper = Wrappers.<CommonRoute>lambdaQuery().eq(CommonRoute::getIsDeleted, 0);
|
||||
List<Long> deptIdList = TransportBusinessSupport.resolveDeptFilterIds(commonRoute.getAllDept(), commonRoute.getDeptId(), "常用线路");
|
||||
List<Long> 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<CommonRouteMapper, C
|
||||
throw new ServiceException("常用线路不存在");
|
||||
}
|
||||
if (checkDept) {
|
||||
TransportBusinessSupport.assertCurrentDept(commonRoute.getDeptId(), "常用线路");
|
||||
TransportBusinessSupport.assertCurrentDept(commonRoute.getDeptId(), "常用线路", TransportBusinessSupport.MENU_COMMON_ROUTE);
|
||||
}
|
||||
return commonRoute;
|
||||
}
|
||||
|
||||
+4
-34
@@ -41,7 +41,6 @@ import org.springblade.core.tenant.annotation.TenantIgnore;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.pojo.entity.UserCustomerScope;
|
||||
import org.springblade.thirdparty.bfi.constant.BfiConstant;
|
||||
import org.springblade.thirdparty.bfi.pojo.dto.BfiCustomerQueryDTO;
|
||||
import org.springblade.thirdparty.bfi.pojo.vo.BfiBillSaveResultErrorVO;
|
||||
@@ -70,7 +69,6 @@ import org.springblade.transport.mapper.CustomerInvoiceContactMapper;
|
||||
import org.springblade.transport.mapper.CustomerInvoiceInfoMapper;
|
||||
import org.springblade.transport.mapper.CustomerReceiptAccountMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.mapper.UserCustomerScopeMapper;
|
||||
import org.springblade.transport.pojo.entity.CreditRatingStandard;
|
||||
import org.springblade.transport.pojo.entity.CreditScoreCategory;
|
||||
import org.springblade.transport.pojo.entity.CreditScoreItem;
|
||||
@@ -220,7 +218,6 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
private final CreditScoreItemMapper itemMapper;
|
||||
private final CreditScoreItemOptionMapper optionMapper;
|
||||
private final CreditRatingStandardMapper standardMapper;
|
||||
private final UserCustomerScopeMapper userCustomerScopeMapper;
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
private final IBfiService bfiService;
|
||||
private final CustomerBfiConverter customerBfiConverter;
|
||||
@@ -285,9 +282,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
boolean result = saveOrUpdate(entity);
|
||||
customer.setId(entity.getId());
|
||||
replaceDetail(entity.getId(), customer);
|
||||
if (created && result) {
|
||||
grantNewCustomerToIncludedUsers(entity);
|
||||
}
|
||||
// 客商权限已从用户侧移除,新增客商不再写入用户客商授权表。
|
||||
// 仅「提交」写入变更记录;「保存」不落变更记录。
|
||||
// 保存后重新读取落库结果再对比,避免前端冗余字段造成误判;无实质变更则不记。
|
||||
if (result && !created && Boolean.TRUE.equals(customer.getRecordChange())) {
|
||||
@@ -1769,26 +1764,6 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
}
|
||||
}
|
||||
|
||||
private void grantNewCustomerToIncludedUsers(CustomerArchive customer) {
|
||||
String tenantId = Func.isNotEmpty(customer.getTenantId()) ? customer.getTenantId() : AuthUtil.getTenantId();
|
||||
List<Long> 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.<UserCustomerScope>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.<CustomerArchive>lambdaQuery()
|
||||
@@ -1843,19 +1818,14 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
}
|
||||
|
||||
private CustomerArchive ensureCustomerAccessible(Long id) {
|
||||
CustomerArchive customer = getExistingCustomer(id);
|
||||
Long userId = currentScopeUserId();
|
||||
if (userId != null && baseMapper.selectCustomerPermissionCount(id, userId) == 0) {
|
||||
throw new ServiceException("无权访问该客商档案");
|
||||
}
|
||||
return customer;
|
||||
return getExistingCustomer(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客商可见范围用户。超管返回空,表示不按用户客商授权表过滤。
|
||||
* 客商可见范围用户。用户侧客商权限已停用,始终返回空表示不过滤。
|
||||
*/
|
||||
private Long currentScopeUserId() {
|
||||
return TransportBusinessSupport.isAdministrator() ? null : AuthUtil.getUserId();
|
||||
return null;
|
||||
}
|
||||
|
||||
private CustomerArchive copyCustomer(CustomerArchive customer) {
|
||||
|
||||
+3
-3
@@ -418,7 +418,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
List<Long> deleteIdList = new ArrayList<>();
|
||||
List<Long> 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<LoadingManageMappe
|
||||
}
|
||||
String ownLoadingNo = loadingManage.getLoadingNo();
|
||||
for (Waybill waybill : waybills) {
|
||||
TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理");
|
||||
TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理", TransportBusinessSupport.MENU_WAYBILL_MANAGE);
|
||||
if (ownChildrenAdmitted && LoadableAdmissionSupport.isOwnChild(waybill, ownLoadingNo)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1346,7 +1346,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
|
||||
private LoadingManage loadEditable(Long id, boolean checkDept) {
|
||||
LoadingManage loadingManage = loadExists(id);
|
||||
if (checkDept) {
|
||||
TransportBusinessSupport.assertCurrentDept(loadingManage.getDeptId(), "配载管理");
|
||||
TransportBusinessSupport.assertCurrentDept(loadingManage.getDeptId(), "配载管理", TransportBusinessSupport.MENU_LOADING_MANAGE);
|
||||
}
|
||||
if (Objects.equals(loadingManage.getBusinessStatus(), STATUS_COMPLETED) || Objects.equals(loadingManage.getBusinessStatus(), STATUS_CANCELLED)) {
|
||||
throw new ServiceException("当前状态不允许修改");
|
||||
|
||||
+12
-4
@@ -27,6 +27,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
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;
|
||||
@@ -109,7 +110,7 @@ public class ProcessConfigServiceImpl extends BaseServiceImpl<ProcessConfigMappe
|
||||
BusinessRemoveResultVO result = new BusinessRemoveResultVO();
|
||||
List<Long> 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<ProcessConfigMappe
|
||||
|
||||
private LambdaQueryWrapper<ProcessConfig> buildQuery(ProcessConfigVO processConfig) {
|
||||
LambdaQueryWrapper<ProcessConfig> queryWrapper = Wrappers.<ProcessConfig>lambdaQuery().eq(ProcessConfig::getIsDeleted, 0);
|
||||
List<Long> deptIdList = TransportBusinessSupport.resolveDeptFilterIds(processConfig.getAllDept(), processConfig.getDeptId(), "过程配置");
|
||||
List<Long> 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<ProcessConfigMappe
|
||||
throw new ServiceException("过程配置不存在");
|
||||
}
|
||||
if (checkDept) {
|
||||
TransportBusinessSupport.assertCurrentDept(processConfig.getDeptId(), "过程配置");
|
||||
TransportBusinessSupport.assertCurrentDept(processConfig.getDeptId(), "过程配置", TransportBusinessSupport.MENU_PROCESS_CONFIG);
|
||||
}
|
||||
return processConfig;
|
||||
}
|
||||
|
||||
+2
-2
@@ -106,7 +106,7 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl<ShippingTemplat
|
||||
BusinessRemoveResultVO result = new BusinessRemoveResultVO();
|
||||
List<Long> 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<ShippingTemplat
|
||||
throw new ServiceException("发货模板不存在");
|
||||
}
|
||||
if (checkDept) {
|
||||
TransportBusinessSupport.assertCurrentDept(shippingTemplate.getDeptId(), "发货模板");
|
||||
TransportBusinessSupport.assertCurrentDept(shippingTemplate.getDeptId(), "发货模板", TransportBusinessSupport.MENU_SHIPPING_TEMPLATE);
|
||||
}
|
||||
return shippingTemplate;
|
||||
}
|
||||
|
||||
+1
-1
@@ -278,7 +278,7 @@ public class TemporaryCreditLimitServiceImpl extends BaseServiceImpl<TemporaryCr
|
||||
if (!List.of(STATUS_DRAFT, STATUS_REJECTED, STATUS_WITHDRAWN).contains(temporaryCreditLimit.getApprovalStatus())) {
|
||||
throw new ServiceException("当前状态不允许编辑");
|
||||
}
|
||||
TransportBusinessSupport.assertCurrentDept(temporaryCreditLimit.getApplyDeptId(), "临时额度管理");
|
||||
TransportBusinessSupport.assertCurrentDept(temporaryCreditLimit.getApplyDeptId(), "临时额度管理", TransportBusinessSupport.MENU_TEMPORARY_CREDIT_LIMIT);
|
||||
return temporaryCreditLimit;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -173,7 +173,7 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
BusinessRemoveResultVO result = new BusinessRemoveResultVO();
|
||||
List<Long> 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<TransportPlanMappe
|
||||
throw new ServiceException("运输计划不存在");
|
||||
}
|
||||
if (checkDept) {
|
||||
TransportBusinessSupport.assertCurrentDept(transportPlan.getDeptId(), "运输计划");
|
||||
TransportBusinessSupport.assertCurrentDept(transportPlan.getDeptId(), "运输计划", TransportBusinessSupport.MENU_TRANSPORT_PLAN);
|
||||
}
|
||||
return transportPlan;
|
||||
}
|
||||
|
||||
+20
-6
@@ -1302,7 +1302,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
BusinessRemoveResultVO result = new BusinessRemoveResultVO();
|
||||
List<Long> 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<WaybillMapper, Waybill>
|
||||
if (Func.isNotEmpty(idList)) {
|
||||
queryWrapper = Wrappers.<Waybill>lambdaQuery().eq(Waybill::getIsDeleted, 0);
|
||||
List<Long> 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<WaybillMapper, Waybill>
|
||||
LambdaQueryWrapper<Waybill> queryWrapper = Wrappers.<Waybill>lambdaQuery().eq(Waybill::getIsDeleted, 0);
|
||||
// 与常用线路等模块一致:超管默认不按组织过滤;普通用户 allDept!=1 时强制当前组织
|
||||
List<Long> 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<WaybillMapper, Waybill>
|
||||
throw new ServiceException("运单管理不存在");
|
||||
}
|
||||
if (checkDept) {
|
||||
TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理");
|
||||
TransportBusinessSupport.assertCurrentDept(waybill.getDeptId(), "运单管理", TransportBusinessSupport.MENU_WAYBILL_MANAGE);
|
||||
}
|
||||
return waybill;
|
||||
}
|
||||
|
||||
+156
-21
@@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否被授予跨组织数据权限:超级管理员,或用户档案「数据权限范围」为全部组织。
|
||||
* <p>
|
||||
* 「数据权限范围」在用户管理里配置,组织树已按它过滤(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 {
|
||||
|
||||
/**
|
||||
* 校验目标业务数据属于当前用户可操作的组织。
|
||||
* <p>
|
||||
* 超管与「数据权限范围=全部组织」的用户不限组织;其余用户仍严格要求与当前会话组织相等。
|
||||
*/
|
||||
public static void assertCurrentDept(Long deptId, String moduleName, String menuCode) {
|
||||
RoleDataScopeVO scope = resolveRoleDataScope(menuCode);
|
||||
if (scope.isAllDept() || scope.isOwnCreate()) {
|
||||
return;
|
||||
}
|
||||
List<Long> 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。
|
||||
* <p>
|
||||
* 普通用户 allDept!=1 时强制当前组织;超管与「全部组织」用户默认不按组织过滤,
|
||||
* 仅在 allDept=1 且传入 deptId 时精确匹配该组织。
|
||||
*
|
||||
* @param allDept 是否查看全部组织
|
||||
* @param deptId 筛选组织 ID
|
||||
* @param moduleName 模块名称
|
||||
* @param menuCode 菜单编码
|
||||
* @return 需要过滤的组织 ID 集合;空集合表示不过滤组织
|
||||
*/
|
||||
public static List<Long> 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<Long> 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<Long> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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='角色菜单数据权限自定义组织';
|
||||
Reference in New Issue
Block a user