feat(2227-featureResource): 新增功能资源树查询接口及实现

This commit is contained in:
李昆鹏 2024-04-10 09:10:05 +08:00
parent ed3ac9e5f8
commit c61a03ba2a
9 changed files with 204 additions and 5 deletions

View File

@ -1,6 +1,7 @@
package cn.axzo.tyr.client.feign; package cn.axzo.tyr.client.feign;
import cn.axzo.framework.domain.web.result.ApiResult; import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.tyr.client.model.req.GetFeatureResourceTreeReq;
import cn.axzo.tyr.client.model.req.ResourceSyncReq; import cn.axzo.tyr.client.model.req.ResourceSyncReq;
import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq; import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode; import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
@ -35,6 +36,10 @@ public interface FeatureResourceApi {
@PostMapping("/api/featureResource/saveOrUpdate") @PostMapping("/api/featureResource/saveOrUpdate")
ApiResult<Void> saveMenu(@RequestBody FeatureResourceTreeSaveReq req); ApiResult<Void> saveMenu(@RequestBody FeatureResourceTreeSaveReq req);
/** 查询功能资源树 **/
@PostMapping("/api/featureResource/getTree")
ApiResult<List<FeatureResourceTreeNode>> getTree(@RequestBody @Valid GetFeatureResourceTreeReq req);
/** 删除菜单/页面/组件 **/ /** 删除菜单/页面/组件 **/
@PostMapping("/api/featureResource/delete") @PostMapping("/api/featureResource/delete")
ApiResult<Void> deleteFeatureResource(@RequestParam Long featureId, @RequestParam Long operatorId); ApiResult<Void> deleteFeatureResource(@RequestParam Long featureId, @RequestParam Long operatorId);

View File

@ -0,0 +1,30 @@
package cn.axzo.tyr.client.model.req;
import lombok.*;
import java.util.List;
/**
* @author likunpeng
* @version 1.0
* @date 2024/4/9
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class GetFeatureResourceTreeReq {
/** 查询搜索关键字 **/
private String keyword;
/** 端 **/
private String terminal;
/** 展示状态 默认不传返回全部 0-隐藏 1-显示 **/
private Integer status;
/** feature类型列表 **/
private List<Integer> featureTypes;
}

View File

@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -11,6 +12,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
@Slf4j @Slf4j
@EnableAsync @EnableAsync
@EnableCaching
@EnableDiscoveryClient @EnableDiscoveryClient
@MapperScan(value = {"cn.axzo.tyr.server.repository.mapper"}) @MapperScan(value = {"cn.axzo.tyr.server.repository.mapper"})
@SpringBootApplication(scanBasePackages = "cn.axzo") @SpringBootApplication(scanBasePackages = "cn.axzo")

View File

@ -3,6 +3,7 @@ package cn.axzo.tyr.server.controller.permission;
import cn.axzo.framework.domain.web.result.ApiResult; import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.tyr.client.feign.FeatureResourceApi; import cn.axzo.tyr.client.feign.FeatureResourceApi;
import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq; import cn.axzo.tyr.client.model.req.FeatureResourceTreeSaveReq;
import cn.axzo.tyr.client.model.req.GetFeatureResourceTreeReq;
import cn.axzo.tyr.client.model.req.ResourceSyncReq; import cn.axzo.tyr.client.model.req.ResourceSyncReq;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode; import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
import cn.axzo.tyr.server.service.FeatureResourceSyncService; import cn.axzo.tyr.server.service.FeatureResourceSyncService;
@ -72,4 +73,9 @@ public class FeatureResourceController implements FeatureResourceApi {
return null; return null;
} }
@Override
public ApiResult<List<FeatureResourceTreeNode>> getTree(GetFeatureResourceTreeReq req) {
return ApiResult.ok(featureResourceService.getTree(req));
}
} }

View File

