package com.supervision.neo4j.service.impl; 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 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 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 findByName(String caseId, String recordId, String nodeType, String name, String picType) { List 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 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 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 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 map = new HashMap<>(); List list = new ArrayList<>(); List> nodes = new ArrayList<>(); try { Session session = driver.session(); StringBuilder relQuery = new StringBuilder("MATCH (n)-[rel]->(m) " + "WHERE n.picType = $picType " + "AND n.caseId = $caseId "); Map 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"); log.info("relQuery:{}", relQuery); Result run = session.run(relQuery.toString(), params); while (run.hasNext()) { Record record = run.next(); // 组织边 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 sourceNodeMap = new HashMap<>(); sourceNodeMap.put("name", record.get("sourceName").asString()); sourceNodeMap.put("id", String.valueOf(sourceId)); nodes.add(sourceNodeMap); Map targetNodeMap = new HashMap<>(); targetNodeMap.put("name", record.get("targetName").asString()); targetNodeMap.put("id", String.valueOf(targetId)); nodes.add(targetNodeMap); } } catch (Exception e) { log.error("查询失败", e); } // 根据节点ID去重 List> distinctNodes = new ArrayList<>(nodes.stream() .collect(Collectors.toMap( node -> node.get("id"), // 以 ID 为唯一键 node -> node, // 保留整个 Map 作为值 (existing, replacement) -> existing)) // 如果有重复 ID,保留第一个 .values()); // 节点和关系合并 Map nodeRecordMap = electNodeRecord(distinctNodes); list = mergerWebRel(list, nodeRecordMap); nodes = mergeNode(nodes, nodeRecordMap); map.put("list", list); map.put("nodes", nodes); return R.ok(map); } @Override public R getNodeAndRelationListByCaseId(String picType, String caseId) { Map> map = new HashMap<>(); Set nodeLabels = new HashSet<>(); Set 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 sourceLabels = record.get("sourceLabels").asList(Value::asString); List 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); map.put("nodeLabels", nodeLabels); map.put("relTypes", relTypes); return R.ok(map); } record NodeMapRecord(String name, String id, Set idSet) { } /** * 推选出代表节点信息 * * @param nodes key: name ,entityName,id 节点信息 * @return key: name ,value: NodeMapRecord */ private Map electNodeRecord(List> nodes) { Map nodeRecordMap = new HashMap<>(); for (Map node : nodes) { String name = node.get("name"); String id = node.get("id"); NodeMapRecord nodeMapRecord = nodeRecordMap.get(name); if (nodeMapRecord == null) { Set 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> mergeNode(List> nodes, Map nodeRecordMap) { return nodes.stream().map(map -> { Map nodeMap = new HashMap<>(); nodeMap.put("name", map.get("name")); 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 mergerWebRel(List webRelDTOList, Map nodeRecordMap) { Map 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 Predicate distinctPredicate(Function function) { ConcurrentHashMap 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 abstractGraphExcelHeaders = reader.readAll(AbstractGraphExcelHeader.class); Map nodeMap = new HashMap<>(); Map 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 nodeIdSet = new HashSet<>(); HashSet 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 mockDataGraphExcelList = reader.readAll(MockDataGraphExcelHeader.class); Map nodeMap = new HashMap<>(); Map 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); } } } }