fix(2227-permissionQuery): 处理分支错误

This commit is contained in:
zhansihu 2024-04-08 19:36:14 +08:00
parent 308623e6d4
commit 0aeae35a1a
14 changed files with 355 additions and 22 deletions

View File

@ -0,0 +1,42 @@
package cn.axzo.tyr.client.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* 功能资源授权类型
*
* @version V1.0
* @author: ZhanSiHu
* @date: 2024/4/8 17:57
*/
@Getter
@AllArgsConstructor
public enum FeatureResourceAuthType {
ALL_ROLE(0, "全部角色"),
GRANT_ROLE(1, "指定角色"),
;
private static final Map<Integer, FeatureResourceAuthType> MAPPING = new HashMap<>();
static {
Arrays.stream(FeatureResourceAuthType.values()).forEach(t -> MAPPING.put(t.code, t));
}
private final Integer code;
private final String desc;
public FeatureResourceAuthType getByCode(Integer code) {
return MAPPING.get(code);
}
public boolean apply(Integer code) {
return this.code.equals(code);
}
}

View File

@ -0,0 +1,42 @@
package cn.axzo.tyr.client.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* 功能资源状态
*
* @version V1.0
* @author: ZhanSiHu
* @date: 2024/4/8 17:37
*/
@Getter
@AllArgsConstructor
public enum FeatureResourceStatus {
HIDE(0, "隐藏"),
NORMAL(1, "展示"),
;
private static final Map<Integer, FeatureResourceStatus> MAPPING = new HashMap<>();
static {
Arrays.stream(FeatureResourceStatus.values()).forEach(t -> MAPPING.put(t.code, t));
}
private final Integer code;
private final String desc;
public FeatureResourceStatus getByCode(Integer code) {
return MAPPING.get(code);
}
public boolean apply(Integer code) {
return this.code.equals(code);
}
}

View File

@ -6,6 +6,7 @@ import lombok.Getter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -51,4 +52,8 @@ public enum RoleTypeEnum {
.orElse(false); .orElse(false);
} }
public boolean apply(String value) {
return Objects.equals(this.value, value);
}
} }

View File

@ -0,0 +1,14 @@
package cn.axzo.tyr.server.common.constants;
/**
* 缓存常量
*
* @version V1.0
* @author: ZhanSiHu
* @date: 2024/4/8 17:50
*/
public class CacheConstant {
/** 免授权缓存KEY **/
public static final String KEY_AUTH_FREE = "tyr:auth-free";
}

View File

@ -5,7 +5,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List; import java.util.Set;
/** /**
* 权限对象 * 权限对象
@ -21,5 +21,6 @@ import java.util.List;
public class PermissionDO { public class PermissionDO {
private Long ouId; private Long ouId;
private Long workspaceId; private Long workspaceId;
private List<Long> featureIds; private Set<Long> featureIds;
private boolean superAdmin;
} }

View File

@ -30,8 +30,6 @@ public class PermissionQueryContext {
private List<WorkspaceOUPair> workspaceOUPairs; private List<WorkspaceOUPair> workspaceOUPairs;
/** 登录端 **/ /** 登录端 **/
private String terminal; private String terminal;
/** 资源类型 **/
private List<Integer> featureTypes;
/** 预览角色ID **/ /** 预览角色ID **/
private List<Long> previewRoleIds; private List<Long> previewRoleIds;

View File

@ -0,0 +1,30 @@
package cn.axzo.tyr.server.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 资源权限
* feature简化权限相关属性
* @version V1.0
* @author: ZhanSiHu
* @date: 2024/4/8 17:11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResourcePermission {
private Long id;
private Long parentId;
private String featureCode;
private Integer featureType;
private Integer authType;
}

View File

@ -0,0 +1,31 @@
package cn.axzo.tyr.server.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 资源权限查询
*
* @version V1.0
* @author: ZhanSiHu
* @date: 2024/4/8 17:10
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ResourcePermissionQueryDTO {
private List<Long> ids;
private List<Integer> featureTypes;
private List<String> terminals;
private List<Integer> authType;
}

View File