@ -1,10 +1,17 @@
package cn.axzo.tyr.server.repository.dao; package cn.axzo.tyr.server.repository.dao;
import cn.axzo.basics.common.constant.enums.TableIsDeleteEnum;
import cn.axzo.tyr.client.model.req.GetFeatureResourceTreeReq;
import cn.axzo.tyr.server.repository.entity.SaasFeatureResource; import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import cn.axzo.tyr.server.repository.mapper.SaasFeatureResourceMapper; import cn.axzo.tyr.server.repository.mapper.SaasFeatureResourceMapper;
import cn.azxo.framework.common.utils.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Objects;
/** /**
* <p> * <p>
* 功能资源表 服务实现类 * 功能资源表 服务实现类
@ -23,4 +30,17 @@ public class SaasFeatureResourceDao extends ServiceImpl<SaasFeatureResourceMappe
public void replacePath(String oldPath, String newPath) { public void replacePath(String oldPath, String newPath) {
this.baseMapper.replacePath(oldPath, newPath); this.baseMapper.replacePath(oldPath, newPath);
} }
public List<SaasFeatureResource> getByResourceTreeParam(GetFeatureResourceTreeReq req) {
return this.lambdaQuery().select(SaasFeatureResource::getId, SaasFeatureResource::getFeatureCode,
SaasFeatureResource::getFeatureName, SaasFeatureResource::getFeatureType,
SaasFeatureResource::getTerminal, SaasFeatureResource::getParentId,
SaasFeatureResource::getDisplayOrder)
.eq(SaasFeatureResource::getIsDelete, TableIsDeleteEnum.NORMAL.value)
.eq(StringUtils.isNotBlank(req.getTerminal()), SaasFeatureResource::getTerminal, req.getTerminal())
.eq(Objects.nonNull(req.getStatus()), SaasFeatureResource::getStatus, req.getStatus())
.in(CollectionUtils.isNotEmpty(req.getFeatureTypes()), SaasFeatureResource::getFeatureType, req.getFeatureTypes())
.list();
}
} }

View File

@ -1,5 +1,7 @@
package cn.axzo.tyr.server.service; package cn.axzo.tyr.server.service;
import cn.axzo.tyr.client.model.req.GetFeatureResourceTreeReq;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
import cn.axzo.tyr.server.model.ResourcePermission; import cn.axzo.tyr.server.model.ResourcePermission;
import cn.axzo.tyr.server.model.ResourcePermissionQueryDTO; import cn.axzo.tyr.server.model.ResourcePermissionQueryDTO;
import cn.axzo.tyr.server.repository.entity.SaasFeatureResource; import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
@ -36,5 +38,8 @@ public interface SaasFeatureResourceService {
/** 是否免授权 **/ /** 是否免授权 **/
boolean isAuthFree(Long featureId); boolean isAuthFree(Long featureId);
/** 查询资源树 **/
List<FeatureResourceTreeNode> getTree(GetFeatureResourceTreeReq req);
SaasFeatureResource getByCode(String featureCode); SaasFeatureResource getByCode(String featureCode);
} }

View File

@ -0,0 +1,31 @@
package cn.axzo.tyr.server.service.impl;
import cn.axzo.tyr.client.model.req.GetFeatureResourceTreeReq;
import cn.axzo.tyr.server.repository.dao.SaasFeatureResourceDao;
import cn.axzo.tyr.server.repository.entity.SaasFeatureResource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author likunpeng
* @version 1.0
* @date 2024/4/9
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SaasFeatureResourceCacheService {
public static final String CACHE_FEATURE_RESOURCE_TREE = "featureResource:tree";
private final SaasFeatureResourceDao featureResourceDao;
@Cacheable(value = CACHE_FEATURE_RESOURCE_TREE, key = "#req.keyword + '_' + #req.terminal+ '_' + #req.featureTypes", unless = "#result.isEmpty()")
public List<SaasFeatureResource> getByResourceTreeParam (GetFeatureResourceTreeReq req) {
log.info("get feature resource tree has not user cache!");
return featureResourceDao.getByResourceTreeParam(req);
}
}

View File

