fu-hsi-service/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java

632 lines
27 KiB
Java

package com.supervision.neo4j.service.impl;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import com.supervision.common.domain.R;
import com.supervision.neo4j.domain.CaseNode;
import com.supervision.neo4j.domain.Rel;
import com.supervision.neo4j.dto.WebRelDTO;
import com.supervision.neo4j.service.Neo4jService;
import com.supervision.neo4j.utils.Neo4jUtils;
import com.supervision.police.vo.GraphReqVO;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.driver.Record;
import org.neo4j.driver.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* @author qmy
* @since 2023-10-26
*/
@Slf4j
@Service
public class Neo4jServiceImpl implements Neo4jService {
private final Driver driver;
@Autowired
private Neo4jServiceImpl(Driver driver) {
this.driver = driver;
}
@Override
public CaseNode save(CaseNode caseNode) {
if (StringUtils.isEmpty(caseNode.getName()) || StringUtils.isEmpty(caseNode.getNodeType())) {
throw new RuntimeException("未传节点名称或节点类型或图谱类型!");
}
List<CaseNode> byName = findByName(caseNode.getCaseId(), caseNode.getRecordId(), caseNode.getNodeType(), caseNode.getName(), caseNode.getPicType());
if (byName != null && !byName.isEmpty()) {
throw new RuntimeException("名称已存在!");
}
CaseNode res = null;
try {
Session session = driver.session();
StringBuilder cql = new StringBuilder();
Map<String, Object> params = new HashMap<>();
cql.append("CREATE (n:").append(caseNode.getNodeType()).append("{name:$name");
params.put("name", caseNode.getName());
if (StringUtils.isNotEmpty(caseNode.getRecordId())) {
cql.append(", recordSplitId:$recordSplitId");
params.put("recordSplitId", caseNode.getRecordSplitId());
}
if (StringUtils.isNotEmpty(caseNode.getRecordId())) {
cql.append(", recordId:$recordId");
params.put("recordId", caseNode.getRecordId());
}
if (StringUtils.isNotEmpty(caseNode.getCaseId())) {
cql.append(", caseId:$caseId");
params.put("caseId", caseNode.getCaseId());
}
if (StringUtils.isNotEmpty(caseNode.getPicType())) {
cql.append(", picType:$picType");
params.put("picType", caseNode.getPicType());
}
cql.append("})").append(Neo4jUtils.NODE_RETURN);
Result run = session.run(cql.toString(), params);
res = Neo4jUtils.getOneNode(run);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return res;
}
/**
* ,,,,
*
* @param id ID
*/
@Override
public void delNode(Long id) {
try {
Session session = driver.session();
StringBuilder cql = new StringBuilder();
cql.append("MATCH (n) where id(n) = ").append(id).append(" DELETE n");
log.info(cql.toString());
Result run = session.run(cql.toString());
while (run.hasNext()) {
run.next();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
@Override
public void deleteNoRelationNode(Long id) {
try {
Session session = driver.session();
StringBuilder cql = new StringBuilder();
cql.append("MATCH (n) WHERE n.id = ").append(id).append(" AND NOT (n)--() DELETE n");
log.info(cql.toString());
Result run = session.run(cql.toString());
while (run.hasNext()) {
run.next();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
*
*
* @param relId ID
*/
public void deleteRel(Long relId) {
try {
Session session = driver.session();
StringBuilder cql = new StringBuilder();
cql.append("MATCH ()-[r]->() WHERE id(r) = ").append(relId).append(" DELETE r");
log.info(cql.toString());
Result run = session.run(cql.toString());
while (run.hasNext()) {
run.next();
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
@Override
public CaseNode findById(Long id) {
CaseNode node = null;
try {
Session session = driver.session();
Result run = session.run("MATCH (n) where id(n) = " + id + Neo4jUtils.NODE_RETURN);
node = Neo4jUtils.getOneNode(run);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return node;
}
@Override
public List<CaseNode> findByName(String caseId, String recordId, String nodeType, String name, String picType) {
List<CaseNode> list = new ArrayList<>();
try {
Session session = driver.session();
StringBuilder cql = new StringBuilder();
cql.append("MATCH (n");
if (StringUtils.isNotEmpty(nodeType)) {
cql.append(":");
cql.append(nodeType);
}
cql.append(") where 1 = 1");
if (StringUtils.isNotEmpty(caseId)) {
cql.append(" and n.caseId = ");
cql.append(caseId);
}
if (StringUtils.isNotEmpty(recordId)) {
cql.append(" and n.recordId = ");
cql.append(recordId);
}
if (StringUtils.isNotEmpty(name)) {
cql.append(" and n.name = '");
cql.append(name);
cql.append("'");
}
if (StringUtils.isNotEmpty(picType)) {
cql.append(" and n.picType = ");
cql.append(picType);
}
cql.append(Neo4jUtils.NODE_RETURN);
Result run = session.run(cql.toString());
list = Neo4jUtils.getNodeList(run);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return list;
}
@Override
public CaseNode findOneByName(String caseId, String recordId, String nodeType, String name, String picType) {
CaseNode node = null;
try {
Session session = driver.session();
StringBuilder cql = new StringBuilder();
Map<String, Object> params = new HashMap<>();
cql.append("MATCH (n");
if (StringUtils.isNotEmpty(nodeType)) {
cql.append(":");
cql.append(nodeType);
}
cql.append(") where 1 = 1");
if (StringUtils.isNotEmpty(caseId)) {
cql.append(" and n.caseId = $caseId");
params.put("caseId", caseId);
}
if (StringUtils.isNotEmpty(recordId)) {
cql.append(" and n.recordId = $recordId");
params.put("recordId", recordId);
}
if (StringUtils.isNotEmpty(name)) {
cql.append(" and n.name = $name");
params.put("name", name);
}
if (StringUtils.isNotEmpty(picType)) {
cql.append(" and n.picType = $picType");
params.put("picType", picType);
}
cql.append(Neo4jUtils.NODE_RETURN);
Result run = session.run(cql.toString(), params);
node = Neo4jUtils.getOneNode(run);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return node;
}
@Override
public Rel findRelation(Rel rel) {
try {
Session session = driver.session();
Map<String, Object> params = new HashMap<>();
String cql = "MATCH (a)-[rel:" + rel.getName() + "]->(b) where id(a) = $sourceId and id(b) = $targetId" +
Neo4jUtils.REL_RETURN;
params.put("sourceId", rel.getSourceId());
params.put("targetId", rel.getTargetId());
Result run = session.run(cql, params);
rel = Neo4jUtils.getOneRel(run);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
if (rel != null && rel.getId() != null) {
return rel;
} else {
return null;
}
}
@Override
public Rel saveRelation(Rel rel) {
try {
Session session = driver.session();
Map<String, Object> params = new HashMap<>();
String cql = "MATCH (a), (b) where id(a) = $sourceId and id(b) = $targetId CREATE(a)-[rel:" + rel.getName() +
"]->(b) " + Neo4jUtils.REL_RETURN;
params.put("sourceId", rel.getSourceId());
params.put("targetId", rel.getTargetId());
Result run = session.run(cql, params);
rel = Neo4jUtils.getOneRel(run);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return rel;
}
@Override
public R<?> getCaseGraph(GraphReqVO graphReqVO) {
Map<String, Object> map = new HashMap<>();
List<WebRelDTO> list = new ArrayList<>();
List<Map<String, String>> nodes = new ArrayList<>();
List<Map<String, String>> labelColorMap = graphReqVO.getNodeLabelColorMap();
try {
Session session = driver.session();
StringBuilder relQuery = new StringBuilder("MATCH (n)-[rel]->(m) " +
"WHERE n.picType = $picType " +
"AND n.caseId = $caseId ");
Map<String, Object> params = new HashMap<>();
params.put("picType", graphReqVO.getPicType());
params.put("caseId", graphReqVO.getCaseId());
if (StringUtils.isNotEmpty(graphReqVO.getQueryStr())) {
params.put("queryStr", graphReqVO.getQueryStr());
relQuery.append("AND (n.name CONTAINS $queryStr OR type(rel) CONTAINS $queryStr OR m.name CONTAINS $queryStr) ");
}
if (graphReqVO.getNodeLabels() != null && !graphReqVO.getNodeLabels().isEmpty()) {
params.put("nodeLabels", graphReqVO.getNodeLabels());
relQuery.append("AND (ANY(label IN $nodeLabels WHERE label IN labels(n)) " +
"OR ANY(label IN $nodeLabels WHERE label IN labels(m))) ");
}
if (graphReqVO.getRelTypes() != null && !graphReqVO.getRelTypes().isEmpty()) {
params.put("relTypes", graphReqVO.getRelTypes());
relQuery.append("AND ANY(type IN $relTypes WHERE type = type(rel)) ");
}
relQuery.append("RETURN id(rel) as id, n.name as source, id(n) as sourceId, n.name as sourceName, type(rel) as relName, m.name as target, id(m) as targetId, m.name as targetName, labels(n) as sourceLabels, labels(m) as targetLabels");
log.info("relQuery:{}", relQuery);
Result run = session.run(relQuery.toString(), params);
while (run.hasNext()) {
Record record = run.next();
List<String> sourceLabels = record.get("sourceLabels").asList(Value::asString);
List<String> targetLabels = record.get("targetLabels").asList(Value::asString);
if (sourceLabels == null || sourceLabels.isEmpty() || targetLabels == null || targetLabels.isEmpty()) {
log.info("节点标签为空,跳过");
continue;
}
// 组织边
long sourceId = record.get("sourceId").asLong();
String relName = record.get("relName").asString();
long targetId = record.get("targetId").asLong();
list.add(new WebRelDTO(sourceId, targetId, relName));
// 组织节点
Map<String, String> sourceNodeMap = new HashMap<>();
sourceNodeMap.put("name", record.get("sourceName").asString());
setNodeIdAndColor(nodes, labelColorMap, sourceLabels, sourceId, sourceNodeMap);
Map<String, String> targetNodeMap = new HashMap<>();
targetNodeMap.put("name", record.get("targetName").asString());
setNodeIdAndColor(nodes, labelColorMap, targetLabels, targetId, targetNodeMap);
}
} catch (Exception e) {
log.error("查询失败", e);
}
// 根据节点ID去重
List<Map<String, String>> distinctNodes = new ArrayList<>(nodes.stream()
.collect(Collectors.toMap(
node -> node.get("id"), // 以 ID 为唯一键
node -> node, // 保留整个 Map 作为值
(existing, replacement) -> existing)) // 如果有重复 ID保留第一个
.values());
// 节点和关系合并
Pair<List<WebRelDTO>, List<Map<String, String>>> pair = mergeRecord(distinctNodes, list);
// 将返回结果List<Map<String, String>>转为Map<String, Object>并将其中key为color的键值对包一层itemStyle将键值对放在里面
List<Map<String, Object>> nodesList = new ArrayList<>();
for (Map<String, String> node : pair.getValue()) {
Map<String, String> itemStyle = new HashMap<>();
itemStyle.put("color", node.get("color"));
Map<String, Object> nodeMap = new HashMap<>();
nodeMap.put("name", node.get("name"));
nodeMap.put("id", node.get("id"));
nodeMap.put("itemStyle", itemStyle);
nodesList.add(nodeMap);
}
map.put("list", pair.getKey());
map.put("nodes", nodesList);
return R.ok(map);
}
private void setNodeIdAndColor(List<Map<String, String>> nodes, List<Map<String, String>> labelColorMap, List<String> labels, long id, Map<String, String> nodeMap) {
nodeMap.put("id", String.valueOf(id));
for (Map<String, String> labelColor : labelColorMap) {
if (labels.contains(labelColor.get("name"))) {
nodeMap.put("color", labelColor.get("color"));
break;
}
}
if (!nodeMap.containsKey("color")) {
log.error("节点【{}】没有颜色,随机生成", nodeMap.get("name"));
nodeMap.put("color", Neo4jUtils.getRandomColorPair()[0]);
}
nodes.add(nodeMap);
}
public Pair<List<WebRelDTO>, List<Map<String, String>>> mergeRecord(List<Map<String, String>> nodes, List<WebRelDTO> relDTOS) {
Map<String, NodeMapRecord> nodeRecordMap = electNodeRecord(nodes);
return Pair.of(mergerWebRel(relDTOS, nodeRecordMap), mergeNode(nodes, nodeRecordMap));
}
@Override
public R<?> getNodeAndRelationListByCaseId(String picType, String caseId) {
Map<String, Object> map = new HashMap<>();
Set<String> nodeLabels = new HashSet<>();
Set<String> relTypes = new HashSet<>();
try (Session session = driver.session()) {
// 查询所有匹配caseId的节点及其关联关系
String query = "MATCH (n)-[r]->(m) WHERE n.caseId = $caseId AND m.caseId = $caseId RETURN labels(n) as sourceLabels, type(r) as relName, labels(m) as targetLabels";
session.executeRead(tx -> {
Result result = tx.run(query, Values.parameters("caseId", caseId));
while (result.hasNext()) {
Record record = result.next();
List<String> sourceLabels = record.get("sourceLabels").asList(Value::asString);
List<String> targetLabels = record.get("targetLabels").asList(Value::asString);
if (!sourceLabels.isEmpty()) {
nodeLabels.add(sourceLabels.get(0));
}
if (!targetLabels.isEmpty()) {
nodeLabels.add(targetLabels.get(0));
}
relTypes.add(record.get("relName").asString());
}
return null;
});
} catch (Exception e) {
log.error("查询失败", e);
}
log.info("查询到的节点类型{}个:{}", nodeLabels.size(), nodeLabels);
log.info("查询到的关系类型{}个:{}", relTypes.size(), relTypes);
// 将nodeLabels重新组装成List<Map<String, Object>>nodeLabels中的值作为key:name的值另外创建一个key:itemStyle值是Map<String, String>一个键值对是color+随机颜色另一个是lightColor+随机颜色
List<Map<String, Object>> nodeLabelsList = nodeLabels.stream().map(label -> {
Map<String, Object> nodeLabelMap = new HashMap<>();
Map<String, String> itemStyle = new HashMap<>();
itemStyle.put("color", Neo4jUtils.getRandomColorPair()[0]);
itemStyle.put("lightColor", Neo4jUtils.getRandomColorPair()[1]);
nodeLabelMap.put("name", label);
nodeLabelMap.put("itemStyle", itemStyle);
return nodeLabelMap;
}).toList();
map.put("nodeLabels", nodeLabelsList);
map.put("relTypes", relTypes);
return R.ok(map);
}
record NodeMapRecord(String name, String id, Set<String> idSet) {
}
/**
*
*
* @param nodes key: name ,entityName,id
* @return key: name ,value: NodeMapRecord
*/
private Map<String, NodeMapRecord> electNodeRecord(List<Map<String, String>> nodes) {
Map<String, NodeMapRecord> nodeRecordMap = new HashMap<>();
for (Map<String, String> node : nodes) {
String name = node.get("name");
String id = node.get("id");
NodeMapRecord nodeMapRecord = nodeRecordMap.get(name);
if (nodeMapRecord == null) {
Set<String> idSet = new HashSet<>();
idSet.add(id);
nodeRecordMap.put(name, new NodeMapRecord(name, id, idSet));
} else {
nodeMapRecord.idSet.add(id);
}
}
return nodeRecordMap;
}
/**
*
*
* name
*
* @param nodes key: name ,entityName,id
* @param nodeRecordMap
* @return
*/
private List<Map<String, String>> mergeNode(List<Map<String, String>> nodes, Map<String, NodeMapRecord> nodeRecordMap) {
return nodes.stream().map(map -> {
Map<String, String> nodeMap = new HashMap<>();
nodeMap.put("name", map.get("name"));
nodeMap.put("color", map.get("color"));
NodeMapRecord nodeMapRecord = nodeRecordMap.get(map.get("name"));
if (null == nodeMapRecord) {
log.warn("mergeNode:节点信息异常nodeRecordMap中不存在节点名称为{}的NodeMapRecord", map.get("name"));
return nodeMap;
}
if (!nodeMapRecord.idSet.contains(map.get("id"))) {
log.warn("mergeNode:节点信息异常nodeMapRecord.idSet中不包含节点id:{},节点名称为:{}", map.get("id"), map.get("name"));
return nodeMap;
}
nodeMap.put("id", nodeMapRecord.id);
return nodeMap;
}).filter(map -> StrUtil.isNotEmpty(map.get("id")))
.filter(distinctPredicate(m -> m.get("id"))).collect(Collectors.toList());
}
/**
*
*
* @param webRelDTOList
* @param nodeRecordMap
* @return
*/
private List<WebRelDTO> mergerWebRel(List<WebRelDTO> webRelDTOList, Map<String, NodeMapRecord> nodeRecordMap) {
Map<String, NodeMapRecord> idNodeRecordMap = nodeRecordMap.entrySet().stream()
.collect(Collectors.toMap(entry -> entry.getValue().id, Map.Entry::getValue));
return webRelDTOList.stream().map(webRelDTO -> {
String target = webRelDTO.getTarget();
String source = webRelDTO.getSource();
String name = webRelDTO.getName();
String sourceNew = idNodeRecordMap.entrySet().stream()
.filter(entry -> entry.getValue().idSet.contains(source))
.findAny().map(Map.Entry::getKey).orElse("");
String targetNew = idNodeRecordMap.entrySet().stream()
.filter(entry -> entry.getValue().idSet.contains(target))
.findAny().map(Map.Entry::getKey).orElse("");
if (StrUtil.isEmpty(sourceNew) || StrUtil.isEmpty(targetNew)) {
log.warn("mergerWebRel:关系信息异常nodeRecordMap中不存在节点id:{}或节点id:{}信息,节点名称为:{}", source, target, name);
}
return new WebRelDTO(sourceNew, targetNew, name);
}).filter(webRelDTO -> StrUtil.isNotEmpty(webRelDTO.getSource()) && StrUtil.isNotEmpty(webRelDTO.getTarget()))
.filter(distinctPredicate(rel -> rel.getSource() + rel.getTarget())).toList();
}
private <K> Predicate<K> distinctPredicate(Function<K, Object> function) {
ConcurrentHashMap<Object, Boolean> map = new ConcurrentHashMap<>();
return (t) -> null == map.putIfAbsent(function.apply(t), true);
}
@Override
public void createAbstractGraph(String path, String sheetName) {
// 首先从数据库中读到数据
ExcelReader reader = ExcelUtil.getReader(path, sheetName);
List<AbstractGraphExcelHeader> abstractGraphExcelHeaders = reader.readAll(AbstractGraphExcelHeader.class);
Map<String, CaseNode> nodeMap = new HashMap<>();
Map<String, Rel> relMap = new HashMap<>();
for (AbstractGraphExcelHeader abstractGraphExcelHeader : abstractGraphExcelHeaders) {
// from
if (!nodeMap.containsKey(abstractGraphExcelHeader.getFrom())) {
CaseNode caseNode = new CaseNode(abstractGraphExcelHeader.getFrom(), abstractGraphExcelHeader.getFrom(), "0");
log.info("点:{}插入成功", abstractGraphExcelHeader.getFrom());
CaseNode save = save(caseNode);
nodeMap.put(abstractGraphExcelHeader.getFrom(), save);
}
// to
if (!nodeMap.containsKey(abstractGraphExcelHeader.getTo())) {
CaseNode caseNode = new CaseNode(abstractGraphExcelHeader.getTo(), abstractGraphExcelHeader.getTo(), "0");
CaseNode save = save(caseNode);
log.info("点:{}插入成功", abstractGraphExcelHeader.getTo());
nodeMap.put(abstractGraphExcelHeader.getTo(), save);
}
// relation
if (!relMap.containsKey(abstractGraphExcelHeader.getFrom() + "->" + abstractGraphExcelHeader.getRelation() + "->" + abstractGraphExcelHeader.getTo())) {
Rel rel = new Rel(nodeMap.get(abstractGraphExcelHeader.getFrom()).getId(), abstractGraphExcelHeader.getRelation(), nodeMap.get(abstractGraphExcelHeader.getTo()).getId(), "0");
saveRelation(rel);
log.info("关系:{}插入成功", (abstractGraphExcelHeader.getFrom() + "->" + abstractGraphExcelHeader.getRelation() + "->" + abstractGraphExcelHeader.getTo()));
relMap.put(abstractGraphExcelHeader.getFrom() + "->" + abstractGraphExcelHeader.getRelation() + "->" + abstractGraphExcelHeader.getTo(), rel);
}
}
}
public void deleteAbstractGraph() {
Session session = driver.session();
// 首先查出来所有的抽象节点
Result run = session.run("MATCH (n) WHERE n.picType = '0' OPTIONAL MATCH (n)-[r]-() RETURN id(n) as nodeId, id(r) as relId");
Set<String> nodeIdSet = new HashSet<>();
HashSet<String> relIdSet = new HashSet<>();
while (run.hasNext()) {
Record record = run.next();
String nodeId = Neo4jUtils.valueTransportString(record.get("nodeId"));
nodeIdSet.add(nodeId);
String relId = Neo4jUtils.valueTransportString(record.get("relId"));
relIdSet.add(relId);
}
// 删除边
for (String s : relIdSet) {
long relId = Long.parseLong(s);
deleteRel(relId);
log.info("删除边:{} 成功", relId);
}
// 删除节点
for (String s : nodeIdSet) {
long nodeId = Long.parseLong(s);
delNode(nodeId);
log.info("删除节点:{} 成功", nodeId);
}
}
@Data
private static class AbstractGraphExcelHeader {
private String from;
private String relation;
private String to;
}
@Data
private static class MockDataGraphExcelHeader {
private String fromType;
private String from;
private String relation;
private String to;
private String toType;
}
@Override
public void mockTestGraph(String path, String sheetName, String recordId, String recordSplitId, String caseId) {
// 首先从数据库中读到数据
ExcelReader reader = ExcelUtil.getReader(path, sheetName);
List<MockDataGraphExcelHeader> mockDataGraphExcelList = reader.readAll(MockDataGraphExcelHeader.class);
Map<String, CaseNode> nodeMap = new HashMap<>();
Map<String, Rel> relMap = new HashMap<>();
for (MockDataGraphExcelHeader mockData : mockDataGraphExcelList) {
// from
if (!nodeMap.containsKey(mockData.getFrom())) {
CaseNode caseNode = new CaseNode(mockData.getFrom(), mockData.getFromType(), recordSplitId, recordId, caseId, "1");
log.info("点:{}插入成功", mockData.getFrom());
CaseNode save = save(caseNode);
nodeMap.put(mockData.getFrom(), save);
}
// to
if (!nodeMap.containsKey(mockData.getTo())) {
CaseNode caseNode = new CaseNode(mockData.getTo(), mockData.getToType(), recordSplitId, recordId, caseId, "1");
CaseNode save = save(caseNode);
log.info("点:{}插入成功", mockData.getTo());
nodeMap.put(mockData.getTo(), save);
}
// relation
if (!relMap.containsKey(mockData.getFrom() + "->" + mockData.getRelation() + "->" + mockData.getTo())) {
Rel rel = new Rel(nodeMap.get(mockData.getFrom()).getId(), mockData.getRelation(), nodeMap.get(mockData.getTo()).getId(), "1");
saveRelation(rel);
log.info("关系:{}插入成功", (mockData.getFrom() + "->" + mockData.getRelation() + "->" + mockData.getTo()));
relMap.put(mockData.getFrom() + "->" + mockData.getRelation() + "->" + mockData.getTo(), rel);
}
}
}
@Override
public List<Record> executeCypher(String cypher, Map<String, Object> parameters) {
return this.driver.session().run(cypher, parameters).list();
}
}