fix: 关系图保存时检测环并且合并图
This commit is contained in:
parent
03d3dceb32
commit
bd2558c00d
|
@ -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);
|
||||||
|
}
|
|
@ -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>
|
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue