fix: 关系图保存时检测环并且合并图

This commit is contained in:
chenjianxing 2021-10-20 14:40:44 +08:00 committed by jianxing
parent 03d3dceb32
commit bd2558c00d
5 changed files with 116 additions and 30 deletions

View File

@ -0,0 +1,10 @@
package io.metersphere.base.mapper.ext;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtRelationshipEdgeMapper {
List<String> getGraphIdsByNodeIds(@Param("ids") List<String> ids);
}

View File

@ -0,0 +1,17 @@
<?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.ExtRelationshipEdgeMapper">
<select id="getGraphIdsByNodeIds" resultType="java.lang.String">
select distinct graph_id
from relationship_edge
where source_id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
or target_id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
</mapper>

View File

@ -563,7 +563,7 @@
<foreach collection="ids" item="id" separator="," open="(" close=")"> <foreach collection="ids" item="id" separator="," open="(" close=")">
#{id} #{id}
</foreach> </foreach>
and test_case.status != 'Trash'; and (test_case.status != 'Trash' or test_case.status is NULL);
</select> </select>
<update id="deleteToGc"> <update id="deleteToGc">

View File

@ -4,6 +4,8 @@ package io.metersphere.service;
import io.metersphere.base.domain.RelationshipEdge; import io.metersphere.base.domain.RelationshipEdge;
import io.metersphere.base.domain.RelationshipEdgeExample; import io.metersphere.base.domain.RelationshipEdgeExample;
import io.metersphere.base.mapper.RelationshipEdgeMapper; import io.metersphere.base.mapper.RelationshipEdgeMapper;
import io.metersphere.base.mapper.ext.ExtRelationshipEdgeMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.SessionUtils; import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.RelationshipEdgeRequest; import io.metersphere.controller.request.RelationshipEdgeRequest;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
@ -15,9 +17,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -31,6 +31,8 @@ public class RelationshipEdgeService {
@Resource @Resource
private RelationshipEdgeMapper relationshipEdgeMapper; private RelationshipEdgeMapper relationshipEdgeMapper;
@Resource @Resource
private ExtRelationshipEdgeMapper extRelationshipEdgeMapper;
@Resource
private SqlSessionFactory sqlSessionFactory; private SqlSessionFactory sqlSessionFactory;
public void delete(String sourceId, String targetId) { public void delete(String sourceId, String targetId) {
@ -95,29 +97,51 @@ public class RelationshipEdgeService {
return relationshipEdgeMapper.selectByExample(example); return relationshipEdgeMapper.selectByExample(example);
} }
/**
* 保存新的边
* 校验是否存在环
* 同时将两个不连通的图合并成一个图
* @param request
*/
public void saveBatch(RelationshipEdgeRequest request) { public void saveBatch(RelationshipEdgeRequest request) {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
RelationshipEdgeMapper batchMapper = sqlSession.getMapper(RelationshipEdgeMapper.class); RelationshipEdgeMapper batchMapper = sqlSession.getMapper(RelationshipEdgeMapper.class);
String graphId = getGraphId(request); String graphId = UUID.randomUUID().toString();
List<RelationshipEdge> relationshipEdges = getEdgesBySaveRequest(request);
// todo 检验是否有环 Set<String> addEdgesIds = new HashSet<>();
if (CollectionUtils.isNotEmpty(request.getTargetIds())) { if (CollectionUtils.isNotEmpty(request.getTargetIds())) {
for (String targetId : request.getTargetIds()) { request.getTargetIds().forEach(targetId -> {
RelationshipEdge edge = getNewRelationshipEdge(graphId, request.getId(), targetId, request.getType()); RelationshipEdge edge = getNewRelationshipEdge(graphId, request.getId(), targetId, request.getType());
batchMapper.insert(edge); relationshipEdges.add(edge);
} addEdgesIds.add(edge.getSourceId() + edge.getTargetId());
});
} }
if (CollectionUtils.isNotEmpty(request.getSourceIds())) { if (CollectionUtils.isNotEmpty(request.getSourceIds())) {
for (String sourceId : request.getSourceIds()) { request.getSourceIds().forEach(sourceId -> {
RelationshipEdge edge = getNewRelationshipEdge(graphId, sourceId, request.getId(), request.getType()); RelationshipEdge edge = getNewRelationshipEdge(graphId, sourceId, request.getId(), request.getType());
batchMapper.insert(edge); relationshipEdges.add(edge);
} addEdgesIds.add(edge.getSourceId() + edge.getTargetId());
});
} }
// 判断是否有环, 两个方向都搜索一遍
if (directedCycle(request.getId(), relationshipEdges, new HashSet<>(), true) ||
directedCycle(request.getId(), relationshipEdges, new HashSet<>(), false)) {
MSException.throwException("关联后存在循环依赖,请检查依赖关系");
};
relationshipEdges.forEach(item -> {
if (addEdgesIds.contains(item.getSourceId() + item.getTargetId())) {
batchMapper.insert(item);
} else {
item.setGraphId(graphId); // 把原来图的id设置成合并后新的图的id
batchMapper.updateByPrimaryKey(item);
}
});
sqlSession.flushStatements(); sqlSession.flushStatements();
} }
@ -133,15 +157,11 @@ public class RelationshipEdgeService {
} }
/** /**
* 获取当前节点所在的图的id * 查找要关联的边所在图的所有的边
* @param request * @param request
* @return * @return
*/ */
private String getGraphId(RelationshipEdgeRequest request) { public List<RelationshipEdge> getEdgesBySaveRequest(RelationshipEdgeRequest request) {
// 判断这些顶点是否已经和其他顶点连通
// 连通的话加到同一个图中否则新建一个图 graphId
String graphId = UUID.randomUUID().toString();
List<String> graphNodes = new ArrayList<>(); List<String> graphNodes = new ArrayList<>();
graphNodes.add(request.getId()); graphNodes.add(request.getId());
if (request.getTargetIds() != null) { if (request.getTargetIds() != null) {
@ -150,18 +170,58 @@ public class RelationshipEdgeService {
if (request.getSourceIds() != null) { if (request.getSourceIds() != null) {
graphNodes.addAll(request.getSourceIds()); graphNodes.addAll(request.getSourceIds());
} }
List<String> graphIds = extRelationshipEdgeMapper.getGraphIdsByNodeIds(graphNodes);
if (CollectionUtils.isEmpty(graphIds)) {
return new ArrayList<>();
}
RelationshipEdgeExample example = new RelationshipEdgeExample(); RelationshipEdgeExample example = new RelationshipEdgeExample();
example.createCriteria() example.createCriteria()
.andSourceIdIn(graphNodes); .andGraphIdIn(graphIds);
example.or(
example.createCriteria() return relationshipEdgeMapper.selectByExample(example);
.andTargetIdIn(graphNodes)
);
List<RelationshipEdge> relationshipEdges = relationshipEdgeMapper.selectByExample(example);
if (CollectionUtils.isNotEmpty(relationshipEdges)) {
return relationshipEdges.get(0).getGraphId();
} }
return graphId;
/**
* 给定一点深度搜索该连通图中是否存在环
* @param id
* @param edges
* @param markSet
* @param isForwardDirection
* @return
*/
public boolean directedCycle(String id, List<RelationshipEdge> edges, Set<String> markSet, Boolean isForwardDirection) {
if (markSet.contains(id)) {
// 如果已经访问过该节点则说明存在环
return true;
}
markSet.add(id);
ArrayList<String> nextLevelNodes = new ArrayList();
for (RelationshipEdge relationshipEdge : edges) {
if (isForwardDirection) {// 正向则搜索 sourceId 是当前节点的边
if (id.equals(relationshipEdge.getSourceId())) {
nextLevelNodes.add(relationshipEdge.getTargetId());
}
} else {
if (id.equals(relationshipEdge.getTargetId())) {
nextLevelNodes.add(relationshipEdge.getSourceId());
}
}
}
for (String nextNode : nextLevelNodes) {
if (directedCycle(nextNode, edges, markSet, isForwardDirection)) {
return true;
};
}
// 关键递归完这一条路径要把这个标记去掉否则会误判为有环
// 比如 1->3, 1->2->3 , 3 经过多次但是无环
markSet.remove(id);
return false;
} }
/** /**

View File

@ -446,7 +446,6 @@ export default {
activated() { activated() {
this.getTemplateField(); this.getTemplateField();
this.condition.filters = {reviewStatus: ["Prepare", "Pass", "UnPass"]}; this.condition.filters = {reviewStatus: ["Prepare", "Pass", "UnPass"]};
this.condition.filters = {status: ["Prepare" , "Underway" , "Completed"]}
let ids = this.$route.params.ids; let ids = this.$route.params.ids;
if (ids) { if (ids) {
this.condition.ids = ids; this.condition.ids = ids;
@ -458,7 +457,7 @@ export default {
selectNodeIds() { selectNodeIds() {
this.page.currentPage = 1; this.page.currentPage = 1;
if(!this.trashEnable){ if(!this.trashEnable){
this.condition.filters.status = ["Prepare" , "Underway" , "Completed"]; this.condition.filters.status = [];
} }
initCondition(this.condition, false); initCondition(this.condition, false);
this.initTableData(); this.initTableData();