refactor:模块树后端重构:

This commit is contained in:
chenjianxing 2020-12-15 10:32:11 +08:00
parent 0dc4e71eb0
commit 27c18ef8a3
19 changed files with 458 additions and 320 deletions

View File

@ -1,16 +1,11 @@
package io.metersphere.api.dto.definition;
import io.metersphere.base.domain.ApiModule;
import io.metersphere.track.dto.TreeNodeDTO;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class ApiModuleDTO extends ApiModule {
private String label;
private List<ApiModuleDTO> children;
public class ApiModuleDTO extends TreeNodeDTO<ApiModuleDTO> {
private String protocol;
}

View File

@ -11,10 +11,12 @@ import io.metersphere.base.domain.ApiModuleExample;
import io.metersphere.base.mapper.ApiDefinitionMapper;
import io.metersphere.base.mapper.ApiModuleMapper;
import io.metersphere.base.mapper.ext.ExtApiDefinitionMapper;
import io.metersphere.base.mapper.ext.ExtApiModuleMapper;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.i18n.Translator;
import io.metersphere.service.NodeTreeService;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
@ -28,11 +30,13 @@ import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class ApiModuleService {
public class ApiModuleService extends NodeTreeService<ApiModuleDTO> {
@Resource
ApiModuleMapper apiModuleMapper;
@Resource
ExtApiModuleMapper extApiModuleMapper;
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ExtApiDefinitionMapper extApiDefinitionMapper;
@ -41,61 +45,10 @@ public class ApiModuleService {
SqlSessionFactory sqlSessionFactory;
public List<ApiModuleDTO> getNodeTreeByProjectId(String projectId, String protocol) {
ApiModuleExample apiDefinitionNodeExample = new ApiModuleExample();
apiDefinitionNodeExample.createCriteria().andProjectIdEqualTo(projectId).andProtocolEqualTo(protocol);
apiDefinitionNodeExample.setOrderByClause("create_time asc");
List<ApiModule> nodes = apiModuleMapper.selectByExample(apiDefinitionNodeExample);
return getNodeTrees(nodes);
List<ApiModuleDTO> apiModules = extApiModuleMapper.getNodeTreeByProjectId(projectId, protocol);
return getNodeTrees(apiModules);
}
public List<ApiModuleDTO> getNodeTrees(List<ApiModule> nodes) {
List<ApiModuleDTO> nodeTreeList = new ArrayList<>();
Map<Integer, List<ApiModule>> nodeLevelMap = new HashMap<>();
nodes.forEach(node -> {
Integer level = node.getLevel();
if (nodeLevelMap.containsKey(level)) {
nodeLevelMap.get(level).add(node);
} else {
List<ApiModule> apiModules = new ArrayList<>();
apiModules.add(node);
nodeLevelMap.put(node.getLevel(), apiModules);
}
});
List<ApiModule> rootNodes = Optional.ofNullable(nodeLevelMap.get(1)).orElse(new ArrayList<>());
rootNodes.forEach(rootNode -> nodeTreeList.add(buildNodeTree(nodeLevelMap, rootNode)));
return nodeTreeList;
}
/**
* 递归构建节点树
*
* @param nodeLevelMap
* @param rootNode
* @return
*/
private ApiModuleDTO buildNodeTree(Map<Integer, List<ApiModule>> nodeLevelMap, ApiModule rootNode) {
ApiModuleDTO nodeTree = new ApiModuleDTO();
BeanUtils.copyBean(nodeTree, rootNode);
nodeTree.setLabel(rootNode.getName());
List<ApiModule> lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
if (lowerNodes == null) {
return nodeTree;
}
List<ApiModuleDTO> children = Optional.ofNullable(nodeTree.getChildren()).orElse(new ArrayList<>());
lowerNodes.forEach(node -> {
if (node.getParentId() != null && node.getParentId().equals(rootNode.getId())) {
children.add(buildNodeTree(nodeLevelMap, node));
nodeTree.setChildren(children);
}
});
return nodeTree;
}
public String addNode(ApiModule node) {
validateNode(node);
return addNodeWithoutValidate(node);

View File

@ -21,5 +21,7 @@ public class ApiModule implements Serializable {
private Long updateTime;
private Double pos;
private static final long serialVersionUID = 1L;
}

View File

@ -633,6 +633,66 @@ public class ApiModuleExample {
addCriterion("update_time not between", value1, value2, "updateTime");
return (Criteria) this;
}
public Criteria andPosIsNull() {
addCriterion("pos is null");
return (Criteria) this;
}
public Criteria andPosIsNotNull() {
addCriterion("pos is not null");
return (Criteria) this;
}
public Criteria andPosEqualTo(Double value) {
addCriterion("pos =", value, "pos");
return (Criteria) this;
}
public Criteria andPosNotEqualTo(Double value) {
addCriterion("pos <>", value, "pos");
return (Criteria) this;
}
public Criteria andPosGreaterThan(Double value) {
addCriterion("pos >", value, "pos");
return (Criteria) this;
}
public Criteria andPosGreaterThanOrEqualTo(Double value) {
addCriterion("pos >=", value, "pos");
return (Criteria) this;
}
public Criteria andPosLessThan(Double value) {
addCriterion("pos <", value, "pos");
return (Criteria) this;
}
public Criteria andPosLessThanOrEqualTo(Double value) {
addCriterion("pos <=", value, "pos");
return (Criteria) this;
}
public Criteria andPosIn(List<Double> values) {
addCriterion("pos in", values, "pos");
return (Criteria) this;
}
public Criteria andPosNotIn(List<Double> values) {
addCriterion("pos not in", values, "pos");
return (Criteria) this;
}
public Criteria andPosBetween(Double value1, Double value2) {
addCriterion("pos between", value1, value2, "pos");
return (Criteria) this;
}
public Criteria andPosNotBetween(Double value1, Double value2) {
addCriterion("pos not between", value1, value2, "pos");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -2,9 +2,8 @@ package io.metersphere.base.mapper;
import io.metersphere.base.domain.ApiModule;
import io.metersphere.base.domain.ApiModuleExample;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface ApiModuleMapper {
long countByExample(ApiModuleExample example);

View File

@ -10,6 +10,7 @@
<result column="level" jdbcType="INTEGER" property="level" />
<result column="create_time" jdbcType="BIGINT" property="createTime" />
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="pos" jdbcType="DOUBLE" property="pos" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -70,7 +71,7 @@
</where>
</sql>
<sql id="Base_Column_List">
id, project_id, `name`, protocol, parent_id, `level`, create_time, update_time
id, project_id, `name`, protocol, parent_id, `level`, create_time, update_time, pos
</sql>
<select id="selectByExample" parameterType="io.metersphere.base.domain.ApiModuleExample" resultMap="BaseResultMap">
select
@ -105,10 +106,12 @@
<insert id="insert" parameterType="io.metersphere.base.domain.ApiModule">
insert into api_module (id, project_id, `name`,
protocol, parent_id, `level`,
create_time, update_time)
create_time, update_time, pos
)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{protocol,jdbcType=VARCHAR}, #{parentId,jdbcType=VARCHAR}, #{level,jdbcType=INTEGER},
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT})
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{pos,jdbcType=DOUBLE}
)
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiModule">
insert into api_module
@ -137,6 +140,9 @@
<if test="updateTime != null">
update_time,
</if>
<if test="pos != null">
pos,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -163,6 +169,9 @@
<if test="updateTime != null">
#{updateTime,jdbcType=BIGINT},
</if>
<if test="pos != null">
#{pos,jdbcType=DOUBLE},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.ApiModuleExample" resultType="java.lang.Long">
@ -198,6 +207,9 @@
<if test="record.updateTime != null">
update_time = #{record.updateTime,jdbcType=BIGINT},
</if>
<if test="record.pos != null">
pos = #{record.pos,jdbcType=DOUBLE},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -212,7 +224,8 @@
parent_id = #{record.parentId,jdbcType=VARCHAR},
`level` = #{record.level,jdbcType=INTEGER},
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT}
update_time = #{record.updateTime,jdbcType=BIGINT},
pos = #{record.pos,jdbcType=DOUBLE}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -241,6 +254,9 @@
<if test="updateTime != null">
update_time = #{updateTime,jdbcType=BIGINT},
</if>
<if test="pos != null">
pos = #{pos,jdbcType=DOUBLE},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -252,7 +268,8 @@
parent_id = #{parentId,jdbcType=VARCHAR},
`level` = #{level,jdbcType=INTEGER},
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT}
update_time = #{updateTime,jdbcType=BIGINT},
pos = #{pos,jdbcType=DOUBLE}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -1,5 +1,6 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.api.dto.definition.ApiModuleDTO;
import io.metersphere.base.domain.ApiModule;
import org.apache.ibatis.annotations.Param;
@ -7,4 +8,5 @@ import java.util.List;
public interface ExtApiModuleMapper {
int insertBatch(@Param("records") List<ApiModule> records);
List<ApiModuleDTO> getNodeTreeByProjectId(@Param("projectId") String projectId, @Param("protocol") String protocol);
}

View File

@ -13,4 +13,12 @@
#{emp.updateTime,jdbcType=BIGINT})
</foreach>
</insert>
<select id="getNodeTreeByProjectId" resultType="io.metersphere.api.dto.definition.ApiModuleDTO">
select
<include refid="io.metersphere.base.mapper.ApiModuleMapper.Base_Column_List"/>
from api_module
where api_module.project_id = #{projectId}
and api_module.protocol = #{protocol}
order by create_time asc
</select>
</mapper>

View File

@ -0,0 +1,17 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.track.dto.TestCaseNodeDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtTestCaseNodeMapper {
List<TestCaseNodeDTO> getNodeTreeByProjectId(@Param("projectId") String projectId);
List<TestCaseNodeDTO> getNodeTreeByProjectIds(@Param("projectIds") List<String> projectIds);
TestCaseNodeDTO get(String id);
void updatePos(String id, Double pos);
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper">
<select id="getNodeTreeByProjectId" resultType="io.metersphere.track.dto.TestCaseNodeDTO">
select
<include refid="io.metersphere.base.mapper.TestCaseNodeMapper.Base_Column_List"/>
from test_case_node
where test_case_node.project_id = #{projectId}
order by pos asc
</select>
<select id="getNodeTreeByProjectIds" resultType="io.metersphere.track.dto.TestCaseNodeDTO">
select
<include refid="io.metersphere.base.mapper.TestCaseNodeMapper.Base_Column_List"/>
from test_case_node
where test_case_node.project_id
in
<foreach collection="projectIds" item="projectId" index="index" open="(" close=")" separator=",">
#{projectId}
</foreach>
</select>
<select id="get" resultType="io.metersphere.track.dto.TestCaseNodeDTO">
select
<include refid="io.metersphere.base.mapper.TestCaseNodeMapper.Base_Column_List"/>
from test_case_node
where id = #{id}
</select>
<update id="updatePos">
update test_case_node set pos = #{pos}
where id = #{id}
</update>
</mapper>

View File

@ -0,0 +1,238 @@
package io.metersphere.service;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.track.dto.TestCaseNodeDTO;
import io.metersphere.track.dto.TreeNodeDTO;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
public class NodeTreeService<T extends TreeNodeDTO> {
protected static final double LIMIT_POS = 64;
protected static final double DEFAULT_POS = 65536;
public List<T> getNodeTrees(List<T> nodes) {
List<T> nodeTreeList = new ArrayList<>();
Map<Integer, List<T>> nodeLevelMap = new HashMap<>();
nodes.forEach(node -> {
Integer level = node.getLevel();
if (nodeLevelMap.containsKey(level)) {
nodeLevelMap.get(level).add(node);
} else {
List<T> testCaseNodes = new ArrayList<>();
testCaseNodes.add(node);
nodeLevelMap.put(node.getLevel(), testCaseNodes);
}
});
List<T> rootNodes = Optional.ofNullable(nodeLevelMap.get(1)).orElse(new ArrayList<>());
rootNodes.forEach(rootNode -> nodeTreeList.add(buildNodeTree(nodeLevelMap, rootNode)));
return nodeTreeList;
}
/**
* 递归构建节点树
*
* @param nodeLevelMap
* @param rootNode
* @return
*/
public T buildNodeTree(Map<Integer, List<T>> nodeLevelMap, T rootNode) {
T nodeTree = (T) new TreeNodeDTO();
BeanUtils.copyBean(nodeTree, rootNode);
nodeTree.setLabel(rootNode.getName());
List<T> lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
if (lowerNodes == null) {
return nodeTree;
}
List<T> children = Optional.ofNullable(nodeTree.getChildren()).orElse(new ArrayList<>());
lowerNodes.forEach(node -> {
if (node.getParentId() != null && node.getParentId().equals(rootNode.getId())) {
children.add(buildNodeTree(nodeLevelMap, node));
nodeTree.setChildren(children);
}
});
return nodeTree;
}
/**
* 去除没有数据的节点
*
* @param rootNode
* @param nodeIds
* @return 是否剪枝
*/
public boolean pruningTree(T rootNode, List<String> nodeIds) {
List<T> children = rootNode.getChildren();
if (children == null || children.isEmpty()) {
//叶子节点,并且该节点无数据
if (!nodeIds.contains(rootNode.getId())) {
return true;
}
}
if (children != null) {
Iterator<T> iterator = children.iterator();
while (iterator.hasNext()) {
T subNode = iterator.next();
if (pruningTree(subNode, nodeIds)) {
iterator.remove();
}
}
if (children.isEmpty() && !nodeIds.contains(rootNode.getId())) {
return true;
}
}
return false;
}
/**
* 根据目标节点路径创建相关节点
*
* @param pathIterator 遍历子路径
* @param path 当前路径
* @param treeNode 当前节点
* @param pathMap 记录节点路径对应的nodeId
*/
protected void createNodeByPathIterator(Iterator<String> pathIterator, String path, TestCaseNodeDTO treeNode,
Map<String, String> pathMap, String projectId, Integer level) {
List<TestCaseNodeDTO> children = treeNode.getChildren();
if (children == null || children.isEmpty() || !pathIterator.hasNext()) {
pathMap.put(path, treeNode.getId());
if (pathIterator.hasNext()) {
createNodeByPath(pathIterator, pathIterator.next().trim(), treeNode, projectId, level, path, pathMap);
}
return;
}
String nodeName = pathIterator.next().trim();
Boolean hasNode = false;
for (TestCaseNodeDTO child : children) {
if (StringUtils.equals(nodeName, child.getName())) {
hasNode = true;
createNodeByPathIterator(pathIterator, path + "/" + child.getName(),
child, pathMap, projectId, level + 1);
}
;
}
//若子节点中不包含该目标节点则在该节点下创建
if (!hasNode) {
createNodeByPath(pathIterator, nodeName, treeNode, projectId, level, path, pathMap);
}
}
/**
* @param pathIterator 迭代器遍历子节点
* @param nodeName 当前节点
* @param pNode 父节点
*/
protected void createNodeByPath(Iterator<String> pathIterator, String nodeName,
TestCaseNodeDTO pNode, String projectId, Integer level,
String rootPath, Map<String, String> pathMap) {
StringBuilder path = new StringBuilder(rootPath);
path.append("/" + nodeName);
String pid = null;
//创建过不创建
if (pathMap.get(path.toString()) != null) {
pid = pathMap.get(path.toString());
level++;
} else {
pid = insertNode(nodeName, pNode == null ? null : pNode.getId(), projectId, level);
pathMap.put(path.toString(), pid);
level++;
}
while (pathIterator.hasNext()) {
String nextNodeName = pathIterator.next();
path.append("/" + nextNodeName);
if (pathMap.get(path.toString()) != null) {
pid = pathMap.get(path.toString());
level++;
} else {
pid = insertNode(nextNodeName, pid, projectId, level);
pathMap.put(path.toString(), pid);
level++;
}
}
}
/**
* 测试用例同级模块排序
*
* @param ids 被拖拽模块相邻的前一个模块 id
* 被拖拽的模块 id
* 被拖拽模块相邻的后一个模块 id
*/
public void sort(List<String> ids) {
// 获取相邻节点 id
String before = ids.get(0);
String id = ids.get(1);
String after = ids.get(2);
T beforeNode = null;
T afterNode = null;
T node = getNode(id);
// 获取相邻节点
if (StringUtils.isNotBlank(before)) {
beforeNode = getNode(before);
beforeNode = beforeNode.getLevel().equals(node.getLevel()) ? beforeNode : null;
}
if (StringUtils.isNotBlank(after)) {
afterNode = getNode(after);
afterNode = afterNode.getLevel().equals(node.getLevel()) ? afterNode : null;
}
double pos;
if (beforeNode == null) {
pos = afterNode != null ? afterNode.getPos() / 2.0 : DEFAULT_POS;
} else {
pos = afterNode != null ? (beforeNode.getPos() + afterNode.getPos()) / 2.0 : beforeNode.getPos() + DEFAULT_POS;
}
node.setPos(pos);
updatePos(node.getId(), node.getPos());
// pos 低于阈值时触发更新方法重新计算此目录的所有同级目录的 pos
if (pos < LIMIT_POS) {
refreshPos(node.getProjectId(), node.getLevel(), node.getParentId());
}
}
public String insertNode(String nodeName, String pId, String projectId, Integer level) {
return "";
}
public void updatePos(String id, Double pos) {
}
protected void refreshPos(String projectId, int level, String parentId) {
}
public T getNode(String id) {
return null;
}
}

View File

@ -4,6 +4,7 @@ import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.TestCaseNode;
import io.metersphere.base.domain.TestCaseNodeExample;
import io.metersphere.base.mapper.TestCaseNodeMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper;
import io.metersphere.commons.constants.TestPlanTestCaseStatus;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.MathUtils;
@ -29,11 +30,9 @@ public class ReportResultComponent extends ReportComponent {
public void init() {
TestCaseNodeService testCaseNodeService = (TestCaseNodeService) CommonBeanFactory.getBean("testCaseNodeService");
TestCaseNodeMapper testCaseNodeMapper = (TestCaseNodeMapper) CommonBeanFactory.getBean("testCaseNodeMapper");
ExtTestCaseNodeMapper extTestCaseNodeMapper = (ExtTestCaseNodeMapper) CommonBeanFactory.getBean("extTestCaseNodeMapper");
TestPlanProjectService testPlanProjectService = (TestPlanProjectService) CommonBeanFactory.getBean("testPlanProjectService");
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdIn(testPlanProjectService.getProjectIdsByPlanId(testPlan.getId()));
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
List<TestCaseNodeDTO> nodes = extTestCaseNodeMapper.getNodeTreeByProjectIds(testPlanProjectService.getProjectIdsByPlanId(testPlan.getId()));
nodeTrees = testCaseNodeService.getNodeTrees(nodes);
nodeTrees.forEach(item -> {
Set<String> childIds = new HashSet<>();

View File

@ -1,16 +1,9 @@
package io.metersphere.track.dto;
import io.metersphere.base.domain.TestCaseNode;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TestCaseNodeDTO extends TestCaseNode {
private String label;
private List<TestCaseNodeDTO> children;
public class TestCaseNodeDTO extends TreeNodeDTO<TestCaseNodeDTO> {
}

View File

@ -0,0 +1,31 @@
package io.metersphere.track.dto;
import io.metersphere.api.dto.definition.ApiModuleDTO;
import lombok.Data;
import java.util.List;
@Data
public class TreeNodeDTO<T> {
private String id;
private String projectId;
private String name;
private String parentId;
private Integer level;
private Long createTime;
private Long updateTime;
private Double pos;
private String label;
private List<T> children;
private static final long serialVersionUID = 1L;
}

View File

@ -5,11 +5,12 @@ import com.google.common.util.concurrent.AtomicDouble;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.*;
import io.metersphere.base.mapper.ext.ExtTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestCaseNodeMapper;
import io.metersphere.commons.constants.TestCaseConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.BeanUtils;
import io.metersphere.exception.ExcelException;
import io.metersphere.i18n.Translator;
import io.metersphere.service.NodeTreeService;
import io.metersphere.track.dto.TestCaseDTO;
import io.metersphere.track.dto.TestCaseNodeDTO;
import io.metersphere.track.request.testcase.DragNodeRequest;
@ -29,11 +30,13 @@ import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class TestCaseNodeService {
public class TestCaseNodeService extends NodeTreeService<TestCaseNodeDTO> {
@Resource
TestCaseNodeMapper testCaseNodeMapper;
@Resource
ExtTestCaseNodeMapper extTestCaseNodeMapper;
@Resource
TestCaseMapper testCaseMapper;
@Resource
TestPlanMapper testPlanMapper;
@ -54,8 +57,6 @@ public class TestCaseNodeService {
@Resource
TestCaseReviewMapper testCaseReviewMapper;
private static final double LIMIT_POS = 64;
public String addNode(TestCaseNode node) {
validateNode(node);
node.setCreateTime(System.currentTimeMillis());
@ -96,64 +97,8 @@ public class TestCaseNodeService {
}
public List<TestCaseNodeDTO> getNodeTreeByProjectId(String projectId) {
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId);
testCaseNodeExample.setOrderByClause("pos asc");
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
return getNodeTrees(nodes);
}
public List<TestCaseNodeDTO> getNodeTrees(List<TestCaseNode> nodes) {
List<TestCaseNodeDTO> nodeTreeList = new ArrayList<>();
Map<Integer, List<TestCaseNode>> nodeLevelMap = new HashMap<>();
nodes.forEach(node -> {
Integer level = node.getLevel();
if (nodeLevelMap.containsKey(level)) {
nodeLevelMap.get(level).add(node);
} else {
List<TestCaseNode> testCaseNodes = new ArrayList<>();
testCaseNodes.add(node);
nodeLevelMap.put(node.getLevel(), testCaseNodes);
}
});
List<TestCaseNode> rootNodes = Optional.ofNullable(nodeLevelMap.get(1)).orElse(new ArrayList<>());
rootNodes.forEach(rootNode -> nodeTreeList.add(buildNodeTree(nodeLevelMap, rootNode)));
return nodeTreeList;
}
/**
* 递归构建节点树
*
* @param nodeLevelMap
* @param rootNode
* @return
*/
private TestCaseNodeDTO buildNodeTree(Map<Integer, List<TestCaseNode>> nodeLevelMap, TestCaseNode rootNode) {
TestCaseNodeDTO nodeTree = new TestCaseNodeDTO();
BeanUtils.copyBean(nodeTree, rootNode);
nodeTree.setLabel(rootNode.getName());
List<TestCaseNode> lowerNodes = nodeLevelMap.get(rootNode.getLevel() + 1);
if (lowerNodes == null) {
return nodeTree;
}
List<TestCaseNodeDTO> children = Optional.ofNullable(nodeTree.getChildren()).orElse(new ArrayList<>());
lowerNodes.forEach(node -> {
if (node.getParentId() != null && node.getParentId().equals(rootNode.getId())) {
children.add(buildNodeTree(nodeLevelMap, node));
nodeTree.setChildren(children);
}
});
return nodeTree;
List<TestCaseNodeDTO> testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId);
return getNodeTrees(testCaseNodes);
}
public int editNode(DragNodeRequest request) {
@ -247,9 +192,7 @@ public class TestCaseNodeService {
return null;
}
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId);
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
List<TestCaseNodeDTO> testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId);
List<String> caseIds = testPlanTestCases.stream()
.map(TestPlanTestCase::getCaseId)
@ -261,7 +204,7 @@ public class TestCaseNodeService {
.map(TestCase::getNodeId)
.collect(Collectors.toList());
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(nodes);
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(testCaseNodes);
Iterator<TestCaseNodeDTO> iterator = nodeTrees.iterator();
while (iterator.hasNext()) {
@ -280,9 +223,7 @@ public class TestCaseNodeService {
return null;
}
TestCaseNodeExample testCaseNodeExample = new TestCaseNodeExample();
testCaseNodeExample.createCriteria().andProjectIdEqualTo(projectId);
List<TestCaseNode> nodes = testCaseNodeMapper.selectByExample(testCaseNodeExample);
List<TestCaseNodeDTO> testCaseNodes = extTestCaseNodeMapper.getNodeTreeByProjectId(projectId);
TestCaseExample testCaseExample = new TestCaseExample();
@ -291,7 +232,7 @@ public class TestCaseNodeService {
.map(TestCase::getNodeId)
.collect(Collectors.toList());
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(nodes);
List<TestCaseNodeDTO> nodeTrees = getNodeTrees(testCaseNodes);
Iterator<TestCaseNodeDTO> iterator = nodeTrees.iterator();
while (iterator.hasNext()) {
@ -304,41 +245,6 @@ public class TestCaseNodeService {
return nodeTrees;
}
/**
* 去除没有数据的节点
*
* @param rootNode
* @param nodeIds
* @return 是否剪枝
*/
public boolean pruningTree(TestCaseNodeDTO rootNode, List<String> nodeIds) {
List<TestCaseNodeDTO> children = rootNode.getChildren();
if (children == null || children.isEmpty()) {
//叶子节点,并且该节点无数据
if (!nodeIds.contains(rootNode.getId())) {
return true;
}
}
if (children != null) {
Iterator<TestCaseNodeDTO> iterator = children.iterator();
while (iterator.hasNext()) {
TestCaseNodeDTO subNode = iterator.next();
if (pruningTree(subNode, nodeIds)) {
iterator.remove();
}
}
if (children.isEmpty() && !nodeIds.contains(rootNode.getId())) {
return true;
}
}
return false;
}
public List<TestCaseNodeDTO> getAllNodeByPlanId(QueryNodeRequest request) {
String planId = request.getTestPlanId();
String projectId = request.getProjectId();
@ -405,86 +311,8 @@ public class TestCaseNodeService {
}
/**
* 根据目标节点路径创建相关节点
*
* @param pathIterator 遍历子路径
* @param path 当前路径
* @param treeNode 当前节点
* @param pathMap 记录节点路径对应的nodeId
*/
private void createNodeByPathIterator(Iterator<String> pathIterator, String path, TestCaseNodeDTO treeNode,
Map<String, String> pathMap, String projectId, Integer level) {
List<TestCaseNodeDTO> children = treeNode.getChildren();
if (children == null || children.isEmpty() || !pathIterator.hasNext()) {
pathMap.put(path, treeNode.getId());
if (pathIterator.hasNext()) {
createNodeByPath(pathIterator, pathIterator.next().trim(), treeNode, projectId, level, path, pathMap);
}
return;
}
String nodeName = pathIterator.next().trim();
Boolean hasNode = false;
for (TestCaseNodeDTO child : children) {
if (StringUtils.equals(nodeName, child.getName())) {
hasNode = true;
createNodeByPathIterator(pathIterator, path + "/" + child.getName(),
child, pathMap, projectId, level + 1);
}
;
}
//若子节点中不包含该目标节点则在该节点下创建
if (!hasNode) {
createNodeByPath(pathIterator, nodeName, treeNode, projectId, level, path, pathMap);
}
}
/**
* @param pathIterator 迭代器遍历子节点
* @param nodeName 当前节点
* @param pNode 父节点
*/
private void createNodeByPath(Iterator<String> pathIterator, String nodeName,
TestCaseNodeDTO pNode, String projectId, Integer level,
String rootPath, Map<String, String> pathMap) {
StringBuilder path = new StringBuilder(rootPath);
path.append("/" + nodeName);
String pid = null;
//创建过不创建
if (pathMap.get(path.toString()) != null) {
pid = pathMap.get(path.toString());
level++;
} else {
pid = insertTestCaseNode(nodeName, pNode == null ? null : pNode.getId(), projectId, level);
pathMap.put(path.toString(), pid);
level++;
}
while (pathIterator.hasNext()) {
String nextNodeName = pathIterator.next();
path.append("/" + nextNodeName);
if (pathMap.get(path.toString()) != null) {
pid = pathMap.get(path.toString());
level++;
} else {
pid = insertTestCaseNode(nextNodeName, pid, projectId, level);
pathMap.put(path.toString(), pid);
level++;
}
}
}
private String insertTestCaseNode(String nodeName, String pId, String projectId, Integer level) {
@Override
public String insertNode(String nodeName, String pId, String projectId, Integer level) {
TestCaseNode testCaseNode = new TestCaseNode();
testCaseNode.setName(nodeName.trim());
testCaseNode.setParentId(pId);
@ -587,51 +415,14 @@ public class TestCaseNodeService {
return testCaseNodeMapper.selectByPrimaryKey(id);
}
@Override
public TestCaseNodeDTO getNode(String id) {
return extTestCaseNodeMapper.get(id);
}
/**
* 测试用例同级模块排序
*
* @param ids 被拖拽模块相邻的前一个模块 id
* 被拖拽的模块 id
* 被拖拽模块相邻的后一个模块 id
*/
public void sort(List<String> ids) {
// 获取相邻节点 id
String before = ids.get(0);
String id = ids.get(1);
String after = ids.get(2);
TestCaseNode beforeCase = null;
TestCaseNode afterCase = null;
TestCaseNode caseNode = getCaseNode(id);
// 获取相邻节点
if (StringUtils.isNotBlank(before)) {
beforeCase = getCaseNode(before);
beforeCase = beforeCase.getLevel().equals(caseNode.getLevel()) ? beforeCase : null;
}
if (StringUtils.isNotBlank(after)) {
afterCase = getCaseNode(after);
afterCase = afterCase.getLevel().equals(caseNode.getLevel()) ? afterCase : null;
}
double pos;
if (beforeCase == null) {
pos = afterCase != null ? afterCase.getPos() / 2.0 : 65536;
} else {
pos = afterCase != null ? (beforeCase.getPos() + afterCase.getPos()) / 2.0 : beforeCase.getPos() + 65536;
}
caseNode.setPos(pos);
testCaseNodeMapper.updateByPrimaryKeySelective(caseNode);
// pos 低于阈值时触发更新方法重新计算此目录的所有同级目录的 pos
if (pos < LIMIT_POS) {
refreshPos(caseNode.getProjectId(), caseNode.getLevel(), caseNode.getParentId());
}
@Override
public void updatePos(String id, Double pos) {
extTestCaseNodeMapper.updatePos(id, pos);
}
/**
@ -661,14 +452,15 @@ public class TestCaseNodeService {
* @param level node level
* @param parentId node parent id
*/
private void refreshPos(String projectId, int level, String parentId) {
@Override
protected void refreshPos(String projectId, int level, String parentId) {
List<TestCaseNode> nodes = getPos(projectId, level, parentId, "pos asc");
if (!CollectionUtils.isEmpty(nodes)) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
TestCaseNodeMapper testCaseNodeMapper = sqlSession.getMapper(TestCaseNodeMapper.class);
AtomicDouble pos = new AtomicDouble(65536);
AtomicDouble pos = new AtomicDouble(DEFAULT_POS);
nodes.forEach((node) -> {
node.setPos(pos.getAndAdd(65536));
node.setPos(pos.getAndAdd(DEFAULT_POS));
testCaseNodeMapper.updateByPrimaryKey(node);
});
sqlSession.flushStatements();
@ -687,9 +479,9 @@ public class TestCaseNodeService {
private double getNextLevelPos(String projectId, int level, String parentId) {
List<TestCaseNode> list = getPos(projectId, level, parentId, "pos desc");
if (!CollectionUtils.isEmpty(list) && list.get(0) != null && list.get(0).getPos() != null) {
return list.get(0).getPos() + 65536;
return list.get(0).getPos() + DEFAULT_POS;
} else {
return 65536;
return DEFAULT_POS;
}
}

@ -1 +1 @@
Subproject commit 1fe20ba15a7ca3fe9f77ddf866021e7c7dfe5969
Subproject commit bb494fc68a2367359c9048fa7250c7618de4afb6

View File

@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS `api_module` (
`protocol` varchar(64) NOT NULL COMMENT 'Node protocol',
`parent_id` varchar(50) DEFAULT NULL COMMENT 'Parent node ID',
`level` int(10) DEFAULT 1 COMMENT 'Node level',
`pos` double DEFAULT NULL COMMENT 'Node order',
`create_time` bigint(13) NOT NULL COMMENT 'Create timestamp',
`update_time` bigint(13) NOT NULL COMMENT 'Update timestamp',
PRIMARY KEY (`id`)

View File

@ -64,8 +64,8 @@
<!--要生成的数据库表 -->
<table tableName="api_scenario"/>
<table tableName="api_scenario_report"/>
<table tableName="api_module"/>
<!--<table tableName="api_scenario_report"/>-->
</context>
</generatorConfiguration>

@ -1 +1 @@
Subproject commit 5835db186d17a3d305073e58affb4e88a71b32f0
Subproject commit a22a3005d9bd254793fcf634d72539cbdf31be3a