✨ 解耦MK表单实例与业务ID,公开页按formInstanceId反查业务数据
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+13
@@ -26,6 +26,7 @@
|
||||
package org.springblade.system.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.system.pojo.entity.Dept;
|
||||
import org.springblade.system.pojo.vo.DeptVO;
|
||||
|
||||
@@ -66,6 +67,18 @@ public interface DeptMapper extends BaseMapper<Dept> {
|
||||
*/
|
||||
List<DeptVO> lazyTree(String tenantId, Long parentId);
|
||||
|
||||
/**
|
||||
* 按组织 ID 集合查询树节点(用于数据权限过滤后的根节点回显)
|
||||
*
|
||||
* @param tenantId 租户ID,空表示不限
|
||||
* @param ids 需要返回的组织 ID
|
||||
* @param childIds 计算 hasChildren 时允许计入的子组织 ID;为空则 hasChildren 恒为 false
|
||||
* @return 树节点列表
|
||||
*/
|
||||
List<DeptVO> treeNodesByIds(@Param("tenantId") String tenantId,
|
||||
@Param("ids") List<Long> ids,
|
||||
@Param("childIds") List<Long> childIds);
|
||||
|
||||
/**
|
||||
* 获取部门名
|
||||
*
|
||||
|
||||
@@ -115,6 +115,47 @@
|
||||
ORDER BY dept.sort
|
||||
</select>
|
||||
|
||||
<select id="treeNodesByIds" resultMap="treeNodeResultMap">
|
||||
SELECT
|
||||
dept.id,
|
||||
dept.parent_id,
|
||||
dept.dept_name AS title,
|
||||
dept.id AS "value",
|
||||
dept.id AS "key",
|
||||
(
|
||||
SELECT
|
||||
CASE WHEN count(1) > 0 THEN 1 ELSE 0 END
|
||||
FROM
|
||||
blade_dept child
|
||||
WHERE
|
||||
child.parent_id = dept.id
|
||||
AND child.is_deleted = 0
|
||||
<choose>
|
||||
<when test="childIds != null and childIds.size() > 0">
|
||||
AND child.id IN
|
||||
<foreach collection="childIds" item="childId" open="(" close=")" separator=",">
|
||||
#{childId}
|
||||
</foreach>
|
||||
</when>
|
||||
<otherwise>
|
||||
AND 1 = 0
|
||||
</otherwise>
|
||||
</choose>
|
||||
) AS "has_children"
|
||||
FROM
|
||||
blade_dept dept
|
||||
WHERE
|
||||
dept.is_deleted = 0
|
||||
AND dept.id IN
|
||||
<foreach collection="ids" item="id" open="(" close=")" separator=",">
|
||||
#{id}
|
||||
</foreach>
|
||||
<if test="tenantId != null and tenantId != ''">
|
||||
AND dept.tenant_id = #{tenantId}
|
||||
</if>
|
||||
ORDER BY dept.sort, dept.id
|
||||
</select>
|
||||
|
||||
<select id="getDeptNames" resultType="java.lang.String">
|
||||
SELECT
|
||||
dept_name
|
||||
|
||||
+5
-3
@@ -68,10 +68,10 @@ public interface IDeptService extends IService<Dept> {
|
||||
List<DeptVO> lazyList(String tenantId, Long parentId, Map<String, Object> param);
|
||||
|
||||
/**
|
||||
* 树形结构(含超管判定 + 当前租户隔离)
|
||||
* 树形结构(含超管判定 + 当前租户隔离 + 用户组织数据权限)
|
||||
* <p>
|
||||
* 仅超级管理员可指定任意 tenantId 查询;其他用户传入的 tenantId 一律被忽略,
|
||||
* 强制使用当前会话租户。
|
||||
* 强制使用当前会话租户,并按用户「数据权限范围」「数据层级范围」过滤可见组织。
|
||||
*
|
||||
* @param tenantId 入参 tenantId(仅超管生效)
|
||||
* @return 部门树
|
||||
@@ -79,9 +79,11 @@ public interface IDeptService extends IService<Dept> {
|
||||
List<DeptVO> tree(String tenantId);
|
||||
|
||||
/**
|
||||
* 懒加载树形结构(含超管判定 + 当前租户隔离)
|
||||
* 懒加载树形结构(含超管判定 + 当前租户隔离 + 用户组织数据权限)
|
||||
* <p>
|
||||
* 仅超级管理员可指定任意 tenantId,空值表示查询全部;其他用户强制使用当前会话租户。
|
||||
* 非超管按用户「数据权限范围」「数据层级范围」过滤可见组织:
|
||||
* 仅所属组织 / 自定义组织,并可选择是否包含无限下级。
|
||||
*
|
||||
* @param tenantId 入参 tenantId(仅超管生效)
|
||||
* @param parentId 父级部门ID
|
||||
|
||||
+154
-2
@@ -44,8 +44,10 @@ 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.UserVO;
|
||||
@@ -90,8 +92,19 @@ 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 IamSyncProperties iamSyncProperties;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@@ -144,7 +157,11 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
String resolvedTenantId = AuthUtil.isAdministrator()
|
||||
? Func.toStrWithEmpty(tenantId, AuthUtil.getTenantId())
|
||||
: AuthUtil.getTenantId();
|
||||
return ForestNodeMerger.merge(baseMapper.tree(resolvedTenantId));
|
||||
DeptDataScopeFilter scopeFilter = resolveDeptDataScopeFilter();
|
||||
if (scopeFilter == null || scopeFilter.allDept()) {
|
||||
return ForestNodeMerger.merge(baseMapper.tree(resolvedTenantId));
|
||||
}
|
||||
return buildScopedDeptTree(resolvedTenantId, scopeFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +170,142 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
String resolvedTenantId = AuthUtil.isAdministrator()
|
||||
? Func.toStr(tenantId, StringPool.EMPTY)
|
||||
: AuthUtil.getTenantId();
|
||||
return ForestNodeMerger.merge(baseMapper.lazyTree(resolvedTenantId, parentId));
|
||||
Long resolvedParentId = parentId == null ? BladeConstant.TOP_PARENT_ID : parentId;
|
||||
DeptDataScopeFilter scopeFilter = resolveDeptDataScopeFilter();
|
||||
if (scopeFilter == null || scopeFilter.allDept()) {
|
||||
return ForestNodeMerger.merge(baseMapper.lazyTree(resolvedTenantId, resolvedParentId));
|
||||
}
|
||||
Set<Long> allowedIds = scopeFilter.allowedDeptIds();
|
||||
if (Func.isEmpty(allowedIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
boolean includeChildren = scopeFilter.includeChildren();
|
||||
List<Long> childIdsForHasChildren = includeChildren ? new ArrayList<>(allowedIds) : Collections.emptyList();
|
||||
// 根层:返回「可见根」——权限集合中父节点不在集合内的组织,避免用户看不到深层权限节点。
|
||||
if (Objects.equals(resolvedParentId, BladeConstant.TOP_PARENT_ID)) {
|
||||
List<Long> visibleRootIds = resolveVisibleRootIds(allowedIds);
|
||||
if (Func.isEmpty(visibleRootIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return ForestNodeMerger.merge(baseMapper.treeNodesByIds(resolvedTenantId, visibleRootIds, childIdsForHasChildren));
|
||||
}
|
||||
// 非根层:无权限的父节点不允许展开;仅本级时不允许继续下钻。
|
||||
if (!includeChildren || !allowedIds.contains(resolvedParentId)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<DeptVO> children = baseMapper.lazyTree(resolvedTenantId, resolvedParentId);
|
||||
List<Long> filteredChildIds = children.stream()
|
||||
.map(DeptVO::getId)
|
||||
.filter(allowedIds::contains)
|
||||
.toList();
|
||||
if (Func.isEmpty(filteredChildIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return ForestNodeMerger.merge(baseMapper.treeNodesByIds(resolvedTenantId, filteredChildIds, childIdsForHasChildren));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按数据权限构建完整组织树(用于 /dept/tree)。
|
||||
*/
|
||||
private List<DeptVO> buildScopedDeptTree(String tenantId, DeptDataScopeFilter scopeFilter) {
|
||||
Set<Long> allowedIds = scopeFilter.allowedDeptIds();
|
||||
if (Func.isEmpty(allowedIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Long> childIdsForHasChildren = scopeFilter.includeChildren()
|
||||
? new ArrayList<>(allowedIds)
|
||||
: Collections.emptyList();
|
||||
return ForestNodeMerger.merge(baseMapper.treeNodesByIds(tenantId, new ArrayList<>(allowedIds), childIdsForHasChildren));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算懒加载首层可见根:父级不在权限集合内的组织。
|
||||
*/
|
||||
private List<Long> resolveVisibleRootIds(Set<Long> allowedIds) {
|
||||
return listByIds(allowedIds).stream()
|
||||
.filter(dept -> dept.getParentId() == null
|
||||
|| Objects.equals(dept.getParentId(), BladeConstant.TOP_PARENT_ID)
|
||||
|| !allowedIds.contains(dept.getParentId()))
|
||||
.sorted(Comparator.comparing(Dept::getSort, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(Dept::getId, Comparator.nullsLast(Long::compareTo)))
|
||||
.map(Dept::getId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前登录用户的组织树数据权限。
|
||||
* <p>
|
||||
* 超管或「全部组织」返回 allDept=true;其余按所属组织/自定义组织,并结合「包含下级/仅本级」展开。
|
||||
*
|
||||
* @return 过滤上下文;无法识别用户时按空权限处理
|
||||
*/
|
||||
private DeptDataScopeFilter resolveDeptDataScopeFilter() {
|
||||
if (AuthUtil.isAdministrator()) {
|
||||
return DeptDataScopeFilter.all();
|
||||
}
|
||||
Long userId = AuthUtil.getUserId();
|
||||
if (Func.isEmpty(userId) || userId <= 0) {
|
||||
return DeptDataScopeFilter.empty();
|
||||
}
|
||||
User user = userService.getById(userId);
|
||||
if (Func.isEmpty(user)) {
|
||||
return DeptDataScopeFilter.empty();
|
||||
}
|
||||
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)) {
|
||||
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) {
|
||||
List<Long> childIds = SysCache.getDeptChildIds(deptId);
|
||||
if (Func.isNotEmpty(childIds)) {
|
||||
allowedDeptIds.addAll(childIds);
|
||||
} else {
|
||||
allowedDeptIds.add(deptId);
|
||||
}
|
||||
} else {
|
||||
allowedDeptIds.add(deptId);
|
||||
}
|
||||
}
|
||||
return new DeptDataScopeFilter(false, includeChildren, allowedDeptIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组织懒加载树的数据权限过滤上下文。
|
||||
*/
|
||||
private record DeptDataScopeFilter(boolean allDept, boolean includeChildren, Set<Long> allowedDeptIds) {
|
||||
private static DeptDataScopeFilter all() {
|
||||
return new DeptDataScopeFilter(true, true, Collections.emptySet());
|
||||
}
|
||||
|
||||
private static DeptDataScopeFilter empty() {
|
||||
return new DeptDataScopeFilter(false, false, Collections.emptySet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user