✨ 用户编辑支持数据管理权限多选组织并与角色权限并集
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+32
@@ -35,6 +35,9 @@ import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.system.pojo.entity.User;
|
||||
import org.springblade.system.feign.IUserClient;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE;
|
||||
import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
|
||||
|
||||
@@ -47,6 +50,7 @@ import static org.springblade.core.launch.constant.FlowConstant.TASK_USR_PREFIX;
|
||||
public class UserCache {
|
||||
private static final String USER_CACHE_ID = "user:id:";
|
||||
private static final String USER_CACHE_ACCOUNT = "user:account:";
|
||||
private static final String USER_CACHE_MANAGE_DEPT = "user:manage-dept:";
|
||||
|
||||
private static IUserClient userClient;
|
||||
|
||||
@@ -89,6 +93,26 @@ public class UserCache {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户「数据管理权限」组织(含下级展开)
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @return 可管理组织 ID 集合
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Long> getManageDeptIds(Long userId) {
|
||||
if (userId == null || userId <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
try {
|
||||
List<Long> deptIdList = CacheUtil.get(USER_CACHE, USER_CACHE_MANAGE_DEPT, userId, () -> loadManageDeptIds(userId));
|
||||
return deptIdList == null ? Collections.emptyList() : deptIdList;
|
||||
} catch (RuntimeException exception) {
|
||||
log.warn("用户数据管理组织缓存读取失败,改为直连查询, userId={}", userId, exception);
|
||||
return loadManageDeptIds(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户姓名
|
||||
*
|
||||
@@ -153,6 +177,14 @@ public class UserCache {
|
||||
return result.getData();
|
||||
}
|
||||
|
||||
private static List<Long> loadManageDeptIds(Long userId) {
|
||||
R<List<Long>> result = getUserClient().manageDeptIds(userId);
|
||||
if (result == null || !result.isSuccess() || result.getData() == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return result.getData();
|
||||
}
|
||||
|
||||
private static void clearBrokenUserCache() {
|
||||
CacheUtil.clear(USER_CACHE);
|
||||
CacheUtil.clear(USER_CACHE, Boolean.FALSE);
|
||||
|
||||
+12
@@ -37,6 +37,8 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User Feign接口类
|
||||
*
|
||||
@@ -62,6 +64,7 @@ public interface IUserClient {
|
||||
String REGISTER_USER = API_PREFIX + "/register-user";
|
||||
String REMOVE_USER = API_PREFIX + "/remove-user";
|
||||
String BIND_WX_MINI_OPENID = API_PREFIX + "/bind-wx-mini-openid";
|
||||
String MANAGE_DEPT_IDS = API_PREFIX + "/manage-dept-ids";
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
@@ -72,6 +75,15 @@ public interface IUserClient {
|
||||
@GetMapping(USER_BY_ID)
|
||||
R<User> userInfoById(@RequestParam("userId") Long userId);
|
||||
|
||||
/**
|
||||
* 解析用户「数据管理权限」组织(含下级)
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @return 可管理组织 ID 集合
|
||||
*/
|
||||
@GetMapping(MANAGE_DEPT_IDS)
|
||||
R<List<Long>> manageDeptIds(@RequestParam("userId") Long userId);
|
||||
|
||||
|
||||
/**
|
||||
* 根据账号获取用户信息
|
||||
|
||||
@@ -38,9 +38,12 @@ import org.springblade.system.service.IUserService;
|
||||
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.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户服务Feign实现类
|
||||
*
|
||||
@@ -60,6 +63,12 @@ public class UserClient implements IUserClient {
|
||||
return R.data(service.getById(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(MANAGE_DEPT_IDS)
|
||||
public R<List<Long>> manageDeptIds(@RequestParam("userId") Long userId) {
|
||||
return R.data(service.resolveManageDeptIds(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@GetMapping(USER_BY_ACCOUNT)
|
||||
public R<User> userByAccount(String tenantId, String account) {
|
||||
|
||||
+8
@@ -257,6 +257,14 @@ public interface IUserService extends BaseService<User> {
|
||||
*/
|
||||
boolean removeUser(String userIds);
|
||||
|
||||
/**
|
||||
* 解析用户「数据管理权限」组织(含下级展开)
|
||||
*
|
||||
* @param userId 用户主键
|
||||
* @return 可管理组织 ID 集合;无配置时返回空列表
|
||||
*/
|
||||
List<Long> resolveManageDeptIds(Long userId);
|
||||
|
||||
/**
|
||||
* 导入用户数据(仅超管 + Excel 显式指定时允许跨租户,否则归属当前会话租户)
|
||||
*
|
||||
|
||||
+69
-4
@@ -86,6 +86,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -663,6 +664,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
boolean tempUser = this.deleteLogic(Func.toLongList(userIds));
|
||||
boolean tempUserDept = userDeptService.remove(Wrappers.<UserDept>lambdaQuery().in(UserDept::getUserId, Func.toLongList(userIds)));
|
||||
if (tempUser && tempUserDept) {
|
||||
userDataScopeMapper.delete(Wrappers.<UserDataScope>lambdaQuery().in(UserDataScope::getUserId, idList));
|
||||
UserOauth userOauth = new UserOauth();
|
||||
userOauth.delete(Wrappers.<UserOauth>lambdaQuery().in(UserOauth::getUserId, Func.toLongList(userIds)));
|
||||
UserWeb userWeb = new UserWeb();
|
||||
@@ -672,6 +674,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
UserOther userOther = new UserOther();
|
||||
userOther.delete(Wrappers.<UserOther>lambdaQuery().in(UserOther::getUserId, Func.toLongList(userIds)));
|
||||
CacheUtil.clear(USER_CACHE);
|
||||
CacheUtil.clear(USER_CACHE, Boolean.FALSE);
|
||||
return true;
|
||||
} else {
|
||||
throw new ServiceException("删除用户失败!");
|
||||
@@ -1119,8 +1122,10 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
if (user.getPersonCategory() == null) {
|
||||
user.setPersonCategory(1);
|
||||
}
|
||||
// 组织/客商数据权限已迁移至角色菜单配置,用户侧字段仅保留兼容默认值,不再参与过滤。
|
||||
if (user.getDataScopeRange() == null) {
|
||||
// 有「数据管理权限」组织时标记为自定义;否则仅补兼容默认值(业务过滤以角色权限 ∪ 用户管理组织为准)
|
||||
if (Func.isNotEmpty(user.getDataScopeDeptIds())) {
|
||||
user.setDataScopeRange(3);
|
||||
} else if (user.getDataScopeRange() == null) {
|
||||
user.setDataScopeRange(2);
|
||||
}
|
||||
if (user.getDataLevelRange() == null) {
|
||||
@@ -1141,8 +1146,33 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户「数据管理权限」组织;dataScopeDeptIds 为 null 时保留原配置(兼容 IAM/同步等未传该字段的调用)。
|
||||
*/
|
||||
private boolean submitUserScopes(User user) {
|
||||
// 用户级组织/客商数据权限已停用,改由角色菜单数据权限配置。
|
||||
if (user == null || user.getId() == null) {
|
||||
return true;
|
||||
}
|
||||
if (user.getDataScopeDeptIds() == null) {
|
||||
return true;
|
||||
}
|
||||
userDataScopeMapper.delete(Wrappers.<UserDataScope>lambdaQuery().eq(UserDataScope::getUserId, user.getId()));
|
||||
List<Long> deptIdList = user.getDataScopeDeptIds().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(deptId -> deptId > 0)
|
||||
.distinct()
|
||||
.toList();
|
||||
for (Long deptId : deptIdList) {
|
||||
UserDataScope scope = new UserDataScope();
|
||||
scope.setUserId(user.getId());
|
||||
scope.setDeptId(deptId);
|
||||
userDataScopeMapper.insert(scope);
|
||||
}
|
||||
this.update(Wrappers.<User>lambdaUpdate()
|
||||
.set(User::getDataScopeRange, deptIdList.isEmpty() ? 2 : 3)
|
||||
.eq(User::getId, user.getId()));
|
||||
CacheUtil.clear(USER_CACHE);
|
||||
CacheUtil.clear(USER_CACHE, Boolean.FALSE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1150,11 +1180,46 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
if (user == null || user.getId() == null) {
|
||||
return user;
|
||||
}
|
||||
user.setDataScopeDeptIds(Collections.emptyList());
|
||||
List<Long> deptIdList = userDataScopeMapper.selectList(Wrappers.<UserDataScope>lambdaQuery()
|
||||
.eq(UserDataScope::getUserId, user.getId()))
|
||||
.stream()
|
||||
.map(UserDataScope::getDeptId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
user.setDataScopeDeptIds(deptIdList);
|
||||
user.setCustomerIds(Collections.emptyList());
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> resolveManageDeptIds(Long userId) {
|
||||
if (Func.isEmpty(userId) || userId <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Long> baseDeptIds = userDataScopeMapper.selectList(Wrappers.<UserDataScope>lambdaQuery()
|
||||
.eq(UserDataScope::getUserId, userId))
|
||||
.stream()
|
||||
.map(UserDataScope::getDeptId)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(deptId -> deptId > 0)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (Func.isEmpty(baseDeptIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
LinkedHashSet<Long> allowedDeptIds = new LinkedHashSet<>();
|
||||
for (Long deptId : baseDeptIds) {
|
||||
List<Long> childIds = SysCache.getDeptChildIds(deptId);
|
||||
if (Func.isNotEmpty(childIds)) {
|
||||
allowedDeptIds.addAll(childIds);
|
||||
} else {
|
||||
allowedDeptIds.add(deptId);
|
||||
}
|
||||
}
|
||||
return List.copyOf(allowedDeptIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一的租户范围过滤:超管放行,其他用户强制限定为当前会话租户。
|
||||
*/
|
||||
|
||||
+63
-5
@@ -32,6 +32,7 @@ import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.SpringUtil;
|
||||
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.vo.RoleDataScopeVO;
|
||||
import org.springblade.transport.mapper.ProjectApplyMapper;
|
||||
@@ -41,6 +42,7 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@@ -132,14 +134,14 @@ public final class TransportBusinessSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析指定菜单下的角色数据权限(超管视为全部组织)。
|
||||
* 解析指定菜单下的角色数据权限(超管视为全部组织),并与用户「数据管理权限」组织取并集。
|
||||
*/
|
||||
public static RoleDataScopeVO resolveRoleDataScope(String menuCode) {
|
||||
if (isAdministrator()) {
|
||||
return RoleDataScopeVO.all();
|
||||
}
|
||||
RoleDataScopeVO scope = RoleDataScopeCache.resolveByMenuCode(menuCode);
|
||||
return scope == null ? RoleDataScopeVO.all() : scope;
|
||||
return mergeUserManageDepts(scope == null ? RoleDataScopeVO.all() : scope);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,10 +161,43 @@ public final class TransportBusinessSupport {
|
||||
if (isAdministrator()) {
|
||||
return true;
|
||||
}
|
||||
RoleDataScopeVO scope = RoleDataScopeCache.resolveWidest();
|
||||
RoleDataScopeVO scope = resolveWidestDataScope();
|
||||
return scope == null || scope.isAllDept();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析最宽角色数据权限,并与用户「数据管理权限」组织取并集。
|
||||
*/
|
||||
public static RoleDataScopeVO resolveWidestDataScope() {
|
||||
if (isAdministrator()) {
|
||||
return RoleDataScopeVO.all();
|
||||
}
|
||||
RoleDataScopeVO scope = RoleDataScopeCache.resolveWidest();
|
||||
return mergeUserManageDepts(scope == null ? RoleDataScopeVO.all() : scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色组织权限与用户数据管理组织取并集。
|
||||
* <p>
|
||||
* 全部组织 / 本人创建 不因用户管理组织改变;其余类型将用户管理组织(含下级)并入 deptIds。
|
||||
*/
|
||||
private static RoleDataScopeVO mergeUserManageDepts(RoleDataScopeVO scope) {
|
||||
if (scope == null || scope.isAllDept() || scope.isOwnCreate()) {
|
||||
return scope;
|
||||
}
|
||||
Long userId = AuthUtil.getUserId();
|
||||
List<Long> manageDeptIds = UserCache.getManageDeptIds(userId);
|
||||
if (Func.isEmpty(manageDeptIds)) {
|
||||
return scope;
|
||||
}
|
||||
LinkedHashSet<Long> mergedDeptIds = new LinkedHashSet<>();
|
||||
if (Func.isNotEmpty(scope.getDeptIds())) {
|
||||
mergedDeptIds.addAll(scope.getDeptIds());
|
||||
}
|
||||
mergedDeptIds.addAll(manageDeptIds);
|
||||
return RoleDataScopeVO.ofDepts(scope.getScopeType(), List.copyOf(mergedDeptIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否仅本人创建数据权限
|
||||
*/
|
||||
@@ -187,6 +222,29 @@ public final class TransportBusinessSupport {
|
||||
return dept;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表行是否只读:与 {@link #assertCurrentDept(Long, String, String)} 判定保持一致。
|
||||
* <p>
|
||||
* 超管 / 全部组织 / 本人创建数据权限下可编辑;指定组织权限下,仅所属组织在授权范围内时可编辑;
|
||||
* 否则仅当前登录用户所属组织的数据可编辑。
|
||||
*
|
||||
* @param deptId 业务数据所属组织
|
||||
* @param menuCode 菜单编码
|
||||
* @return true-前端仅展示查看
|
||||
*/
|
||||
public static boolean isDeptReadonly(Long deptId, String menuCode) {
|
||||
RoleDataScopeVO scope = resolveRoleDataScope(menuCode);
|
||||
if (scope.isAllDept() || scope.isOwnCreate()) {
|
||||
return false;
|
||||
}
|
||||
List<Long> allowedDeptIds = scope.getDeptIds();
|
||||
if (Func.isNotEmpty(allowedDeptIds)) {
|
||||
return deptId == null || deptId <= 0 || !allowedDeptIds.contains(deptId);
|
||||
}
|
||||
Long currentDeptId = Func.firstLong(AuthUtil.getDeptId());
|
||||
return currentDeptId != null && !Objects.equals(deptId, currentDeptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验目标业务数据属于当前用户可操作的组织。
|
||||
*/
|
||||
@@ -214,7 +272,7 @@ public final class TransportBusinessSupport {
|
||||
if (hasAllDeptScope()) {
|
||||
return;
|
||||
}
|
||||
RoleDataScopeVO scope = RoleDataScopeCache.resolveWidest();
|
||||
RoleDataScopeVO scope = resolveWidestDataScope();
|
||||
if (scope != null && scope.isOwnCreate()) {
|
||||
return;
|
||||
}
|
||||
@@ -396,7 +454,7 @@ public final class TransportBusinessSupport {
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
RoleDataScopeVO scope = RoleDataScopeCache.resolveWidest();
|
||||
RoleDataScopeVO scope = resolveWidestDataScope();
|
||||
if (scope != null && scope.isOwnCreate()) {
|
||||
if (Objects.equals(allDept, 1) && Func.isNotEmpty(deptId) && deptId > 0) {
|
||||
return Collections.singletonList(deptId);
|
||||
|
||||
Reference in New Issue
Block a user