@ -5,7 +5,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List; import java.util.Set;
/** /**
* 角色和权限 * 角色和权限
@ -28,5 +28,5 @@ public class RoleWithFeature {
private Integer productUnitType; private Integer productUnitType;
private List<Long> featureIds; private Set<Long> featureIds;
} }

View File

@ -0,0 +1,26 @@
package cn.axzo.tyr.server.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 租户产品权限关系
*
* @version V1.0
* @author: ZhanSiHu
* @date: 2024/4/8 15:56
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WorkspaceFeatureRelation {
private Long workspaceId;
private Integer productUnitType;
private Long featureId;
}

View File

@ -1,5 +1,7 @@
package cn.axzo.tyr.server.service; package cn.axzo.tyr.server.service;
import cn.axzo.tyr.server.model.ResourcePermission;
import cn.axzo.tyr.server.model.ResourcePermissionQueryDTO;
import cn.axzo.tyr.server.repository.entity.SaasFeatureResource; import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq; import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq;
@ -15,6 +17,12 @@ import java.util.List;
public interface SaasFeatureResourceService { public interface SaasFeatureResourceService {
void saveOrUpdateMenu(FeatureResourceTreeSaveReq req); void saveOrUpdateMenu(FeatureResourceTreeSaveReq req);
/** 根据ID查询导航菜单页面信息 - 限制查询字段 **/ /** 根据ID查询导航菜单页面信息 仅可显示 - 限制查询字段 **/
List<SaasFeatureResource> listNavByIds(List<Long> featureIds); List<SaasFeatureResource> listNavByIds(List<Long> featureIds);
/** 资源权限通用查询 **/
List<ResourcePermission> permissionQuery(ResourcePermissionQueryDTO param);
/** 是否免授权 **/
boolean isAuthFree(Long featureId);
} }

View File

