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.common.utils.StringUtils; 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 lombok.Data; import lombok.extern.slf4j.Slf4j; import org.neo4j.driver.*; import org.neo4j.driver.Record; 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(); StringBuffer cql = new StringBuffer(); 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) { e.printStackTrace(); } return res; } /** * 删除节点,注意,删除节点的时候,要先把边删除掉,不然会报错 * * @param id 点的ID */ @Override public void delNode(Long id) { try { Session session = driver.session(); StringBuffer cql = new StringBuffer(); 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()) { Record next = run.next(); // log.info(next.toString()); } } catch (Exception e) { log.error(e.getMessage(), e); } } @Override public void deleteNoRelationNode(Long id) { try { Session session = driver.session(); StringBuffer cql = new StringBuffer(); 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()) { Record next = run.next(); // log.info(next.toString()); } } 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()) { Record next = run.next(); // log.info(next.toString()); } } catch (Exception e) { e.printStackTrace(); } } @Override public CaseNode findById(Long id) { CaseNode node = null; try { Session session = driver.session(); StringBuffer cql = new StringBuffer(); cql.append("MATCH (n) where id(n) = ").append(id).append(Neo4jUtils.NODE_RETURN); Result run = session.run(cql.toString()); node = Neo4jUtils.getOneNode(run); } catch (Exception e) { e.printStackTrace(); } 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(); StringBuffer cql = new StringBuffer(); 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) { e.printStackTrace(); } return list; } @Override public CaseNode findOneByName(String caseId, String recordId, String nodeType, String name, String picType) { CaseNode node = null; try { Session session = driver.session(); StringBuffer cql = new StringBuffer(); 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) { e.printStackTrace(); } return node; } @Override public Rel findRelation(Rel rel) { try { Session session = driver.session(); StringBuffer cql = new StringBuffer(); Map params = new HashMap<>(); cql.append("MATCH (a)-[rel:").append(rel.getName()).append("]->(b) where id(a) = $sourceId and id(b) = $targetId") .append(Neo4jUtils.REL_RETURN); params.put("sourceId", rel.getSourceId()); params.put("targetId", rel.getTargetId()); Result run = session.run(cql.toString(), params); rel = Neo4jUtils.getOneRel(run); } catch (Exception e) { e.printStackTrace(); } if (rel != null && rel.getId() != null) { return rel; } else { return null; } } @Override public Rel saveRelation(Rel rel) { Rel res = null; try { Session session = driver.session(); StringBuffer cql = new StringBuffer(); Map params = new HashMap<>(); cql.append("MATCH (a), (b) where id(a) = $sourceId and id(b) = $targetId CREATE(a)-[rel:").append(rel.getName()) .append("]->(b) ").append(Neo4jUtils.REL_RETURN); params.put("sourceId", rel.getSourceId()); params.put("targetId", rel.getTargetId()); Result run = session.run(cql.toString(), params); rel = Neo4jUtils.getOneRel(run); } catch (Exception e) { e.printStackTrace(); } return rel; } @Override public R getNode(String picType, String caseId) { Map map = new HashMap<>(); List list = new ArrayList<>(); List> nodes = new ArrayList<>(); try { Session session = driver.session(); Map params = new HashMap<>(); params.put("picType", picType); params.put("caseId", caseId); Result run = session.run("MATCH (n)-[rel]->(r) where n.picType = r.picType = $picType and n.caseId = r.caseId = $caseId" + " RETURN id(rel) as id, n.name as source, id(n) as sourceId, type(rel) as name, r.name as target, id(r) as targetId", params); while (run.hasNext()) { Record record = run.next(); //long id = record.get("id").asLong(); //String source = record.get("source").asString(); long sourceId = record.get("sourceId").asLong(); String name = record.get("name").asString(); //String target = record.get("target").asString(); long targetId = record.get("targetId").asLong(); list.add(new WebRelDTO(sourceId, targetId, name)); } Result node = session.run("MATCH (n) where n.picType = $picType and n.caseId = $caseId RETURN id(n) as id, n.name as name", params); while (node.hasNext()) { Record record = node.next(); String name = record.get("name").asString(); long idLong = record.get("id").asLong(); Map nodeMap = new HashMap<>(); nodeMap.put("name", name); nodeMap.put("entityName", name); nodeMap.put("id", String.valueOf(idLong)); nodes.add(nodeMap); } } catch (Exception e) { e.printStackTrace(); } // 节点和关系合并 Map nodeRecordMap = electNodeRecord(nodes); list = mergerWebRel(list,nodeRecordMap); nodes = mergeNode(nodes, nodeRecordMap); map.put("list", list); map.put("nodes", nodes); return R.ok(map); } record NodeMapRecord(String name, String id, Set idSet) { } /** * 推选出代表节点信息 * @param nodes key: name ,entityName,id 节点信息 * @return */ 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")); nodeMap.put("entityName", map.get("entityName")); 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); } } } }