diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/FeatureResourceAuthType.java b/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/FeatureResourceAuthType.java new file mode 100644 index 00000000..01f726a5 --- /dev/null +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/FeatureResourceAuthType.java @@ -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 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); + } +} diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/FeatureResourceStatus.java b/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/FeatureResourceStatus.java new file mode 100644 index 00000000..c14d359f --- /dev/null +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/FeatureResourceStatus.java @@ -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 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); + } +} diff --git a/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/RoleTypeEnum.java b/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/RoleTypeEnum.java index 8f3d3f08..04a09e7f 100644 --- a/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/RoleTypeEnum.java +++ b/tyr-api/src/main/java/cn/axzo/tyr/client/common/enums/RoleTypeEnum.java @@ -6,6 +6,7 @@ import lombok.Getter; import java.util.Arrays; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; @@ -51,4 +52,8 @@ public enum RoleTypeEnum { .orElse(false); } + public boolean apply(String value) { + return Objects.equals(this.value, value); + } + } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/common/constants/CacheConstant.java b/tyr-server/src/main/java/cn/axzo/tyr/server/common/constants/CacheConstant.java new file mode 100644 index 00000000..3b5f250a --- /dev/null +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/common/constants/CacheConstant.java @@ -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"; +} diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionDO.java b/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionDO.java index 10dc7dc4..eb4247e1 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionDO.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionDO.java @@ -5,7 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; +import java.util.Set; /** * 权限对象 @@ -21,5 +21,6 @@ import java.util.List; public class PermissionDO { private Long ouId; private Long workspaceId; - private List featureIds; + private Set featureIds; + private boolean superAdmin; } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionQueryContext.java b/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionQueryContext.java index d2c8ceff..1531be19 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionQueryContext.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/model/PermissionQueryContext.java @@ -30,8 +30,6 @@ public class PermissionQueryContext { private List workspaceOUPairs; /** 登录端 **/ private String terminal; - /** 资源类型 **/ - private List featureTypes; /** 预览角色ID **/ private List previewRoleIds; diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/model/ResourcePermission.java b/tyr-server/src/main/java/cn/axzo/tyr/server/model/ResourcePermission.java new file mode 100644 index 00000000..02460801 --- /dev/null +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/model/ResourcePermission.java @@ -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; +} diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/model/ResourcePermissionQueryDTO.java b/tyr-server/src/main/java/cn/axzo/tyr/server/model/ResourcePermissionQueryDTO.java new file mode 100644 index 00000000..0b7653fd --- /dev/null +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/model/ResourcePermissionQueryDTO.java @@ -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 ids; + + private List featureTypes; + + private List terminals; + + private List authType; + +} diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/model/RoleWithFeature.java b/tyr-server/src/main/java/cn/axzo/tyr/server/model/RoleWithFeature.java index 12a07d65..418af1be 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/model/RoleWithFeature.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/model/RoleWithFeature.java @@ -5,7 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.List; +import java.util.Set; /** * 角色和权限 @@ -28,5 +28,5 @@ public class RoleWithFeature { private Integer productUnitType; - private List featureIds; + private Set featureIds; } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/model/WorkspaceFeatureRelation.java b/tyr-server/src/main/java/cn/axzo/tyr/server/model/WorkspaceFeatureRelation.java new file mode 100644 index 00000000..52f24b3f --- /dev/null +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/model/WorkspaceFeatureRelation.java @@ -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; +} diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java index 0f8df3c4..d8edafaf 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/SaasFeatureResourceService.java @@ -1,5 +1,7 @@ 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.client.model.req.FeatureResourceTreeSaveReq; @@ -15,6 +17,12 @@ import java.util.List; public interface SaasFeatureResourceService { void saveOrUpdateMenu(FeatureResourceTreeSaveReq req); - /** 根据ID查询导航菜单页面信息 - 限制查询字段 **/ + /** 根据ID查询导航菜单页面信息 仅可显示 - 限制查询字段 **/ List listNavByIds(List featureIds); + + /** 资源权限通用查询 **/ + List permissionQuery(ResourcePermissionQueryDTO param); + + /** 是否免授权 **/ + boolean isAuthFree(Long featureId); } diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/PermissionQueryServiceImpl.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/PermissionQueryServiceImpl.java index dc306a98..1a7455fb 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/PermissionQueryServiceImpl.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/PermissionQueryServiceImpl.java @@ -1,18 +1,22 @@ package cn.axzo.tyr.server.service.impl; import cn.axzo.basics.common.BeanMapper; +import cn.axzo.basics.common.util.NumberUtil; 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.req.NavTreeReq; +import cn.axzo.tyr.client.model.res.NavTreeResp; import cn.axzo.tyr.server.model.PermissionDO; 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.UserIdentity; -import cn.axzo.tyr.client.model.base.WorkspaceOUPair; -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.model.WorkspaceFeatureRelation; import cn.axzo.tyr.server.repository.entity.SaasFeatureResource; import cn.axzo.tyr.server.repository.entity.SaasRoleUserRelation; import cn.axzo.tyr.server.service.PermissionQueryService; @@ -26,9 +30,13 @@ import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -51,14 +59,16 @@ public class PermissionQueryServiceImpl implements PermissionQueryService { public List getNavTree(NavTreeReq req) { //构造参数 PermissionQueryContext context = BeanMapper.copyBean(req, PermissionQueryContext.class); - //限制只查菜单页面 - context.setFeatureTypes(FeatureResourceType.navTypes()); + //查询权限 List permissions = queryUserPermission(context); if (CollectionUtil.isEmpty(permissions)) { return Collections.emptyList(); } - List featureIds = permissions.stream().map(PermissionDO::getFeatureIds).flatMap(List::stream).collect(Collectors.toList()); + List featureIds = permissions.stream() + .map(PermissionDO::getFeatureIds) + .flatMap(Set::stream) + .collect(Collectors.toList()); //反查资源信息 List resourceList = featureResourceService.listNavByIds(featureIds); //组装导航树 @@ -74,13 +84,91 @@ public class PermissionQueryServiceImpl implements PermissionQueryService { log.warn("no user role relation found"); return Collections.emptyList(); } - Set realWorkspaceId = userRoleRelations.stream().map(SaasRoleUserRelation::getWorkspaceId).collect(Collectors.toSet()); - //TODO: 查询产品分配的权限 + //查询租户产品权限点 + List workspaceFeatureRelations = listWorkspaceFeatureRelations(context); Set roleIds = userRoleRelations.stream().map(SaasRoleUserRelation::getRoleId).collect(Collectors.toSet()); //查询角色权限 List roles = roleService.listWithFeatures(roleIds, context.getFeatureIds()); + //取交集确定权限 + return buildFinalPermission(userRoleRelations, workspaceFeatureRelations, roles); + } - return Collections.emptyList(); + private List buildFinalPermission(List userRoleRelations, + List workspaceFeatureRelations, + List roles) { + + //mapping + Map roleMap = roles.stream() + .collect(Collectors.toMap(RoleWithFeature::getRoleId, Function.identity())); + Map> workspaceMap = workspaceFeatureRelations.stream() + .collect(Collectors.groupingBy(WorkspaceFeatureRelation::getWorkspaceId)); + //按拥有的角色构建权限结果 + Map 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 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 buildNormalPermission(RoleWithFeature role, List allFeatures) { + //普通角色:角色同类型的租户产品权限已分配 且角色上已分配 + 免授权 + Set 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 buildAdminPermission(RoleWithFeature role, List allFeatures) { + //超管和管理员 直接取和角色类型匹配的租户产品权限 + return allFeatures.stream() + .filter(f -> Objects.equals(f.getProductUnitType(), role.getProductUnitType()) + || !NumberUtil.isPositiveNumber(role.getProductUnitType())) + .map(WorkspaceFeatureRelation::getFeatureId) + .collect(Collectors.toList()); + } + + private List listWorkspaceFeatureRelations(PermissionQueryContext context) { + //TODO:@Zhan 本期没做产品权限配置,这里暂时先查询所有OMS资源作为已配置产品权限 + List permissions = featureResourceService.permissionQuery(ResourcePermissionQueryDTO.builder() + .terminals(Collections.singletonList(TerminalInfo.NT_OMS_WEB)) + .build()); + List result = new ArrayList<>(); + for (WorkspaceOUPair ow : context.getWorkspaceOUPairs()) { + List 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 listRoleUserRelations(PermissionQueryContext context) { diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/RoleServiceImpl.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/RoleServiceImpl.java index c45dafb0..0c380e6a 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/RoleServiceImpl.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/RoleServiceImpl.java @@ -989,16 +989,16 @@ public class RoleServiceImpl implements RoleService { } List relations = saasRoleDao.listFeatureByIds(roleIds, featureIds); - Map> mapping = relations.stream() + Map> mapping = relations.stream() .collect(Collectors.groupingBy(RoleFeatureRelation::getRoleId, - Collectors.mapping(RoleFeatureRelation::getFeatureId, Collectors.toList()))); + Collectors.mapping(RoleFeatureRelation::getFeatureId, Collectors.toSet()))); for (SaasRole role : roles) { result.add(RoleWithFeature.builder() .roleId(role.getId()) .roleName(role.getName()) .roleType(role.getRoleType()) .productUnitType(role.getProductUnitType()) - .featureIds(mapping.getOrDefault(role.getId(), Collections.emptyList())) + .featureIds(mapping.getOrDefault(role.getId(), Collections.emptySet())) .build()); } return result; diff --git a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java index 3825f3bf..8833044c 100644 --- a/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java +++ b/tyr-server/src/main/java/cn/axzo/tyr/server/service/impl/SaasFeatureResourceServiceImpl.java @@ -2,12 +2,19 @@ package cn.axzo.tyr.server.service.impl; import cn.axzo.basics.common.BeanMapper; 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.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.entity.SaasFeatureResource; import cn.axzo.tyr.server.service.SaasFeatureResourceService; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.BooleanUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -15,8 +22,11 @@ import org.springframework.stereotype.Service; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; 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() .select(SaasFeatureResource::getId, + SaasFeatureResource::getParentId, SaasFeatureResource::getFeatureCode, SaasFeatureResource::getFeatureName, SaasFeatureResource::getFeatureType) + .eq(SaasFeatureResource::getStatus, FeatureResourceStatus.NORMAL.getCode()) .in(SaasFeatureResource::getId, featureIds) + .in(SaasFeatureResource::getFeatureType, FeatureResourceType.navTypes()) .list(); } + @Override + public List permissionQuery(ResourcePermissionQueryDTO param) { + List 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 public void saveOrUpdateMenu(FeatureResourceTreeSaveReq req) { SaasFeatureResource baseResource = BeanMapper.copyBean(req, SaasFeatureResource.class);