@ -1,18 +1,22 @@
package cn.axzo.tyr.server.service.impl; package cn.axzo.tyr.server.service.impl;
import cn.axzo.basics.common.BeanMapper; import cn.axzo.basics.common.BeanMapper;
import cn.axzo.basics.common.util.NumberUtil;
import cn.axzo.basics.common.util.TreeUtil; import cn.axzo.basics.common.util.TreeUtil;
import cn.axzo.tyr.client.common.enums.FeatureResourceType; import cn.axzo.framework.auth.domain.TerminalInfo;
import cn.axzo.maokai.common.enums.SaasCooperateShipCooperateTypeEnum;
import cn.axzo.tyr.client.common.enums.RoleTypeEnum;
import cn.axzo.tyr.client.model.base.WorkspaceOUPair;
import cn.axzo.tyr.client.model.enums.IdentityType; import cn.axzo.tyr.client.model.enums.IdentityType;
import cn.axzo.tyr.client.model.req.NavTreeReq;
import cn.axzo.tyr.client.model.res.NavTreeResp;
import cn.axzo.tyr.server.model.PermissionDO; import cn.axzo.tyr.server.model.PermissionDO;
import cn.axzo.tyr.server.model.PermissionQueryContext; import cn.axzo.tyr.server.model.PermissionQueryContext;
import cn.axzo.tyr.server.model.ResourcePermission;
import cn.axzo.tyr.server.model.ResourcePermissionQueryDTO;
import cn.axzo.tyr.server.model.RoleWithFeature; import cn.axzo.tyr.server.model.RoleWithFeature;
import cn.axzo.tyr.server.model.UserIdentity; import cn.axzo.tyr.server.model.UserIdentity;
import cn.axzo.tyr.client.model.base.WorkspaceOUPair; import cn.axzo.tyr.server.model.WorkspaceFeatureRelation;
import cn.axzo.tyr.client.model.req.IdentityAuthReq;
import cn.axzo.tyr.client.model.req.NavTreeReq;
import cn.axzo.tyr.client.model.req.PermissionQueryReq;
import cn.axzo.tyr.client.model.res.NavTreeResp;
import cn.axzo.tyr.server.repository.entity.SaasFeatureResource; import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import cn.axzo.tyr.server.repository.entity.SaasRoleUserRelation; import cn.axzo.tyr.server.repository.entity.SaasRoleUserRelation;
import cn.axzo.tyr.server.service.PermissionQueryService; import cn.axzo.tyr.server.service.PermissionQueryService;
@ -26,9 +30,13 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -51,14 +59,16 @@ public class PermissionQueryServiceImpl implements PermissionQueryService {
public List<NavTreeResp> getNavTree(NavTreeReq req) { public List<NavTreeResp> getNavTree(NavTreeReq req) {
//构造参数 //构造参数
PermissionQueryContext context = BeanMapper.copyBean(req, PermissionQueryContext.class); PermissionQueryContext context = BeanMapper.copyBean(req, PermissionQueryContext.class);
//限制只查菜单页面
context.setFeatureTypes(FeatureResourceType.navTypes());
//查询权限 //查询权限
List<PermissionDO> permissions = queryUserPermission(context); List<PermissionDO> permissions = queryUserPermission(context);
if (CollectionUtil.isEmpty(permissions)) { if (CollectionUtil.isEmpty(permissions)) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<Long> featureIds = permissions.stream().map(PermissionDO::getFeatureIds).flatMap(List::stream).collect(Collectors.toList()); List<Long> featureIds = permissions.stream()
.map(PermissionDO::getFeatureIds)
.flatMap(Set::stream)
.collect(Collectors.toList());
//反查资源信息 //反查资源信息
List<SaasFeatureResource> resourceList = featureResourceService.listNavByIds(featureIds); List<SaasFeatureResource> resourceList = featureResourceService.listNavByIds(featureIds);
//组装导航树 //组装导航树
@ -74,13 +84,91 @@ public class PermissionQueryServiceImpl implements PermissionQueryService {
log.warn("no user role relation found"); log.warn("no user role relation found");
return Collections.emptyList(); return Collections.emptyList();
} }
Set<Long> realWorkspaceId = userRoleRelations.stream().map(SaasRoleUserRelation::getWorkspaceId).collect(Collectors.toSet()); //查询租户产品权限点
//TODO: 查询产品分配的权限 List<WorkspaceFeatureRelation> workspaceFeatureRelations = listWorkspaceFeatureRelations(context);
Set<Long> roleIds = userRoleRelations.stream().map(SaasRoleUserRelation::getRoleId).collect(Collectors.toSet()); Set<Long> roleIds = userRoleRelations.stream().map(SaasRoleUserRelation::getRoleId).collect(Collectors.toSet());
//查询角色权限 //查询角色权限
List<RoleWithFeature> roles = roleService.listWithFeatures(roleIds, context.getFeatureIds()); List<RoleWithFeature> roles = roleService.listWithFeatures(roleIds, context.getFeatureIds());
//取交集确定权限
return buildFinalPermission(userRoleRelations, workspaceFeatureRelations, roles);
}
return Collections.emptyList(); private List<PermissionDO> buildFinalPermission(List<SaasRoleUserRelation> userRoleRelations,
List<WorkspaceFeatureRelation> workspaceFeatureRelations,
List<RoleWithFeature> roles) {
//mapping
Map<Long, RoleWithFeature> roleMap = roles.stream()
.collect(Collectors.toMap(RoleWithFeature::getRoleId, Function.identity()));
Map<Long, List<WorkspaceFeatureRelation>> workspaceMap = workspaceFeatureRelations.stream()
.collect(Collectors.groupingBy(WorkspaceFeatureRelation::getWorkspaceId));
//按拥有的角色构建权限结果
Map<String, PermissionDO> result = new HashMap<>();
for (SaasRoleUserRelation relation : userRoleRelations) {
RoleWithFeature role = roleMap.get(relation.getRoleId());
if (role == null) {
log.warn("no role found for id:{}", relation.getRoleId());
continue;
}
List<WorkspaceFeatureRelation> allFeatures = workspaceMap.get(relation.getWorkspaceId());
if (CollectionUtil.isEmpty(allFeatures)) {
log.warn("no workspace product feature found for id:{}", relation.getWorkspaceId());
continue;
}
String key = relation.getWorkspaceId() + "-" + relation.getOuId();
PermissionDO permission = result.getOrDefault(key,
PermissionDO.builder().ouId(relation.getOuId()).workspaceId(relation.getWorkspaceId()).build());
if (RoleTypeEnum.isAdmin(role.getRoleType())) {
//管理员和超管类权限
permission.getFeatureIds().addAll(buildAdminPermission(role, allFeatures));
if (RoleTypeEnum.SUPER_ADMIN.apply(role.getRoleType())) {
//超管标记
permission.setSuperAdmin(true);
}
} else {
//普通角色权限
permission.getFeatureIds().addAll(buildNormalPermission(role, allFeatures));
}
result.put(key, permission);
}
return null;
}
private List<Long> buildNormalPermission(RoleWithFeature role, List<WorkspaceFeatureRelation> allFeatures) {
//普通角色角色同类型的租户产品权限已分配 且角色上已分配 + 免授权
Set<Long> roleFeatures = role.getFeatureIds();
return allFeatures.stream()
.filter(f -> Objects.equals(f.getProductUnitType(), role.getProductUnitType())
|| !NumberUtil.isPositiveNumber(role.getProductUnitType()))
.map(WorkspaceFeatureRelation::getFeatureId)
.filter(id -> roleFeatures.contains(id) || featureResourceService.isAuthFree(id))
.collect(Collectors.toList());
}
private List<Long> buildAdminPermission(RoleWithFeature role, List<WorkspaceFeatureRelation> allFeatures) {
//超管和管理员 直接取和角色类型匹配的租户产品权限
return allFeatures.stream()
.filter(f -> Objects.equals(f.getProductUnitType(), role.getProductUnitType())
|| !NumberUtil.isPositiveNumber(role.getProductUnitType()))
.map(WorkspaceFeatureRelation::getFeatureId)
.collect(Collectors.toList());
}
private List<WorkspaceFeatureRelation> listWorkspaceFeatureRelations(PermissionQueryContext context) {
//TODO:@Zhan 本期没做产品权限配置这里暂时先查询所有OMS资源作为已配置产品权限
List<ResourcePermission> permissions = featureResourceService.permissionQuery(ResourcePermissionQueryDTO.builder()
.terminals(Collections.singletonList(TerminalInfo.NT_OMS_WEB))
.build());
List<WorkspaceFeatureRelation> result = new ArrayList<>();
for (WorkspaceOUPair ow : context.getWorkspaceOUPairs()) {
List<WorkspaceFeatureRelation> owPermission = permissions.stream().map(p -> WorkspaceFeatureRelation.builder().workspaceId(ow.getWorkspaceId())
.productUnitType(SaasCooperateShipCooperateTypeEnum.OMS.code)
.build()).collect(Collectors.toList());
result.addAll(owPermission);
}
return result;
} }
private List<SaasRoleUserRelation> listRoleUserRelations(PermissionQueryContext context) { private List<SaasRoleUserRelation> listRoleUserRelations(PermissionQueryContext context) {

View File

@ -989,16 +989,16 @@ public class RoleServiceImpl implements RoleService {
} }
List<RoleFeatureRelation> relations = saasRoleDao.listFeatureByIds(roleIds, featureIds); List<RoleFeatureRelation> relations = saasRoleDao.listFeatureByIds(roleIds, featureIds);
Map<Long, List<Long>> mapping = relations.stream() Map<Long, Set<Long>> mapping = relations.stream()
.collect(Collectors.groupingBy(RoleFeatureRelation::getRoleId, .collect(Collectors.groupingBy(RoleFeatureRelation::getRoleId,
Collectors.mapping(RoleFeatureRelation::getFeatureId, Collectors.toList()))); Collectors.mapping(RoleFeatureRelation::getFeatureId, Collectors.toSet())));
for (SaasRole role : roles) { for (SaasRole role : roles) {
result.add(RoleWithFeature.builder() result.add(RoleWithFeature.builder()
.roleId(role.getId()) .roleId(role.getId())
.roleName(role.getName()) .roleName(role.getName())
.roleType(role.getRoleType()) .roleType(role.getRoleType())
.productUnitType(role.getProductUnitType()) .productUnitType(role.getProductUnitType())
.featureIds(mapping.getOrDefault(role.getId(), Collections.emptyList())) .featureIds(mapping.getOrDefault(role.getId(), Collections.emptySet()))
.build()); .build());
} }
return result; return result;

View File

@ -2,12 +2,19 @@ package cn.axzo.tyr.server.service.impl;
import cn.axzo.basics.common.BeanMapper; import cn.axzo.basics.common.BeanMapper;
import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import cn.axzo.pokonyan.config.mybatisplus.BaseEntity;
import cn.axzo.pokonyan.config.redis.RedisClient;
import cn.axzo.tyr.client.common.enums.FeatureResourceAuthType;
import cn.axzo.tyr.client.common.enums.FeatureResourceStatus;
import cn.axzo.tyr.client.common.enums.FeatureResourceType;
import cn.axzo.tyr.client.model.req.FeatureComponentSaveReq; import cn.axzo.tyr.client.model.req.FeatureComponentSaveReq;
import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq; import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq;
import cn.axzo.tyr.server.model.ResourcePermission;
import cn.axzo.tyr.server.model.ResourcePermissionQueryDTO;
import cn.axzo.tyr.server.repository.dao.SaasFeatureResourceDao; import cn.axzo.tyr.server.repository.dao.SaasFeatureResourceDao;
import cn.axzo.tyr.server.repository.entity.SaasFeatureResource; import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import cn.axzo.tyr.server.service.SaasFeatureResourceService; import cn.axzo.tyr.server.service.SaasFeatureResourceService;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.BooleanUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -15,8 +22,11 @@ import org.springframework.stereotype.Service;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static cn.axzo.tyr.server.common.constants.CacheConstant.KEY_AUTH_FREE;
/** /**
* 功能资源服务实现 * 功能资源服务实现
* *
@ -36,13 +46,51 @@ public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceServic
//按需扩展要查询的字段 //按需扩展要查询的字段
return featureResourceDao.lambdaQuery() return featureResourceDao.lambdaQuery()
.select(SaasFeatureResource::getId, .select(SaasFeatureResource::getId,
SaasFeatureResource::getParentId,
SaasFeatureResource::getFeatureCode, SaasFeatureResource::getFeatureCode,
SaasFeatureResource::getFeatureName, SaasFeatureResource::getFeatureName,
SaasFeatureResource::getFeatureType) SaasFeatureResource::getFeatureType)
.eq(SaasFeatureResource::getStatus, FeatureResourceStatus.NORMAL.getCode())
.in(SaasFeatureResource::getId, featureIds) .in(SaasFeatureResource::getId, featureIds)
.in(SaasFeatureResource::getFeatureType, FeatureResourceType.navTypes())
.list(); .list();
} }
@Override
public List<ResourcePermission> permissionQuery(ResourcePermissionQueryDTO param) {
List<SaasFeatureResource> resourceList = featureResourceDao.lambdaQuery()
.select(SaasFeatureResource::getId,
SaasFeatureResource::getParentId,
SaasFeatureResource::getFeatureCode,
SaasFeatureResource::getFeatureType,
SaasFeatureResource::getAuthType)
.in(CollectionUtil.isNotEmpty(param.getIds()), SaasFeatureResource::getId, param.getIds())
.in(CollectionUtil.isNotEmpty(param.getFeatureTypes()), SaasFeatureResource::getFeatureType, param.getFeatureTypes())
.in(CollectionUtil.isNotEmpty(param.getTerminals()), SaasFeatureResource::getTerminal, param.getTerminals())
.list();
return BeanMapper.copyList(resourceList, ResourcePermission.class);
}
@Override
public boolean isAuthFree(Long featureId) {
if (BooleanUtil.isTrue(RedisClient.KeyOps.hasKey(KEY_AUTH_FREE))) {
return RedisClient.SetOps.sIsMember(KEY_AUTH_FREE, featureId);
}
//load from DB
String[] featureIds = this.permissionQuery(ResourcePermissionQueryDTO.builder()
.authType(Collections.singletonList(FeatureResourceAuthType.ALL_ROLE.getCode()))
.build())
.stream()
.map(ResourcePermission::getId).map(String::valueOf)
.toArray(String[]::new);
RedisClient.SetOps.sAdd(KEY_AUTH_FREE, featureIds);
RedisClient.KeyOps.expire(KEY_AUTH_FREE, 120L, TimeUnit.MINUTES);
return RedisClient.SetOps.sIsMember(KEY_AUTH_FREE, featureId);
}
@Override @Override
public void saveOrUpdateMenu(FeatureResourceTreeSaveReq req) { public void saveOrUpdateMenu(FeatureResourceTreeSaveReq req) {
SaasFeatureResource baseResource = BeanMapper.copyBean(req, SaasFeatureResource.class); SaasFeatureResource baseResource = BeanMapper.copyBean(req, SaasFeatureResource.class);