feat:(REQ-2227) 增加单测,修改角色树查询接口增加查询条件

This commit is contained in:
lilong 2024-04-16 16:19:11 +08:00
parent a79b7e4272
commit af815aa384
13 changed files with 458 additions and 6 deletions

View File

@ -5,6 +5,8 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@ -21,4 +23,22 @@ public class TreeRoleReq {
* '是否显示'
*/
private Boolean isDisplay;
/**
* 项目部id(不传或者传-1查询的是标准分组)
*/
private List<Long> workspaceIds;
/**
* 单位id(不传或者传-1查询的是标准分组)
*/
private List<Long> ouIds;
/**
* 会转成workspaceTypeCode去过滤根节点
* NT_OMS_WEB -> 6
* NT_CMS_WEB_GENERAL -> 1,2
* NT_CMP_APP_GENERAL -> 1,2
*/
private String terminal;
}

View File

@ -105,6 +105,22 @@
<groupId>cn.axzo.pokonyan</groupId>
<artifactId>pokonyan</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.0.202</version>
<scope>test</scope>
</dependency>
<!-- 内嵌redis server-->
<dependency>
<groupId>com.github.caryyu</groupId>
<artifactId>spring-embedded-redis-server</artifactId>
<version>1.1</version>
<type>pom</type>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -44,6 +44,7 @@ import cn.axzo.tyr.server.service.SaasRoleGroupRelationService;
import cn.axzo.tyr.server.service.SaasRoleGroupService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
@ -59,6 +60,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -98,6 +100,16 @@ public class SaasRoleController implements TyrSaasRoleApi {
private static final String ROLE_TYPE = "ROLE";
private static final String ROLE_GROUP_TYPE = "ROLE_GROUP";
/**
* 没有统一维护的地方
*/
private static final Map<String, List<String>> TERMINAL_WORKSPACE_CODES = Maps.newHashMap();
static {
TERMINAL_WORKSPACE_CODES.put("NT_OMS_WEB", Lists.newArrayList("6"));
TERMINAL_WORKSPACE_CODES.put("NT_CMS_WEB_GENERAL", Lists.newArrayList("1", "2"));
TERMINAL_WORKSPACE_CODES.put("NT_CMP_APP_GENERAL", Lists.newArrayList("1", "2"));
}
@Override
public ApiResult<Long> saveOrUpdate(SaveOrUpdateRoleVO saveOrUpdateRole) {
Long result = roleService.saveOrUpdate(saveOrUpdateRole);
@ -334,6 +346,8 @@ public class SaasRoleController implements TyrSaasRoleApi {
.roleIds(Lists.transform(saasRoleGroupRelations, SaasRoleGroupRelation::getRoleId))
.isDisplay(req.getIsDisplay())
.workspaceType(req.getWorkspaceType())
.workspaceIds(req.getWorkspaceIds())
.ouIds(req.getOuIds())
.build())
.stream()
.collect(Collectors.toMap(SaasRoleRes::getId, Function.identity()));
@ -358,11 +372,13 @@ public class SaasRoleController implements TyrSaasRoleApi {
return Collections.emptyMap();
}
List<SaasRoleGroupVO> roleGroups = saasRoleGroupService.getList(QuerySaasRoleGroupReq.builder()
List<SaasRoleGroupVO> roleGroups = saasRoleGroupService.getRoleGroupList(QuerySaasRoleGroupReq.builder()
.workspaceTypeCode(Lists.transform(commonDicts, CommonDictResp::getDictCode))
.workspaceIds(req.getWorkspaceIds())
.ouIds(req.getOuIds())
.build())
.stream()
.sorted(Comparator.comparing(SaasRoleGroupVO::getSort))
.sorted(Comparator.comparing(e -> Optional.ofNullable(e.getSort()).orElse(1)))
.collect(Collectors.toList());
Map<Long, List<RoleTreeRes>> roles = listRole(roleGroups, req);
@ -425,9 +441,18 @@ public class SaasRoleController implements TyrSaasRoleApi {
}
private List<CommonDictResp> listRootRole(TreeRoleReq req) {
return saasCommonDictService.query(CommonDictQueryReq.builder()
CommonDictQueryReq commonDictQueryReq = CommonDictQueryReq.builder()
.codes(StringUtils.isBlank(req.getWorkspaceTypeCode()) ? null : Lists.newArrayList(req.getWorkspaceTypeCode()))
.scope("role")
.build());
.build();
if (StringUtils.isNotBlank(req.getTerminal())) {
List<String> workspaceTypeCodes = TERMINAL_WORKSPACE_CODES.get(req.getTerminal());
if (CollectionUtils.isEmpty(workspaceTypeCodes)) {
return Collections.emptyList();
}
commonDictQueryReq.setCodes(workspaceTypeCodes);
}
return saasCommonDictService.query(commonDictQueryReq);
}
}

View File

@ -144,6 +144,12 @@ public interface RoleService extends IService<SaasRole> {
@CriteriaField(field = "roleCode", operator = Operator.EQ)
private String roleCode;
@CriteriaField(field = "workspaceId", operator = Operator.EQ)
private List<Long> workspaceIds;
@CriteriaField(field = "ownerOuId", operator = Operator.EQ)
private List<Long> ouIds;
@CriteriaField(ignore = true)
private Boolean needPermission;

View File

@ -76,7 +76,7 @@ spring:
cloud:
nacos:
config:
server-addr: ${NACOS_HOST:test-nacos.axzo.cn}:${NACOS_PORT:80}
server-addr: ${NACOS_HOST:https://test-nacos.axzo.cn}:${NACOS_PORT:443}
file-extension: yaml
namespace: ${NACOS_NAMESPACE_ID:f3c0f0d2-bac4-4498-bee7-9c3636b3afdf}
---

View File

@ -4,7 +4,7 @@
<!-- 导入安心筑全局日志配置 -->
<include resource="logback/logback-axzo.xml" />
<!-- 覆盖开发环境日志配置 -->
<springProfile name="local,dev">
<springProfile name="local,dev,test">
<logger name="cn.axzo" level="DEBUG" />
</springProfile>
</configuration>

View File

@ -0,0 +1,101 @@
package cn.axzo.tyr.server.permission;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.google.common.io.Files;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.AopTestUtils;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.List;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {TestConfig.class})
@AutoConfigureMockMvc
@Transactional
@ActiveProfiles("unittest")
public abstract class BaseTest {
private static final String JSON_FILE_CLASSPATH = "classpath:json/";
/**
* 记录对象原始的 field . 因为这些值可能被mock替换, 在每次执行单元测试前统一进行一次替换恢复
* 避免影响后续的单元测试.
*/
private static Table<Object, String, Object> ORIGIN_FIELD_TABLE = HashBasedTable.create();
@Autowired
protected MockMvc mockMvc;
@BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
}
@AfterEach
public void teardown() {
// restore origin value of field.
ORIGIN_FIELD_TABLE.cellSet().stream().forEach(e -> {
ReflectionTestUtils.setField(e.getRowKey(), e.getColumnKey(), e.getValue());
});
}
protected <T> T unwrapProxy(Object bean) {
return AopTestUtils.getUltimateTargetObject(bean);
}
protected <T> List<T> parseListContent(MvcResult result, Class<T> contentClazz) throws UnsupportedEncodingException {
JSONObject response = JSONObject.parseObject(result.getResponse().getContentAsString());
return JSON.parseObject(response.getString("content"), new TypeReference<List<T>>(contentClazz) {
});
}
public static <T> T loadJson(String fileName, Class<T> clz) {
try {
String jsonString = Files.asCharSource(ResourceUtils.getFile(JSON_FILE_CLASSPATH + fileName), StandardCharsets.UTF_8).read();
return JSON.parseObject(jsonString, clz);
} catch (IOException e) {
throw new RuntimeException("loadJson got error", e);
}
}
public static <T> List<T> loadJsonAsList(String fileName, Class<T> clz) {
try {
String jsonString = Files.asCharSource(ResourceUtils.getFile(JSON_FILE_CLASSPATH + fileName), StandardCharsets.UTF_8).read();
return JSONArray.parseArray(jsonString, clz);
} catch (IOException e) {
throw new RuntimeException("loadJsonAsList got error", e);
}
}
/**
* 设置对象field为mock对象. teardown时恢复成原spring代理的对象
*/
protected void setMockField(Object targetObject, String fieldName, Object value) {
// 保存一份默认值
Object oldField = ReflectionTestUtils.getField(targetObject, fieldName);
ReflectionTestUtils.setField(targetObject, fieldName, value);
if (!ORIGIN_FIELD_TABLE.contains(targetObject, fieldName) && oldField != null) {
ORIGIN_FIELD_TABLE.put(targetObject, fieldName, oldField);
}
}
}

View File

@ -0,0 +1,26 @@
package cn.axzo.tyr.server.permission;
import cn.axzo.framework.domain.web.result.ApiResult;
import cn.axzo.tyr.client.model.permission.PermissionPointTreeNode;
import cn.axzo.tyr.client.model.permission.PermissionPointTreeQueryReq;
import cn.axzo.tyr.server.repository.dao.SaasFeatureDao;
import cn.axzo.tyr.server.repository.entity.SaasFeature;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Arrays;
import java.util.List;
public class DemoTest extends BaseTest {
@Autowired
private SaasFeatureDao saasFeatureDao;
@Test
void ff() {
List<SaasFeature> saasFeatures = saasFeatureDao.listByIds(Arrays.asList(206L, 207L, 208L));
System.out.println(JSON.toJSONString(saasFeatures));
}
}

View File

@ -0,0 +1,132 @@
package cn.axzo.tyr.server.permission;
import com.google.common.base.Joiner;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.Files;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ResourceUtils;
import java.io.File;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* mysql 测试数据加载
* <p>
* 测试数据后缀.sql, 默认加载 DEFAULT section 的数据. 如果需要加载指定section的数据需要带上sectionName
*
* <p>
* 文件中通过 "#-->"来定义section. loader可以加载指定section的数据.
* </p>
* <p>
* 文件路径: test/resources/mysql/${testClassName}.sql.
* <p>
* 数据格式如下
* <pre>
* #-->DEFAULT
* sql statement1;
* sql statement2;
*
* #-->section1
* sql statement3;
* sql statement4;
*
* #-->section2
* sql statement5;
* </pre>
*
* @author chenjun
*/
@Slf4j
public class MysqlDataLoader {
private final static String MYSQL_DATA_PATH = "classpath:mysql/";
private final static String MYSQL_FILE_SUFFIX = ".sql";
private final static String DEFAULT_SECTION = "DEFAULT";
@Autowired
private JdbcTemplate jdbcTemplate;
private LoadingCache<File, Multimap<String, String>> sqlFileCaches = CacheBuilder.newBuilder()
.build(new CacheLoader<File, Multimap<String, String>>() {
@SuppressWarnings("NullableProblems")
public Multimap<String, String> load(File file) throws Exception {
List<String> lines = Files.asCharSource(file, Charset.forName("utf-8")).readLines();
String sectionFlag = "#-->";
Multimap<String, String> sections = LinkedListMultimap.create();
String currentSectionName = "";
// 按照section分组.
for (String line : lines) {
line = line.trim();
if (StringUtils.isAllBlank(line)) {
continue;
}
String sectionName = StringUtils.substringAfter(line, sectionFlag);
if (org.apache.logging.log4j.util.Strings.isNotBlank(sectionName)) {
currentSectionName = sectionName;
} else {
sections.put(currentSectionName, line);
}
}
return sections;
}
});
public void loadFromClassName(String className) {
String locationPattern = MYSQL_DATA_PATH + className + MYSQL_FILE_SUFFIX;
File file = getFile(locationPattern);
execStatements(file, ImmutableList.of(DEFAULT_SECTION));
}
public void loadFromClassName(String className, List<String> includeSections) {
String locationPattern = MYSQL_DATA_PATH + className + MYSQL_FILE_SUFFIX;
File file = getFile(locationPattern);
execStatements(file, includeSections);
}
public void load(String filePath) {
String locationPattern = MYSQL_DATA_PATH + filePath;
File file = getFile(locationPattern);
execStatements(file, ImmutableList.of(DEFAULT_SECTION));
}
public void load(String filePath, List<String> includeSections) {
String locationPattern = MYSQL_DATA_PATH + filePath;
File file = getFile(locationPattern);
execStatements(file, includeSections);
}
private File getFile(String locationPattern) {
File file;
try {
file = ResourceUtils.getFile(locationPattern);
} catch (Exception ex) {
throw new RuntimeException("try get file error", ex);
}
return file;
}
private void execStatements(File file, List<String> includeSections) {
Multimap<String, String> sections = sqlFileCaches.getUnchecked(file);
List<String> statements = sections.entries().stream()
.filter(e -> includeSections.isEmpty() || includeSections.contains(e.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
String sql = Joiner.on("\n").join(statements);
log.info("\n>>>>>>>>>>>>>>> Execute SQL {}\n {}", Joiner.on(",").join(includeSections), sql);
jdbcTemplate.update(sql);
log.info("<<<<<<<<<<<<< Execute DONE\n");
}
}

View File

@ -0,0 +1,35 @@
package cn.axzo.tyr.server.permission;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.util.ResourceUtils;
import redis.embedded.RedisServer;
import redis.embedded.exceptions.OsDetectionException;
import redis.embedded.util.OS;
import redis.embedded.util.OSDetector;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@Slf4j
@TestConfiguration
public class TestConfig {
private RedisServer redisServer;
@PostConstruct
public void postConstruct() throws IOException {
redisServer = new RedisServer(16379);
// redis server会在后台启动一个redis server的进程默认IP=127.0.0.1
redisServer.start();
}
@PreDestroy
public void preDestroy() {
// 测试结束后必须调用redisServer.stop(), 否则后台运行的redis server进程会一直存在并占用6379端口
redisServer.stop();
}
}

View File

@ -0,0 +1,34 @@
spring:
datasource:
url: jdbc:h2:mem:demo;MODE=MySQL;DB_CLOSE_DELAY=-1;TRACE_LEVEL_SYSTEM_OUT=0;
driver-class-name: org.h2.Driver
schema: classpath:/mysql/schema.sql
data: classpath:/mysql/data.sql
redis:
host: 127.0.0.1
port: 16379
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
xxl:
job:
admin:
# 地址为刚刚配置的调度中心的访问地址
addresses: http://xxl-job:8080/xxl-job-admin
executor:
# 自定义appName执行器名称
appname: tyr
# ip 可以写 程序跑在的机器ip上也可以不写
ip:
port: 9999
# 执行器日志文件存储路径
# logpath: /data/applogs/xxl-job/jobhandler
# 设置日志过期时间 -1表示永不过期
logretentiondays: -1
accessToken:
flush:
role1052:
saasPreTempalteIdOfProject: 5
saasPreTempalteIdOfOu: 43

View File

@ -0,0 +1 @@
select 1;

View File

@ -0,0 +1,56 @@
CREATE TABLE `demo` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`code` VARCHAR(64) NOT NULL COMMENT 'Code, 唯一',
`name` VARCHAR(128) NOT NULL COMMENT '名称',
-- `tags` JSON NULL COMMENT 'tags, jsonArray',
`tags` VARCHAR(128) NOT NULL DEFAULT '' COMMENT 'json tags',
`features` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'features功能列表',
`description` VARCHAR(512) NOT NULL DEFAULT '' COMMENT '描述',
`status` VARCHAR(16) NOT NULL DEFAULT 'ENABLED' COMMENT '状态: ENABLED',
-- `ext` JSON NULL COMMENT '额外信息, 使用json便于查询',
`ext` VARCHAR(2048) NOT NULL DEFAULT '{}' COMMENT '额外信息',
`creator` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '创建人',
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`modifier` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '更新人',
`modify_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`),
UNIQUE KEY `uk_demo_code` (`code`)
) COMMENT ='demo';
CREATE TABLE `saas_feature` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`feature_name` varchar(128) NOT NULL DEFAULT '' COMMENT '名称',
`feature_code` varchar(128) NOT NULL DEFAULT '' COMMENT 'code',
`icon` varchar(256) NOT NULL DEFAULT '' COMMENT '图标地址',
`parent_id` bigint NOT NULL DEFAULT '0' COMMENT '菜单上级id',
`parent_module_id` bigint NOT NULL DEFAULT '0' COMMENT '产品板块Id',
`link_url` varchar(256) NOT NULL DEFAULT '' COMMENT '链接地址',
`link_type` tinyint NOT NULL DEFAULT '1' COMMENT '1:CMS 2:小程序 4:原生',
`link_ext` text COMMENT '扩展字段',
`micro_app_item_id` varchar(50) NOT NULL DEFAULT '' COMMENT '小程序id 关联micro_app_item id',
`path` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '路径',
`description` varchar(128) NOT NULL DEFAULT '' COMMENT '描述',
`sort` int NOT NULL DEFAULT '5' COMMENT '排序',
`terminal` varchar(64) NOT NULL DEFAULT '' COMMENT '菜单适用于平台 0:企业工作台 1:项目工作台 ',
`feature_type` tinyint NOT NULL DEFAULT '0' COMMENT '类型 0.模块 1.菜单 2页面 3功能',
`is_delete` bigint NOT NULL DEFAULT '0' COMMENT '是否删除',
`create_by` bigint NOT NULL DEFAULT '0' COMMENT '创建人id',
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`update_by` bigint DEFAULT '0' COMMENT '修改人id',
`legacy_layout` varchar(255) NOT NULL DEFAULT '',
`operate_type` tinyint DEFAULT NULL COMMENT '0:查看 1:操作',
`old_id` bigint DEFAULT NULL COMMENT '为了迁移菜单用的,迁移后将废弃',
`fit_ou_type_bit` bigint NOT NULL DEFAULT '65535' COMMENT '适用单位类型-65535:所有 1:总包 2:建设单位 4:监理单位 8:劳务分包 16:专业分包',
`fit_ou_node_type_bit` bigint NOT NULL DEFAULT '65535' COMMENT '适用节点类型65535:所有 1:部门 2:班组 4:小组',
`app_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '网关专属字段,所属应用',
`feature_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '网关专属字段 功能URL对应后端接口url',
`need_cert` tinyint(1) NOT NULL DEFAULT '1' COMMENT '网关专属字段,是否认证 0:无需要认证 1:需要认证',
`need_auth` tinyint(1) NOT NULL DEFAULT '1' COMMENT '网关专属字段,是否授权 0:无需要授权 1:需要授权',
`business_no` varchar(64) DEFAULT NULL COMMENT '业务id用于环境同步作为唯一键',
`parent_business_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '0' COMMENT '父级业务id',
`delegated_type` tinyint DEFAULT NULL COMMENT '授权策略类型,允许为空 1-平台授权型 2-客户授权型 3-免授权型',
PRIMARY KEY (`id`),
KEY `IDX_CODE` (`feature_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4618 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='saas-菜单页面表';