@ -1,7 +1,8 @@
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.model.IBaseTree; import cn.axzo.basics.common.util.StopWatchUtil;
import cn.axzo.basics.common.util.TreeUtil;
import cn.axzo.pokonyan.config.mybatisplus.BaseEntity; import cn.axzo.pokonyan.config.mybatisplus.BaseEntity;
import cn.axzo.pokonyan.config.redis.RedisClient; import cn.axzo.pokonyan.config.redis.RedisClient;
import cn.axzo.tyr.client.common.enums.FeatureResourceAuthType; import cn.axzo.tyr.client.common.enums.FeatureResourceAuthType;
@ -9,27 +10,32 @@ import cn.axzo.tyr.client.common.enums.FeatureResourceStatus;
import cn.axzo.tyr.client.common.enums.FeatureResourceType; 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.client.model.req.GetFeatureResourceTreeReq;
import cn.axzo.tyr.client.model.res.FeatureResourceDTO;
import cn.axzo.tyr.client.model.res.FeatureResourceTreeNode;
import cn.axzo.tyr.server.model.ResourcePermission; import cn.axzo.tyr.server.model.ResourcePermission;
import cn.axzo.tyr.server.model.ResourcePermissionQueryDTO; 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.azxo.framework.common.utils.StringUtils;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import io.swagger.models.auth.In; import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -46,9 +52,19 @@ import static cn.axzo.tyr.server.common.constants.CacheConstant.KEY_AUTH_FREE;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@RefreshScope
public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceService { public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceService {
private final SaasFeatureResourceDao featureResourceDao; private final SaasFeatureResourceDao featureResourceDao;
private final SaasFeatureResourceCacheService saasFeatureResourceCacheService;
/**
* 功能资源树根节点配置
* key < - > TerminalInfo定义的terminal
* value < - > 端描述
*/
@Value("#{${featureResourceTreeRootNodeMap:{'NT_CMS_WEB_GENERAL':'CMS端', 'NT_OMS_WEB':'OMS端'}}}")
private Map<String, String> treeRootNodeMap;
@Override @Override
public List<SaasFeatureResource> listNavByIds(List<Long> featureIds) { public List<SaasFeatureResource> listNavByIds(List<Long> featureIds) {
@ -108,6 +124,40 @@ public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceServic
} }
@Override @Override
public List<FeatureResourceTreeNode> getTree(GetFeatureResourceTreeReq req) {
List<FeatureResourceTreeNode> rootNodes = treeRootNodeMap.entrySet().stream().map(e -> FeatureResourceTreeNode.builder()
.id(0L).terminal(e.getKey()).featureName(e.getValue()).children(Lists.newArrayList()).build()).collect(Collectors.toList());
StopWatchUtil watch = StopWatchUtil.createStarted("feature-resource-tree");
watch.start("dbQuery");
List<SaasFeatureResource> saasFeatureResources = saasFeatureResourceCacheService.getByResourceTreeParam(req);
watch.stop();
if (CollectionUtils.isEmpty(saasFeatureResources)) {
return Collections.emptyList();
}
List<FeatureResourceTreeNode> treeList = TreeUtil.buildTree(saasFeatureResources.stream()
.map(this::featureResource2Node)
.sorted(Comparator.comparing(FeatureResourceDTO::getDisplayOrder))
.collect(Collectors.toList()));
saasFeatureResources = null; // help GC
//搜索或需要按授权策略过滤 - 有额外的过滤条件
watch.start("filter-node");
List<FeatureResourceTreeNode> filterResultNode = filterTreeNode(req.getKeyword(), treeList);
watch.stop();
//把处理后的树结构添加上根节点
watch.start("fill-children-to-root");
List<FeatureResourceTreeNode> resultNode = fillChildren2Root(rootNodes, filterResultNode);
watch.stop();
log.info("feature-resource-tree cost:{} , ms:{}", watch.prettyPrint(), watch.getTotalTimeMillis());
return resultNode;
}
@Override
@CacheEvict(value = SaasFeatureResourceCacheService.CACHE_FEATURE_RESOURCE_TREE,allEntries = true)
public void saveOrUpdateMenu(FeatureResourceTreeSaveReq req) { public void saveOrUpdateMenu(FeatureResourceTreeSaveReq req) {
SaasFeatureResource baseResource = BeanMapper.copyBean(req, SaasFeatureResource.class); SaasFeatureResource baseResource = BeanMapper.copyBean(req, SaasFeatureResource.class);
baseResource.setUpdateBy(req.getOperatorId()); baseResource.setUpdateBy(req.getOperatorId());
@ -258,4 +308,52 @@ public class SaasFeatureResourceServiceImpl implements SaasFeatureResourceServic
featureResourceDao.updateBatchById(parallelFeature); featureResourceDao.updateBatchById(parallelFeature);
} }
private FeatureResourceTreeNode featureResource2Node(SaasFeatureResource featureResource) {
return FeatureResourceTreeNode.builder()
.id(featureResource.getId())
.featureCode(featureResource.getFeatureCode())
.featureName(featureResource.getFeatureName())
.featureType(featureResource.getFeatureType())
.terminal(featureResource.getTerminal())
.parentId(featureResource.getParentId())
.displayOrder(featureResource.getDisplayOrder())
.build();
}
private List<FeatureResourceTreeNode> filterTreeNode(String keyword, List<FeatureResourceTreeNode> treeList) {
if (StringUtils.isBlank(keyword)) {
return treeList;
}
return treeList.stream().filter(x -> this.recursionFilter(keyword, x)).collect(Collectors.toList());
}
private boolean recursionFilter(String keyword, FeatureResourceTreeNode node) {
boolean matched = node.getFeatureName().contains(keyword);
if (CollectionUtils.isEmpty(node.getNodeChildren())) {
return matched;
}
// 过滤子节点
List<FeatureResourceTreeNode> filterChildren = node.getChildren().stream().filter(x -> recursionFilter(keyword, x)).collect(Collectors.toList());
// 重置子节点
node.setChildren(filterChildren);
if (CollectionUtils.isEmpty(filterChildren)) {
return matched;
}
return true;
}
private List<FeatureResourceTreeNode> fillChildren2Root(List<FeatureResourceTreeNode> rootNodes, List<FeatureResourceTreeNode> childrenNodes) {
Map<String, FeatureResourceTreeNode> rootNodeMap = rootNodes.stream().collect(Collectors.toMap(FeatureResourceDTO::getTerminal, Function.identity(), (v1, v2) -> v1));
for(FeatureResourceTreeNode child : childrenNodes) {
FeatureResourceTreeNode rootNode = rootNodeMap.get(child.getTerminal());
if (child.getParentId() > 0 || Objects.isNull(rootNode)) {
continue;
}
rootNode.getChildren().add(child);
}
return rootNodes;
}
} }

View File

@ -1,4 +1,6 @@
spring: spring:
cache:
type: redis
application: application:
name: tyr name: tyr
cloud: cloud: