From 063394857086d1a862b34629f1549daaf4ac58f7 Mon Sep 17 00:00:00 2001 From: "DESKTOP-DDTUS3E\\yaxin" Date: Tue, 12 Nov 2024 09:54:09 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0README.md=2011.6=E5=8F=91?= =?UTF-8?q?=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++--- docker/docker-compose/README.md | 53 +++++++++++++------ docker/docker-compose/asset/dispatchList.png | Bin 0 -> 39728 bytes docker/docker-compose/asset/executorAdd.png | Bin 0 -> 9852 bytes docker/docker-compose/asset/taskAdd.png | Bin 0 -> 26478 bytes 5 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 docker/docker-compose/asset/dispatchList.png create mode 100644 docker/docker-compose/asset/executorAdd.png create mode 100644 docker/docker-compose/asset/taskAdd.png diff --git a/README.md b/README.md index 4bfb638..3b2e4e3 100644 --- a/README.md +++ b/README.md @@ -9,21 +9,25 @@ # 发布时间 2024-11-06 # 变更 +- 优化了案件指标(原子指标)结果详情笔录、证据的展示,统一为右侧抽屉弹框展示详情 +- 优化了提示词配置的交互,支持结构化提示词的创建与修改,支持提示词的调试 +- 优化了原子指标-指标算法交互,数据库、图谱推理指标保留配置语句方式,结构化提取可选择对应提示词 +- 优化案件分析得分规则指标配置,支持部分证据的属性条件判断 +- 优化了案件得分详情添加证据指引功能,提示当前案件待补充证据类型 - 修复笔录提取进度反复横跳的问题 -- 原子指标添加结构化推理功能 -- 变更指标配置规则(得分计算规则) -- 案件指标(原子指标)结果添加证据详情 -- 案件得分详情添加证据指引功能 +- 修复了其他已发现的交互问题 # 新特性 -- 新增提示词配置功能(大模型提取属性、知识图谱展示、新增提示词、列表展示提示词) +- 新增OCR识别证据文本功能 +- 新增证据文件关键信息提取功能 +- 新增提示词配置、调试功能 - 新增案件证据目录分类功能 # 更新步骤 1. 加载镜像(如果docker可以访问外网直接下载,否则需要先上传到服务器) - docker load -i xxl-job-admin:2.4.1.tar.gz 2. 修改.env中的xxl-job中对应的配置 - - XXl_JOB_PASSWORD=sT7SSTiX8&&s + - XXl_JOB_PASSWORD=sT7SSTiX8s - XXL_JOB_PORT_HTTP=8081 - SPRING_DATASOURCE_URL=jdbc:mysql://fu-hsi-mysql:3306/xxl-job?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai 3. 替换docker-compose-base.yml、docker-compose.yml配置文件 diff --git a/docker/docker-compose/README.md b/docker/docker-compose/README.md index dfeca5f..4b022c8 100644 --- a/docker/docker-compose/README.md +++ b/docker/docker-compose/README.md @@ -1,5 +1,3 @@ - - ## 🎬 快速开始 ### 📝 前提条件 @@ -10,17 +8,23 @@ - Docker >= 24.0.6 & Docker Compose >= v2.27.0 ### 安装包结构说明 -| 文件/文件夹 | 说明 | -| - |---------------| -| fu-hsi-nginx/ | 用于存放nginx静态文件 | -| fu-hsi-web/ | 用户存放后端服务程序 | -| .env | 应用的环境配置 | -| docker-compose.yml | docker-compose 配置文件 | + +| 文件/文件夹 | 说明 | +|-------------------------|---------------------| +| fu-hsi-nginx/ | 用于存放nginx静态文件 | +| fu-hsi-web/ | 用户存放后端服务程序 | +| logs/ | 日志目录(fu-hsi-web) | +| minio/ | minio数据 | +| mysql/ | mysql数据 | +| neo4j/ | neo4j数据 | +| .env | 应用的环境配置 | +| docker-compose.yml | docker-compose 配置文件 | | docker-compose-base.yml | docker-compose 配置文件 | ### 🚀 启动服务 1. 加载镜像 + ```shell # 进入install-all文件目录 cd ./install-all @@ -31,6 +35,7 @@ docker load -i fu-shi-install.1.0.0.tar.gz ``` 2. 修改配置文件 + ```shell # 编辑配置文件 $ vi .env @@ -42,23 +47,25 @@ $ vi .env ``` 3. 进入install-all目录,执行命令 + ```shell docker-compose up -d ``` + 等待程序启动完成即可,如果没有启动成功,可重试上面的命令。 启动完成后会打印出下面的日志信息: [+] Running 8/8 -✔ Network docker-compose_fu-hsi Created 0.2s -✔ Container fu-hsi-neo4j Started 0.1s -✔ Container fu-hsi-ocr Started 0.1s -✔ Container fu-hsi-mysql Started 0.1s -✔ Container fu-hsi-minio Started 0.1s -✔ Container fu-hsi-web-docker-compose Started 0.0s -✔ Container fu-hsi-xxl-job-admin Started 0.0s -✔ Container fu-hsi-nginx-docker-compose Started - +✔ Network docker-compose_fu-hsi Created 0.2s +✔ Container fu-hsi-neo4j Started 0.1s +✔ Container fu-hsi-ocr Started 0.1s +✔ Container fu-hsi-mysql Started 0.1s +✔ Container fu-hsi-minio Started 0.1s +✔ Container fu-hsi-web-docker-compose Started 0.0s +✔ Container fu-hsi-xxl-job-admin Started 0.0s +✔ Container fu-hsi-nginx-docker-compose Started 4. 查看启动情况 + ```shell $ docker-compose ps @@ -74,5 +81,17 @@ fu-hsi-web-docker-compose fu-hsi-web:1.0.0 fu-hsi-xxl-job-admin xuxueli/xxl-job-admin:2.4.1 "sh -c 'java -jar $J…" xxl-job-admin 56 seconds ago Up 54 seconds 0.0.0.0:8081->8080/tcp, :::8081->8080/tcp ``` +### xxl-job任务配置 +1. 访问地址:http://ip:8081/xxl-job-admin +2. 登录 用户名:admin 密码:见.env配置文件 +3. 执行器管理-新增 +![img.png](asset/executorAdd.png) +4. 任务管理-新增 +![img.png](asset/taskAdd.png) +5. 任务管理列表-操作-执行一次-保存 +6. 调度日志查看执行结果 +![img.png](asset/dispatchList.png) + ## 🚀 访问服务 + 访问地址:http://ip:9978/case-management 用户名:admin 密码:llm@sst$09637674#w diff --git a/docker/docker-compose/asset/dispatchList.png b/docker/docker-compose/asset/dispatchList.png new file mode 100644 index 0000000000000000000000000000000000000000..992631f5cd84e31a960507bd25b3af4b166bcc24 GIT binary patch literal 39728 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 anw95x*_=s9gl?4qRu;Br@=xD*_&)$lZ=rDj literal 0 HcmV?d00001 diff --git a/docker/docker-compose/asset/taskAdd.png b/docker/docker-compose/asset/taskAdd.png new file mode 100644 index 0000000000000000000000000000000000000000..9302c28e164cc76de255e7d6c7c33e9f3f49c9ec GIT binary patch literal 26478 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 z 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 Date: Wed, 13 Nov 2024 15:12:44 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=A0=B9=E6=8D=AEcaseId=E6=9F=A5=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E7=B1=BB=E5=9E=8B=EF=BC=8C=E5=85=B3=E7=B3=BB=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E9=9B=86=E5=90=88=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/supervision/config/Neo4jConfig.java | 2 +- .../neo4j/controller/Neo4jController.java | 4 + .../neo4j/service/Neo4jService.java | 2 + .../neo4j/service/impl/Neo4jServiceImpl.java | 148 +++++++++++------- 4 files changed, 98 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/supervision/config/Neo4jConfig.java b/src/main/java/com/supervision/config/Neo4jConfig.java index 2c67682..f7d4eff 100644 --- a/src/main/java/com/supervision/config/Neo4jConfig.java +++ b/src/main/java/com/supervision/config/Neo4jConfig.java @@ -23,7 +23,7 @@ public class Neo4jConfig { // 这里可以添加额外的配置,比如加密、连接池设置等 Config config = Config.builder() // 示例:关闭加密(注意:在生产环境中应该启用加密) - .withoutEncryption() +// .withoutEncryption() // 你可以在这里添加更多的配置选项 .build(); diff --git a/src/main/java/com/supervision/neo4j/controller/Neo4jController.java b/src/main/java/com/supervision/neo4j/controller/Neo4jController.java index 338bcdf..1df32f4 100644 --- a/src/main/java/com/supervision/neo4j/controller/Neo4jController.java +++ b/src/main/java/com/supervision/neo4j/controller/Neo4jController.java @@ -89,6 +89,10 @@ public class Neo4jController { public R getNode(@RequestParam String picType, @RequestParam String caseId) { return neo4jService.getNode(picType, caseId); } + @GetMapping("/getNodeAndRelationListByCaseId") + public R getNodeAndRelationListByCaseId(@RequestParam String picType, @RequestParam String caseId) { + return neo4jService.getNodeAndRelationListByCaseId(picType, caseId); + } // @GetMapping("/test") // public R test() { diff --git a/src/main/java/com/supervision/neo4j/service/Neo4jService.java b/src/main/java/com/supervision/neo4j/service/Neo4jService.java index 77fa0c6..2d59b14 100644 --- a/src/main/java/com/supervision/neo4j/service/Neo4jService.java +++ b/src/main/java/com/supervision/neo4j/service/Neo4jService.java @@ -35,6 +35,8 @@ public interface Neo4jService { R getNode(String picType, String caseId); + R getNodeAndRelationListByCaseId(String picType, String caseId); + // R test(); void deleteAbstractGraph(); diff --git a/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java b/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java index 74a649b..e360c0d 100644 --- a/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java +++ b/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java @@ -12,8 +12,8 @@ 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.neo4j.driver.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -106,7 +106,7 @@ public class Neo4jServiceImpl implements Neo4jService { try { Session session = driver.session(); StringBuffer cql = new StringBuffer(); - cql.append("MATCH (n) WHERE n.id = " ).append(id).append(" AND NOT (n)--() DELETE n"); + 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()) { @@ -144,9 +144,7 @@ public class Neo4jServiceImpl implements Neo4jService { 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()); + Result run = session.run("MATCH (n) where id(n) = " + id + Neo4jUtils.NODE_RETURN); node = Neo4jUtils.getOneNode(run); } catch (Exception e) { e.printStackTrace(); @@ -234,14 +232,13 @@ public class Neo4jServiceImpl implements Neo4jService { 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); + 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.toString(), params); + Result run = session.run(cql, params); rel = Neo4jUtils.getOneRel(run); } catch (Exception e) { e.printStackTrace(); @@ -258,14 +255,13 @@ public class Neo4jServiceImpl implements Neo4jService { 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); + 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.toString(), params); + Result run = session.run(cql, params); rel = Neo4jUtils.getOneRel(run); } catch (Exception e) { e.printStackTrace(); @@ -312,7 +308,7 @@ public class Neo4jServiceImpl implements Neo4jService { // 节点和关系合并 Map nodeRecordMap = electNodeRecord(nodes); - list = mergerWebRel(list,nodeRecordMap); + list = mergerWebRel(list, nodeRecordMap); nodes = mergeNode(nodes, nodeRecordMap); map.put("list", list); @@ -320,6 +316,40 @@ public class Neo4jServiceImpl implements Neo4jService { 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) { } @@ -327,59 +357,63 @@ public class Neo4jServiceImpl implements Neo4jService { /** * 推选出代表节点信息 + * * @param nodes key: name ,entityName,id 节点信息 * @return */ - private Map electNodeRecord(List> nodes){ + 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){ + if (nodeMapRecord == null) { Set idSet = new HashSet<>(); idSet.add(id); - nodeRecordMap.put(name, new NodeMapRecord(name, id,idSet)); - }else { + nodeRecordMap.put(name, new NodeMapRecord(name, id, idSet)); + } else { nodeMapRecord.idSet.add(id); } } return nodeRecordMap; } + /** - *合并节点信息 + * 合并节点信息 * 合并依据: - * name为唯一标识 - * @param nodes key: name ,entityName,id + * name为唯一标识 + * + * @param nodes key: name ,entityName,id * @param nodeRecordMap 代表节点信息 * @return */ - private List> mergeNode(List> nodes,Map nodeRecordMap) { + 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()); + 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 代表节点信息 + * @param nodeRecordMap 代表节点信息 * @return */ private List mergerWebRel(List webRelDTOList, Map nodeRecordMap) { @@ -387,31 +421,31 @@ public class Neo4jServiceImpl implements Neo4jService { 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(); + 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 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(""); + 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); - } + 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())) + 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){ + private Predicate distinctPredicate(Function function) { ConcurrentHashMap map = new ConcurrentHashMap<>(); - return (t)-> null == map.putIfAbsent(function.apply(t),true); + return (t) -> null == map.putIfAbsent(function.apply(t), true); } From 0486ef1891275335b883410d5ff8366e5b6d37a8 Mon Sep 17 00:00:00 2001 From: "DESKTOP-DDTUS3E\\yaxin" Date: Thu, 14 Nov 2024 15:16:41 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=A1=88=E4=BB=B6?= =?UTF-8?q?=E5=9B=BE=E8=B0=B1=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=A1=88=E4=BB=B6=E5=9B=BE=E8=B0=B1=E5=85=A8=E9=87=8F=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=92=8C=E5=85=B3=E7=B3=BB=E6=8E=A5=E5=8F=A3=E5=88=9D?= =?UTF-8?q?=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../neo4j/controller/Neo4jController.java | 10 +- .../neo4j/service/Neo4jService.java | 18 ++- .../neo4j/service/impl/Neo4jServiceImpl.java | 112 ++++++++++-------- .../com/supervision/police/vo/GraphReqVO.java | 32 +++++ 4 files changed, 117 insertions(+), 55 deletions(-) create mode 100644 src/main/java/com/supervision/police/vo/GraphReqVO.java diff --git a/src/main/java/com/supervision/neo4j/controller/Neo4jController.java b/src/main/java/com/supervision/neo4j/controller/Neo4jController.java index 1df32f4..64efed2 100644 --- a/src/main/java/com/supervision/neo4j/controller/Neo4jController.java +++ b/src/main/java/com/supervision/neo4j/controller/Neo4jController.java @@ -5,8 +5,7 @@ import com.supervision.common.domain.R; import com.supervision.neo4j.domain.CaseNode; import com.supervision.neo4j.domain.Rel; import com.supervision.neo4j.service.Neo4jService; -//import io.swagger.annotations.Api; -//import io.swagger.annotations.ApiOperation; +import com.supervision.police.vo.GraphReqVO; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -85,10 +84,11 @@ public class Neo4jController { /*************************************************************************************/ - @GetMapping("/getNode") - public R getNode(@RequestParam String picType, @RequestParam String caseId) { - return neo4jService.getNode(picType, caseId); + @PostMapping("/getCaseGraph") + public R getCaseGraph(@RequestBody GraphReqVO graphReqVO) { + return neo4jService.getCaseGraph(graphReqVO); } + @GetMapping("/getNodeAndRelationListByCaseId") public R getNodeAndRelationListByCaseId(@RequestParam String picType, @RequestParam String caseId) { return neo4jService.getNodeAndRelationListByCaseId(picType, caseId); diff --git a/src/main/java/com/supervision/neo4j/service/Neo4jService.java b/src/main/java/com/supervision/neo4j/service/Neo4jService.java index 2d59b14..1f38a47 100644 --- a/src/main/java/com/supervision/neo4j/service/Neo4jService.java +++ b/src/main/java/com/supervision/neo4j/service/Neo4jService.java @@ -3,6 +3,7 @@ package com.supervision.neo4j.service; import com.supervision.common.domain.R; import com.supervision.neo4j.domain.CaseNode; import com.supervision.neo4j.domain.Rel; +import com.supervision.police.vo.GraphReqVO; import java.util.List; @@ -33,8 +34,21 @@ public interface Neo4jService { Rel saveRelation(Rel rel); - R getNode(String picType, String caseId); + /** + * 查询案件图谱 + * + * @param graphReqVO 图谱查询请求参数 + * @return 图谱数据 + */ + R getCaseGraph(GraphReqVO graphReqVO); + /** + * 查询案件图谱全量节点和关系 + * + * @param picType 图谱类型 + * @param caseId 案件id + * @return 图谱数据 + */ R getNodeAndRelationListByCaseId(String picType, String caseId); // R test(); @@ -43,5 +57,5 @@ public interface Neo4jService { void createAbstractGraph(String path, String sheetName); - void mockTestGraph(String path, String sheetName, String recordId, String recordSplitId,String caseId); + void mockTestGraph(String path, String sheetName, String recordId, String recordSplitId, String caseId); } diff --git a/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java b/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java index e360c0d..7788c21 100644 --- a/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java +++ b/src/main/java/com/supervision/neo4j/service/impl/Neo4jServiceImpl.java @@ -4,14 +4,15 @@ 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 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; @@ -50,7 +51,7 @@ public class Neo4jServiceImpl implements Neo4jService { CaseNode res = null; try { Session session = driver.session(); - StringBuffer cql = new StringBuffer(); + StringBuilder cql = new StringBuilder(); Map params = new HashMap<>(); cql.append("CREATE (n:").append(caseNode.getNodeType()).append("{name:$name"); params.put("name", caseNode.getName()); @@ -74,7 +75,7 @@ public class Neo4jServiceImpl implements Neo4jService { Result run = session.run(cql.toString(), params); res = Neo4jUtils.getOneNode(run); } catch (Exception e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return res; } @@ -88,13 +89,12 @@ public class Neo4jServiceImpl implements Neo4jService { public void delNode(Long id) { try { Session session = driver.session(); - StringBuffer cql = new StringBuffer(); + 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()) { - Record next = run.next(); - // log.info(next.toString()); + run.next(); } } catch (Exception e) { log.error(e.getMessage(), e); @@ -105,13 +105,12 @@ public class Neo4jServiceImpl implements Neo4jService { public void deleteNoRelationNode(Long id) { try { Session session = driver.session(); - StringBuffer cql = new StringBuffer(); + 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()) { - Record next = run.next(); - // log.info(next.toString()); + run.next(); } } catch (Exception e) { log.error(e.getMessage(), e); @@ -131,11 +130,10 @@ public class Neo4jServiceImpl implements Neo4jService { log.info(cql.toString()); Result run = session.run(cql.toString()); while (run.hasNext()) { - Record next = run.next(); - // log.info(next.toString()); + run.next(); } } catch (Exception e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } } @@ -147,7 +145,7 @@ public class Neo4jServiceImpl implements Neo4jService { Result run = session.run("MATCH (n) where id(n) = " + id + Neo4jUtils.NODE_RETURN); node = Neo4jUtils.getOneNode(run); } catch (Exception e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return node; } @@ -157,7 +155,7 @@ public class Neo4jServiceImpl implements Neo4jService { List list = new ArrayList<>(); try { Session session = driver.session(); - StringBuffer cql = new StringBuffer(); + StringBuilder cql = new StringBuilder(); cql.append("MATCH (n"); if (StringUtils.isNotEmpty(nodeType)) { cql.append(":"); @@ -185,7 +183,7 @@ public class Neo4jServiceImpl implements Neo4jService { Result run = session.run(cql.toString()); list = Neo4jUtils.getNodeList(run); } catch (Exception e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return list; } @@ -195,7 +193,7 @@ public class Neo4jServiceImpl implements Neo4jService { CaseNode node = null; try { Session session = driver.session(); - StringBuffer cql = new StringBuffer(); + StringBuilder cql = new StringBuilder(); Map params = new HashMap<>(); cql.append("MATCH (n"); if (StringUtils.isNotEmpty(nodeType)) { @@ -223,7 +221,7 @@ public class Neo4jServiceImpl implements Neo4jService { Result run = session.run(cql.toString(), params); node = Neo4jUtils.getOneNode(run); } catch (Exception e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return node; } @@ -241,7 +239,7 @@ public class Neo4jServiceImpl implements Neo4jService { Result run = session.run(cql, params); rel = Neo4jUtils.getOneRel(run); } catch (Exception e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } if (rel != null && rel.getId() != null) { return rel; @@ -252,7 +250,6 @@ public class Neo4jServiceImpl implements Neo4jService { @Override public Rel saveRelation(Rel rel) { - Rel res = null; try { Session session = driver.session(); Map params = new HashMap<>(); @@ -264,50 +261,71 @@ public class Neo4jServiceImpl implements Neo4jService { Result run = session.run(cql, params); rel = Neo4jUtils.getOneRel(run); } catch (Exception e) { - e.printStackTrace(); + log.error(e.getMessage(), e); } return rel; } @Override - public R getNode(String picType, String caseId) { + 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", 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); + 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 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(); + String relName = record.get("relName").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); + 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) { - e.printStackTrace(); + 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(nodes); + Map nodeRecordMap = electNodeRecord(distinctNodes); list = mergerWebRel(list, nodeRecordMap); nodes = mergeNode(nodes, nodeRecordMap); @@ -359,7 +377,7 @@ public class Neo4jServiceImpl implements Neo4jService { * 推选出代表节点信息 * * @param nodes key: name ,entityName,id 节点信息 - * @return + * @return key: name ,value: NodeMapRecord */ private Map electNodeRecord(List> nodes) { Map nodeRecordMap = new HashMap<>(); @@ -385,15 +403,13 @@ public class Neo4jServiceImpl implements Neo4jService { * * @param nodes key: name ,entityName,id * @param nodeRecordMap 代表节点信息 - * @return + * @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")); @@ -414,7 +430,7 @@ public class Neo4jServiceImpl implements Neo4jService { * * @param webRelDTOList 关系信息 * @param nodeRecordMap 代表节点信息 - * @return + * @return 合并后的关系信息 */ private List mergerWebRel(List webRelDTOList, Map nodeRecordMap) { diff --git a/src/main/java/com/supervision/police/vo/GraphReqVO.java b/src/main/java/com/supervision/police/vo/GraphReqVO.java new file mode 100644 index 0000000..9bf968e --- /dev/null +++ b/src/main/java/com/supervision/police/vo/GraphReqVO.java @@ -0,0 +1,32 @@ +package com.supervision.police.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 图谱查询请求参数 + */ +@Data +public class GraphReqVO { + /** + * 案件id + */ + private String caseId; + /** + * 图类型 + */ + private String picType; + /** + * 查询字符串 + */ + private String queryStr; + /** + * 节点标签(如:行为人) + */ + private List nodeLabels; + /** + * 关系类型(如:购买) + */ + private List relTypes; +}