diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0be1c0c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic", + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/kgBuilder-base/pom.xml b/kgBuilder-base/pom.xml index c5b91b3..56c8276 100644 --- a/kgBuilder-base/pom.xml +++ b/kgBuilder-base/pom.xml @@ -78,6 +78,12 @@ poi-ooxml 5.2.3 + + + com.hankcs + hanlp + portable-1.8.3 + diff --git a/kgBuilder-base/src/main/java/com/warmer/base/service/KeywordExtractionService.java b/kgBuilder-base/src/main/java/com/warmer/base/service/KeywordExtractionService.java new file mode 100644 index 0000000..07ea0c5 --- /dev/null +++ b/kgBuilder-base/src/main/java/com/warmer/base/service/KeywordExtractionService.java @@ -0,0 +1,140 @@ +package com.warmer.base.service; + +import com.hankcs.hanlp.HanLP; +import com.hankcs.hanlp.seg.common.Term; +import com.hankcs.hanlp.tokenizer.StandardTokenizer; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 关键字提取服务 + * + * @author tanc + */ +@Service +public class KeywordExtractionService { + + // 停用词集合 + private static final Set STOP_WORDS = new HashSet<>(Arrays.asList( + "的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", "一", "一个", "上", "也", "很", "到", "说", "要", "去", "你", "会", "着", "没有", "看", "好", "自己", "这" + )); + + // 故障相关的重要词性 + private static final Set IMPORTANT_POS = new HashSet<>(Arrays.asList( + "n", "nr", "ns", "nt", "nz", "nl", "ng", "v", "vn", "a", "an" + )); + + /** + * 从故障描述中提取关键字 + * + * @param text 故障描述文本 + * @return 关键字列表,按重要性排序 + */ + // 强化自定义词典,保证行业术语被识别为稳定分词 + static { + try { + // 先设置 IOAdapter:从 jar 的 classpath 读取 HanLP 资源 + com.hankcs.hanlp.HanLP.Config.IOAdapter = new com.hankcs.hanlp.corpus.io.ResourceIOAdapter(); + + // 强化自定义词典,保证行业术语被识别为稳定分词 + com.hankcs.hanlp.dictionary.CustomDictionary.add("冷轧", "nz 1000"); + com.hankcs.hanlp.dictionary.CustomDictionary.add("热轧", "nz 1000"); + com.hankcs.hanlp.dictionary.CustomDictionary.add("轴承座", "n 1000"); + com.hankcs.hanlp.dictionary.CustomDictionary.add("轴承", "n 1000"); + } catch (Exception ignored) {} + } + public List extractKeywords(String text) { + if (text == null || text.trim().isEmpty()) { + return new ArrayList<>(); + } + + // 使用HanLP进行分词和词性标注 + List terms = StandardTokenizer.segment(text); + + // 提取关键词 + Map keywordCount = new HashMap<>(); + + for (Term term : terms) { + String word = term.word.trim(); + String nature = term.nature.toString(); + + // 过滤条件:长度大于1,不是停用词,词性重要 + if (word.length() > 1 && + !STOP_WORDS.contains(word) && + IMPORTANT_POS.contains(nature)) { + + keywordCount.put(word, keywordCount.getOrDefault(word, 0) + 1); + } + } + + // 按词频排序,返回关键字列表 + return keywordCount.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed()) + .map(Map.Entry::getKey) + .limit(10) // 最多返回10个关键字 + .collect(Collectors.toList()); + } + + /** + * 提取关键字并返回详细信息 + * + * @param text 故障描述文本 + * @return 关键字详细信息 + */ + public List extractKeywordsWithInfo(String text) { + if (text == null || text.trim().isEmpty()) { + return new ArrayList<>(); + } + + List terms = StandardTokenizer.segment(text); + Map keywordMap = new HashMap<>(); + + for (Term term : terms) { + String word = term.word.trim(); + String nature = term.nature.toString(); + + if (word.length() > 1 && + !STOP_WORDS.contains(word) && + IMPORTANT_POS.contains(nature)) { + + KeywordInfo info = keywordMap.getOrDefault(word, new KeywordInfo(word, nature)); + info.incrementCount(); + keywordMap.put(word, info); + } + } + + return keywordMap.values().stream() + .sorted((a, b) -> Integer.compare(b.getCount(), a.getCount())) + .limit(10) + .collect(Collectors.toList()); + } + + /** + * 关键字信息类 + */ + public static class KeywordInfo { + private String keyword; + private String partOfSpeech; + private int count; + + public KeywordInfo(String keyword, String partOfSpeech) { + this.keyword = keyword; + this.partOfSpeech = partOfSpeech; + this.count = 1; + } + + public void incrementCount() { + this.count++; + } + + // Getters and Setters + public String getKeyword() { return keyword; } + public void setKeyword(String keyword) { this.keyword = keyword; } + public String getPartOfSpeech() { return partOfSpeech; } + public void setPartOfSpeech(String partOfSpeech) { this.partOfSpeech = partOfSpeech; } + public int getCount() { return count; } + public void setCount(int count) { this.count = count; } + } +} \ No newline at end of file diff --git a/kgBuilder-pro/pom.xml b/kgBuilder-pro/pom.xml index c9302ec..69d9f98 100644 --- a/kgBuilder-pro/pom.xml +++ b/kgBuilder-pro/pom.xml @@ -57,7 +57,11 @@ neo4j-java-driver 4.4.12 - + + com.hankcs + hanlp + portable-1.8.3 + diff --git a/kgBuilder-pro/src/main/java/com/warmer/Application.java b/kgBuilder-pro/src/main/java/com/warmer/Application.java index 43cc0ad..43cbf9f 100644 --- a/kgBuilder-pro/src/main/java/com/warmer/Application.java +++ b/kgBuilder-pro/src/main/java/com/warmer/Application.java @@ -44,6 +44,7 @@ public class Application implements ApplicationRunner { domainModel.setModifyTime(DateUtil.getDateNow()); domainModel.setModifyUser("sa"); domainModel.setNodeCount(value); + domainModel.setStatus(1); kgManagerService.updateDomain(domainModel); }else { domainModel=KgDomain.builder() diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/controller/CustomDictController.java b/kgBuilder-pro/src/main/java/com/warmer/web/controller/CustomDictController.java new file mode 100644 index 0000000..cef57f6 --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/controller/CustomDictController.java @@ -0,0 +1,41 @@ +package com.warmer.web.controller; + +import com.warmer.base.util.R; +import com.warmer.web.model.CustomDictEntry; +import com.warmer.web.service.CustomDictService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.*; + +@RestController +@RequestMapping("/customDict") +@CrossOrigin(origins = "*", maxAge = 3600) +public class CustomDictController { + + @Autowired + private CustomDictService customDictService; + + @GetMapping("/list") + public R> list(@RequestParam(required = false) String domain, + @RequestParam(required = false) Integer enabled) { + return R.success(customDictService.list(domain, enabled)); + } + + @PostMapping("/add") + public R add(@RequestBody Map body) { + String word = Objects.toString(body.get("word"), ""); + String nature = Objects.toString(body.get("nature"), "n"); + Integer freq = body.get("freq") == null ? 1000 : Integer.valueOf(body.get("freq").toString()); + String domain = Objects.toString(body.get("domain"), null); + String source = Objects.toString(body.get("source"), "manual"); + if (word.isEmpty()) return R.error("word不能为空"); + boolean ok = customDictService.addWord(word, nature, freq, domain, source); + return ok ? R.success(true) : R.error("添加失败"); + } + + @PostMapping("/disable") + public R disable(@RequestParam Integer id) { + return customDictService.disable(id) ? R.success(true) : R.error("停用失败"); + } +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/controller/GraphDomainController.java b/kgBuilder-pro/src/main/java/com/warmer/web/controller/GraphDomainController.java new file mode 100644 index 0000000..7f1d2b0 --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/controller/GraphDomainController.java @@ -0,0 +1,62 @@ +package com.warmer.web.controller; + +import com.warmer.base.util.R; +import com.warmer.base.util.GraphPageRecord; +import com.warmer.web.service.KGGraphService; +import com.warmer.web.request.GraphQuery; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; + +@RestController +@RequestMapping("/kg/domain") +public class GraphDomainController extends BaseController { + + @Autowired + private KGGraphService kgGraphService; + + /** + * 领域分页(支持按关键字过滤) + * 请求体支持 GraphQuery.keywords,pageIndex,pageSize + */ + @PostMapping("/page") + @ResponseBody + public R>> page(@RequestBody GraphQuery query) { + GraphPageRecord> data = kgGraphService.getPageDomain(query); + return R.success(data, "操作成功"); + } + + /** + * 删除指定领域(标签) + */ + @DeleteMapping("/{domain}") + @ResponseBody + public R delete(@PathVariable("domain") String domain) { + if (StringUtils.isBlank(domain)) { + return R.error("领域名称不能为空"); + } + kgGraphService.deleteKGDomain(domain); + return R.success(null, "删除成功"); + } + + /** + * 重命名领域(标签) + * 请求体:{ "oldName": "...", "newName": "..." } + */ + @PutMapping("/rename") + @ResponseBody + public R rename(@RequestBody HashMap body) { + String oldName = body.get("oldName"); + String newName = body.get("newName"); + if (StringUtils.isBlank(oldName) || StringUtils.isBlank(newName)) { + return R.error("oldName/newName 不能为空"); + } + if (StringUtils.equals(oldName, newName)) { + return R.error("新名称不能与旧名称相同"); + } + kgGraphService.renameKGDomain(oldName, newName); + return R.success(null, "重命名成功"); + } +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/controller/IntelligentSearchController.java b/kgBuilder-pro/src/main/java/com/warmer/web/controller/IntelligentSearchController.java new file mode 100644 index 0000000..1ae8c58 --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/controller/IntelligentSearchController.java @@ -0,0 +1,146 @@ +package com.warmer.web.controller; + +import com.warmer.base.util.R; +import com.warmer.web.request.IntelligentSearchRequest; +import com.warmer.web.service.IntelligentSearchService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; +import java.util.List; + +/** + * 智能搜索控制器 + * + * @author tanc + */ +@RestController +@RequestMapping("/intelligent") +@CrossOrigin(origins = "*", maxAge = 3600) +public class IntelligentSearchController { + + @Autowired + private IntelligentSearchService intelligentSearchService; + + /** + * 智能搜索接口 + * + * @param request 搜索请求对象 + * @return 搜索结果 + */ + @PostMapping("/search") + public R> search(@RequestBody IntelligentSearchRequest request) { + try { + // 参数验证 + if (StringUtils.isEmpty(request.getText())) { + return R.error("搜索文本不能为空"); + } + + if (StringUtils.isEmpty(request.getDomain())) { + request.setDomain("default"); + } + + Map result = intelligentSearchService.intelligentSearch( + request.getText(), + request.getDomain() + ); + return R.success(result); + } catch (Exception e) { + return R.error("智能搜索失败: " + e.getMessage()); + } + } + + /** + * 仅通过文本搜索(使用默认领域) + * + * @param text 搜索文本 + * @return 搜索结果 + */ + @PostMapping("/searchByText") + public R> searchByText(@RequestParam String text) { + try { + if (StringUtils.isEmpty(text)) { + return R.error("搜索文本不能为空"); + } + + // 使用默认领域进行搜索 + Map result = intelligentSearchService.intelligentSearch(text, "default"); + return R.success(result); + } catch (Exception e) { + return R.error("智能搜索失败: " + e.getMessage()); + } + } + + /** + * 简单的GET接口用于测试 + * + * @param text 搜索文本 + * @param domain 领域名称(可选) + * @return 搜索结果 + */ + @GetMapping("/search") + public R> searchGet(@RequestParam String text, + @RequestParam(required = false, defaultValue = "default") String domain) { + try { + if (StringUtils.isEmpty(text)) { + return R.error("搜索文本不能为空"); + } + + Map result = intelligentSearchService.intelligentSearch(text, domain); + return R.success(result); + } catch (Exception e) { + return R.error("智能搜索失败: " + e.getMessage()); + } + } + + /** + * 关键词提取接口 + * + * @param requestBody 包含原始文本的请求体 + * @return 提取的关键词列表 + */ + @PostMapping("/extractKeywords") + public R extractKeywords(@RequestBody Map requestBody) { + try { + String text = requestBody.get("text"); + if (StringUtils.isEmpty(text)) { + return R.error("输入文本不能为空"); + } + + // 使用关键词提取服务 + List keywordInfoList = + intelligentSearchService.getKeywordExtractionResult(text); + return R.success(keywordInfoList); + } catch (Exception e) { + return R.error("关键词提取失败: " + e.getMessage()); + } + } + + /** + * 获取搜索建议(输入提示) + * + * @param query 查询关键词 + * @param domain 领域名称 + * @return 节点名称建议列表 + */ + @GetMapping("/suggestions") + public R> getSuggestions(@RequestParam String query, + @RequestParam(required = false) String domain) { + try { + if (query == null || query.trim().isEmpty()) { + return R.success(new java.util.ArrayList<>()); + } + + // 如果没有指定领域,使用默认领域 + if (domain == null || domain.trim().isEmpty()) { + domain = "default"; + } + + List suggestions = intelligentSearchService.getSearchSuggestions(query, domain); + return R.success(suggestions); + } catch (Exception e) { + return R.error("获取搜索建议失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/controller/KGBuilderController.java b/kgBuilder-pro/src/main/java/com/warmer/web/controller/KGBuilderController.java index df219b0..aa1e33e 100644 --- a/kgBuilder-pro/src/main/java/com/warmer/web/controller/KGBuilderController.java +++ b/kgBuilder-pro/src/main/java/com/warmer/web/controller/KGBuilderController.java @@ -16,6 +16,7 @@ import com.warmer.web.entity.KgNodeDetailFile; import com.warmer.web.model.NodeItem; import com.warmer.web.request.*; import com.warmer.web.service.FeedBackService; +import com.warmer.web.service.IntelligentSearchService; import com.warmer.web.service.KGGraphService; import com.warmer.web.service.KGManagerService; import org.springframework.beans.factory.annotation.Autowired; @@ -40,6 +41,8 @@ public class KGBuilderController extends BaseController { private KGManagerService kgManagerService; @Autowired FeedBackService feedBackService; + @Autowired + private IntelligentSearchService intelligentSearchService; /** * 获取图谱标签列表(存放mysql表) @@ -75,7 +78,19 @@ public class KGBuilderController extends BaseController { @PostMapping(value = "/queryGraphResult") public R> queryGraphResult(@RequestBody GraphQuery query) { try { + // 如果启用了智能搜索,先处理关键字提取 + if (query.getEnableKeywordExtraction() != null && query.getEnableKeywordExtraction()) { + query = intelligentSearchService.processIntelligentSearch(query); + } + HashMap graphData = kgGraphService.queryGraphResult(query); + + // 如果是智能搜索,添加关键字信息到返回结果 + if (query.getEnableKeywordExtraction() != null && query.getEnableKeywordExtraction() && + query.getKeywords() != null) { + graphData.put("extractedKeywords", query.getKeywords()); + } + return R.success(graphData); } catch (Exception e) { e.printStackTrace(); diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/dao/CustomDictDao.java b/kgBuilder-pro/src/main/java/com/warmer/web/dao/CustomDictDao.java new file mode 100644 index 0000000..e3225aa --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/dao/CustomDictDao.java @@ -0,0 +1,15 @@ +package com.warmer.web.dao; + +import com.warmer.web.model.CustomDictEntry; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface CustomDictDao { + List list(@Param("domain") String domain, @Param("enabled") Integer enabled); + int insert(CustomDictEntry entry); + int disable(@Param("id") Integer id); + CustomDictEntry getByWord(@Param("word") String word, @Param("domain") String domain); +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/dao/KGGraphDao.java b/kgBuilder-pro/src/main/java/com/warmer/web/dao/KGGraphDao.java index 8edb50f..0c70e32 100644 --- a/kgBuilder-pro/src/main/java/com/warmer/web/dao/KGGraphDao.java +++ b/kgBuilder-pro/src/main/java/com/warmer/web/dao/KGGraphDao.java @@ -1,9 +1,8 @@ package com.warmer.web.dao; - +import com.warmer.base.util.GraphPageRecord; import com.warmer.web.model.NodeItem; import com.warmer.web.request.GraphQuery; -import com.warmer.base.util.GraphPageRecord; import com.warmer.web.request.NodeCoordinateItem; import org.apache.ibatis.annotations.Mapper; @@ -11,232 +10,233 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - @Mapper public interface KGGraphDao { - /** - * 领域标签分页 - * @param queryItem - * @return - */ - GraphPageRecord> getPageDomain(GraphQuery queryItem); - /** - * 删除Neo4j 标签 - * - * @param domain - */ - void deleteKgDomain(String domain); + /** + * 领域标签分页 + * @param queryItem + * @return + */ + GraphPageRecord> getPageDomain(GraphQuery queryItem); + /** + * 删除Neo4j 标签 + * + * @param domain + */ + void deleteKgDomain(String domain); - /** - * 查询图谱节点和关系 - * - * @param query - * @return node relationship - */ - HashMap queryGraphResult(GraphQuery query); + /** + * 查询图谱节点和关系 + * + * @param query + * @return node relationship + */ + HashMap queryGraphResult(GraphQuery query); - /** - * 获取节点列表 - * - * @param domain - * @param pageIndex - * @param pageSize - * @return - */ - HashMap getDomainNodes(String domain, Integer pageIndex, Integer pageSize); + /** + * 获取节点列表 + * + * @param domain + * @param pageIndex + * @param pageSize + * @return + */ + HashMap getDomainNodes(String domain, Integer pageIndex, Integer pageSize); - /** - * 获取某个领域指定节点拥有的上下级的节点数 - * - * @param domain - * @param nodeId - * @return long 数值 - */ - long getRelationNodeCount(String domain, long nodeId); + /** + * 获取某个领域指定节点拥有的上下级的节点数 + * + * @param domain + * @param nodeId + * @return long 数值 + */ + long getRelationNodeCount(String domain, long nodeId); - /** - * 创建领域,默认创建一个新的节点,给节点附上默认属性 - * - * @param domain - */ - void createDomain(String domain); - void quickCreateDomain(String domain,String nodeName); - /** - * 获取/展开更多节点,找到和该节点有关系的节点 - * - * @param domain - * @param nodeId - * @return - */ - HashMap getMoreRelationNode(String domain, String nodeId); + /** + * 创建领域,默认创建一个新的节点,给节点附上默认属性 + * + * @param domain + */ + void createDomain(String domain); + void quickCreateDomain(String domain,String nodeName); + /** + * 获取/展开更多节点,找到和该节点有关系的节点 + * + * @param domain + * @param nodeId + * @return + */ + HashMap getMoreRelationNode(String domain, String nodeId); - /** - * 更新节点名称 - * - * @param domain - * @param nodeId - * @param nodeName - * @return 修改后的节点 - */ - HashMap updateNodeName(String domain, String nodeId, String nodeName); + /** + * 更新节点名称 + * + * @param domain + * @param nodeId + * @param nodeName + * @return 修改后的节点 + */ + HashMap updateNodeName(String domain, String nodeId, String nodeName); - /** - * 创建单个节点 - * - * @param domain - * @param entity - * @return - */ - HashMap createNode(String domain, NodeItem entity); - HashMap createNodeWithUUid(String domain, NodeItem entity); + /** + * 创建单个节点 + * + * @param domain + * @param entity + * @return + */ + HashMap createNode(String domain, NodeItem entity); + HashMap createNodeWithUUid(String domain, NodeItem entity); - /** - * 批量创建节点和关系 - * - * @param domain - * 领域 - * @param sourceName - * 源节点 - * @param relation - * 关系 - * @param targetNames - * 目标节点数组 - * @return - */ - HashMap batchCreateNode(String domain, String sourceName, String relation, String[] targetNames); + /** + * 批量创建节点和关系 + * + * @param domain + * 领域 + * @param sourceName + * 源节点 + * @param relation + * 关系 + * @param targetNames + * 目标节点数组 + * @return + */ + HashMap batchCreateNode(String domain, String sourceName, String relation, String[] targetNames); - /** - * 批量创建下级节点 - * - * @param domain - * 领域 - * @param sourceId - * 源节点id - * @param entityType - * 节点类型 - * @param targetNames - * 目标节点名称数组 - * @param relation - * 关系 - * @return - */ - HashMap batchCreateChildNode(String domain, String sourceId, Integer entityType, - String[] targetNames, String relation); + /** + * 批量创建下级节点 + * + * @param domain + * 领域 + * @param sourceId + * 源节点id + * @param entityType + * 节点类型 + * @param targetNames + * 目标节点名称数组 + * @param relation + * 关系 + * @return + */ + HashMap batchCreateChildNode(String domain, String sourceId, Integer entityType, + String[] targetNames, String relation); - /** - * 批量创建同级节点 - * - * @param domain - * 领域 - * @param entityType - * 节点类型 - * @param sourceNames - * 节点名称 - * @return - */ - List> batchCreateSameNode(String domain, Integer entityType, String[] sourceNames); + /** + * 批量创建同级节点 + * + * @param domain + * 领域 + * @param entityType + * 节点类型 + * @param sourceNames + * 节点名称 + * @return + */ + List> batchCreateSameNode(String domain, Integer entityType, String[] sourceNames); - /** - * 添加关系 - * - * @param domain - * 领域 - * @param sourceId - * 源节点id - * @param targetId - * 目标节点id - * @param ship - * 关系 - * @return - */ - HashMap createLink(String domain, long sourceId, long targetId, String ship); - HashMap createLinkByUuid(String domain, long sourceId, long targetId, String ship); + /** + * 添加关系 + * + * @param domain + * 领域 + * @param sourceId + * 源节点id + * @param targetId + * 目标节点id + * @param ship + * 关系 + * @return + */ + HashMap createLink(String domain, long sourceId, long targetId, String ship); + HashMap createLinkByUuid(String domain, long sourceId, long targetId, String ship); - /** - * 更新关系 - * - * @param domain - * 领域 - * @param shipId - * 关系id - * @param shipName - * 关系名称 - * @return - */ - HashMap updateLink(String domain, long shipId, String shipName); + /** + * 更新关系 + * + * @param domain + * 领域 + * @param shipId + * 关系id + * @param shipName + * 关系名称 + * @return + */ + HashMap updateLink(String domain, long shipId, String shipName); - /** - * 删除节点(先删除关系再删除节点) - * - * @param domain - * @param nodeId - * @return - */ - List> deleteNode(String domain, long nodeId); + /** + * 删除节点(先删除关系再删除节点) + * + * @param domain + * @param nodeId + * @return + */ + List> deleteNode(String domain, long nodeId); - /** - * 删除关系 - * - * @param domain - * @param shipId - */ - void deleteLink(String domain, long shipId); + /** + * 删除关系 + * + * @param domain + * @param shipId + */ + void deleteLink(String domain, long shipId); - /** - * 段落识别出的三元组生成图谱 - * - * @param domain - * @param entityType - * @param operateType - * @param sourceId - * @param rss - * 关系三元组 - * [[startname;ship;endname],[startname1;ship1;endname1],[startname2;ship2;endname2]] - * @return node relationship - */ - HashMap createGraphByText(String domain, Integer entityType, Integer operateType, Integer sourceId, - String[] rss); - /** - * 批量创建节点,关系 - * @param domain - * @param params 三元组 sourceNode,relationship,targetNode - */ - void batchCreateGraph(String domain, List> params); + /** + * 段落识别出的三元组生成图谱 + * + * @param domain + * @param entityType + * @param operateType + * @param sourceId + * @param rss + * 关系三元组 + * [[startname;ship;endname],[startname1;ship1;endname1],[startname2;ship2;endname2]] + * @return node relationship + */ + HashMap createGraphByText(String domain, Integer entityType, Integer operateType, Integer sourceId, + String[] rss); + /** + * 批量创建节点,关系 + * @param domain + * @param params 三元组 sourceNode,relationship,targetNode + */ + void batchCreateGraph(String domain, List> params); - /** - * 批量更新节点坐标 - * @param domain - * @param params - */ - void batchUpdateGraphNodesCoordinate(String domain,List params); - /** - * 更新节点有无附件 - * @param domain - * @param nodeId - * @param status - */ - void updateNodeFileStatus(String domain,long nodeId, int status); + /** + * 批量更新节点坐标 + * @param domain + * @param params + */ + void batchUpdateGraphNodesCoordinate(String domain,List params); + /** + * 更新节点有无附件 + * @param domain + * @param nodeId + * @param status + */ + void updateNodeFileStatus(String domain,long nodeId, int status); - /** - * 更新图谱节点的图片 - * @param domain - * @param nodeId - * @param img - */ - void updateNodeImg(String domain, long nodeId, String img); + /** + * 更新图谱节点的图片 + * @param domain + * @param nodeId + * @param img + */ + void updateNodeImg(String domain, long nodeId, String img); - /** - * 移除节点图片 - * @param domain - * @param nodeId - */ - void removeNodeImg(String domain, long nodeId); - /** - * 导入csv - * @param domain - * @param csvUrl - * @param status - */ - void batchInsertByCsv(String domain, String csvUrl, int status) ; - void updateCoordinateOfNode(String domain, String uuid, Double fx, Double fy); + /** + * 移除节点图片 + * @param domain + * @param nodeId + */ + void removeNodeImg(String domain, long nodeId); + /** + * 导入csv + * @param domain + * @param csvUrl + * @param status + */ + void batchInsertByCsv(String domain, String csvUrl, int status) ; + void updateCoordinateOfNode(String domain, String uuid, Double fx, Double fy); + // 新增:重命名领域标签 + void renameKgDomain(String oldDomain, String newDomain); } diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/dao/impl/KGGraphRepository.java b/kgBuilder-pro/src/main/java/com/warmer/web/dao/impl/KGGraphRepository.java index 0480534..de7ef3d 100644 --- a/kgBuilder-pro/src/main/java/com/warmer/web/dao/impl/KGGraphRepository.java +++ b/kgBuilder-pro/src/main/java/com/warmer/web/dao/impl/KGGraphRepository.java @@ -19,32 +19,50 @@ import java.util.Map; @Repository public class KGGraphRepository implements KGGraphDao { /** - * 领域标签分页 + * 领域标签分页(支持按关键字过滤) */ @Override public GraphPageRecord> getPageDomain(GraphQuery queryItem) { GraphPageRecord> resultRecord = new GraphPageRecord>(); try { - String totalCountQuery = "MATCH (n) RETURN count(distinct labels(n)) as count"; - long totalCount = 0; - totalCount = Neo4jUtil.getGraphValue(totalCountQuery); - if (totalCount > 0) { - int skipCount = (queryItem.getPageIndex() - 1) * queryItem.getPageSize(); - int limitCount = queryItem.getPageSize(); - String domainSql = String.format( - "START n=node(*) RETURN distinct labels(n) as domain,count(n) as nodeCount order by nodeCount desc SKIP %s LIMIT %s", - skipCount, limitCount); - List> pageList = Neo4jUtil.getGraphNode(domainSql); - resultRecord.setPageIndex(queryItem.getPageIndex()); - resultRecord.setPageSize(queryItem.getPageSize()); - resultRecord.setTotalCount(totalCount); - resultRecord.setNodeList(pageList); + String[] keywords = queryItem.getKeywords(); + String whereClause = ""; + if (keywords != null && keywords.length > 0) { + List conds = new ArrayList<>(); + for (String kw : keywords) { + if (!StringUtil.isBlank(kw)) { + conds.add(String.format("domain CONTAINS '%s'", kw)); + } + } + if (conds.size() > 0) { + whereClause = "WHERE " + String.join(" OR ", conds); + } } + // total count of distinct labels, with optional filter + String totalCountQuery = String.format( + "MATCH (n) UNWIND labels(n) AS label WITH DISTINCT label AS domain %s RETURN count(domain) AS count", + whereClause + ); + long totalCount = Neo4jUtil.getGraphValue(totalCountQuery); + int skipCount = (queryItem.getPageIndex() - 1) * queryItem.getPageSize(); + int limitCount = queryItem.getPageSize(); + + // page query: domain label + node count, ordered desc + String domainSql = String.format( + "MATCH (n) UNWIND labels(n) AS label WITH label AS domain, n %s " + + "RETURN domain, count(n) AS nodeCount ORDER BY nodeCount DESC SKIP %s LIMIT %s", + whereClause, skipCount, limitCount + ); + List> pageList = Neo4jUtil.getGraphNode(domainSql); + + resultRecord.setPageIndex(queryItem.getPageIndex()); + resultRecord.setPageSize(queryItem.getPageSize()); + resultRecord.setTotalCount(totalCount); + resultRecord.setNodeList(pageList); } catch (Exception e) { e.printStackTrace(); } - return resultRecord; } @@ -60,7 +78,6 @@ public class KGGraphRepository implements KGGraphDao { Neo4jUtil.runCypherSql(deleteNode); } catch (Exception e) { e.printStackTrace(); - } } @@ -723,4 +740,15 @@ public class KGGraphRepository implements KGGraphDao { } Neo4jUtil.runCypherSql(cypher); } + @Override + public void renameKgDomain(String oldDomain, String newDomain) { + try { + String addLabel = String.format("MATCH (n:`%s`) SET n:`%s`", oldDomain, newDomain); + Neo4jUtil.runCypherSql(addLabel); + String removeLabel = String.format("MATCH (n:`%s`) REMOVE n:`%s`", oldDomain, oldDomain); + Neo4jUtil.runCypherSql(removeLabel); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/model/CustomDictEntry.java b/kgBuilder-pro/src/main/java/com/warmer/web/model/CustomDictEntry.java new file mode 100644 index 0000000..35f1d5f --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/model/CustomDictEntry.java @@ -0,0 +1,84 @@ +package com.warmer.web.model; + +public class CustomDictEntry { + private Integer id; + private String word; + private String nature; + private Integer freq; + private String domain; + private Boolean enabled; + private String source; + private java.sql.Timestamp createdAt; + private java.sql.Timestamp updatedAt; + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getWord() { + return word; + } + + public void setWord(String word) { + this.word = word; + } + + public String getNature() { + return nature; + } + + public void setNature(String nature) { + this.nature = nature; + } + + public Integer getFreq() { + return freq; + } + + public void setFreq(Integer freq) { + this.freq = freq; + } + + public String getDomain() { + return domain; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public java.sql.Timestamp getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(java.sql.Timestamp createdAt) { + this.createdAt = createdAt; + } + + public java.sql.Timestamp getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(java.sql.Timestamp updatedAt) { + this.updatedAt = updatedAt; + } +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/request/GraphQuery.java b/kgBuilder-pro/src/main/java/com/warmer/web/request/GraphQuery.java index f079b60..0429b3c 100644 --- a/kgBuilder-pro/src/main/java/com/warmer/web/request/GraphQuery.java +++ b/kgBuilder-pro/src/main/java/com/warmer/web/request/GraphQuery.java @@ -14,4 +14,10 @@ public class GraphQuery{ private int matchType; private int pageSize = 10; private int pageIndex = 1; + + // 新增字段:支持智能搜索 + private String originalText; // 原始故障描述文本 + private String[] keywords; // 提取的关键字数组 + private Integer searchMode; // 搜索模式:0=传统单关键字,1=智能多关键字,2=语义搜索 + private Boolean enableKeywordExtraction; // 是否启用关键字提取 } diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/request/IntelligentSearchRequest.java b/kgBuilder-pro/src/main/java/com/warmer/web/request/IntelligentSearchRequest.java new file mode 100644 index 0000000..8ad47f9 --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/request/IntelligentSearchRequest.java @@ -0,0 +1,42 @@ +package com.warmer.web.request; + +import lombok.Data; + +/** + * 智能搜索请求对象 + * + * @author tanc + */ +@Data +public class IntelligentSearchRequest { + + /** + * 输入的文本内容 + */ + private String text; + + /** + * 搜索的领域/图谱名称 + */ + private String domain; + + /** + * 搜索模式:0=关键词匹配,1=模糊匹配,2=语义搜索 + */ + private Integer searchMode = 0; + + /** + * 是否返回关键词提取结果 + */ + private Boolean includeKeywords = true; + + /** + * 最大返回结果数量 + */ + private Integer maxResults = 20; + + /** + * 是否包含关系信息 + */ + private Boolean includeRelations = true; +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/service/CustomDictService.java b/kgBuilder-pro/src/main/java/com/warmer/web/service/CustomDictService.java new file mode 100644 index 0000000..0a2c9a1 --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/service/CustomDictService.java @@ -0,0 +1,14 @@ +package com.warmer.web.service; + +import com.warmer.web.model.CustomDictEntry; + +import java.util.List; + +public interface CustomDictService { + void loadAllToHanLPIfNeeded(); + void refreshDomainToHanLP(String domain); + List list(String domain, Integer enabled); + boolean addWord(String word, String nature, Integer freq, String domain, String source); + boolean disable(Integer id); + boolean exists(String word, String domain); +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/service/IntelligentSearchService.java b/kgBuilder-pro/src/main/java/com/warmer/web/service/IntelligentSearchService.java new file mode 100644 index 0000000..fb0a2ca --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/service/IntelligentSearchService.java @@ -0,0 +1,96 @@ +package com.warmer.web.service; + +import com.warmer.base.service.KeywordExtractionService; +import com.warmer.web.request.GraphQuery; + +import java.util.*; + +/** + * 智能搜索服务接口 + * + * @author tanc + */ +public interface IntelligentSearchService { + + /** + * 智能搜索 - 基于文本直接搜索知识图谱 + * + * @param text 输入文本 + * @param domain 搜索领域 + * @return 搜索结果 + */ + Map intelligentSearch(String text, String domain); + + /** + * 根据关键词搜索节点 + * + * @param keyword 关键词 + * @param domain 搜索领域 + * @return 搜索结果 + */ + Map searchNodesByKeyword(String keyword, String domain); + + /** + * 创建空的搜索结果 + * + * @return 空的搜索结果 + */ + Map createEmptySearchResult(); + + /** + * 去除重复的节点 + * + * @param nodes 节点列表 + * @return 去重后的节点列表 + */ + List> removeDuplicateNodes(List> nodes); + + /** + * 去除重复的关系 + * + * @param relations 关系列表 + * @return 去重后的关系列表 + */ + List> removeDuplicateRelations(List> relations); + + /** + * 处理智能搜索请求 + * + * @param query 搜索请求 + * @return 处理后的搜索请求 + */ + GraphQuery processIntelligentSearch(GraphQuery query); + + /** + * 获取关键字提取结果(用于前端显示) + * + * @param text 原始文本 + * @return 关键字信息列表 + */ + List getKeywordExtractionResult(String text); + + /** + * 获取关键词提取结果 + * + * @param text 输入文本 + * @return 关键词提取结果 + */ + List getKeywordExtractionResultMap(String text); + + /** + * 获取搜索建议 + * + * @param query 查询关键词 + * @param domain 领域名称 + * @return 节点名称建议列表 + */ + List getSearchSuggestions(String query, String domain); + + /** + * 提取关键词(简单版本) + * + * @param text 原始文本 + * @return 关键词列表 + */ + List extractKeywords(String text); +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/service/KGGraphService.java b/kgBuilder-pro/src/main/java/com/warmer/web/service/KGGraphService.java index 0a13f21..3b60ddd 100644 --- a/kgBuilder-pro/src/main/java/com/warmer/web/service/KGGraphService.java +++ b/kgBuilder-pro/src/main/java/com/warmer/web/service/KGGraphService.java @@ -24,6 +24,8 @@ public interface KGGraphService { * @param domain */ void deleteKGDomain(String domain); + // 新增:重命名领域标签 + void renameKGDomain(String oldDomain, String newDomain); /** * 查询图谱节点和关系 diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/CustomDictServiceImpl.java b/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/CustomDictServiceImpl.java new file mode 100644 index 0000000..01284d2 --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/CustomDictServiceImpl.java @@ -0,0 +1,76 @@ +package com.warmer.web.service.impl; + +import com.hankcs.hanlp.dictionary.CustomDictionary; +import com.warmer.web.dao.CustomDictDao; +import com.warmer.web.model.CustomDictEntry; +import com.warmer.web.service.CustomDictService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +@Service +public class CustomDictServiceImpl implements CustomDictService { + @Autowired + private CustomDictDao customDictDao; + + private final AtomicBoolean loaded = new AtomicBoolean(false); + + @Override + public void loadAllToHanLPIfNeeded() { + if (loaded.compareAndSet(false, true)) { + List all = customDictDao.list(null, 1); + for (CustomDictEntry e : all) { + try { + CustomDictionary.add(e.getWord(), (e.getNature() == null ? "n" : e.getNature()) + " " + (e.getFreq() == null ? 1000 : e.getFreq())); + } catch (Exception ignored) {} + } + } + } + + @Override + public void refreshDomainToHanLP(String domain) { + List items = customDictDao.list(domain, 1); + for (CustomDictEntry e : items) { + try { + CustomDictionary.add(e.getWord(), (e.getNature() == null ? "n" : e.getNature()) + " " + (e.getFreq() == null ? 1000 : e.getFreq())); + } catch (Exception ignored) {} + } + } + + @Override + public List list(String domain, Integer enabled) { + return customDictDao.list(domain, enabled); + } + + @Override + public boolean addWord(String word, String nature, Integer freq, String domain, String source) { + if (exists(word, domain)) return true; + CustomDictEntry e = new CustomDictEntry(); + e.setWord(word); + e.setNature(nature == null ? "n" : nature); + e.setFreq(freq == null ? 1000 : freq); + e.setDomain(domain); + e.setEnabled(true); + e.setSource(source == null ? "manual" : source); + int ret = customDictDao.insert(e); + if (ret > 0) { + try { + CustomDictionary.add(e.getWord(), e.getNature() + " " + e.getFreq()); + } catch (Exception ignored) {} + return true; + } + return false; + } + + @Override + public boolean disable(Integer id) { + return customDictDao.disable(id) > 0; + } + + @Override + public boolean exists(String word, String domain) { + return customDictDao.getByWord(word, domain) != null; + } +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/IntelligentSearchServiceImpl.java b/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/IntelligentSearchServiceImpl.java new file mode 100644 index 0000000..59ffe65 --- /dev/null +++ b/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/IntelligentSearchServiceImpl.java @@ -0,0 +1,323 @@ +package com.warmer.web.service.impl; + +import com.warmer.base.service.KeywordExtractionService; +import com.warmer.base.util.Neo4jUtil; +import com.warmer.web.request.GraphQuery; +import com.warmer.web.service.IntelligentSearchService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 智能搜索服务实现类 + * + * @author tanc + */ +@Service +public class IntelligentSearchServiceImpl implements IntelligentSearchService { + + @Autowired + private KeywordExtractionService keywordExtractionService; + @Autowired + private com.warmer.web.service.CustomDictService customDictService; + + @Override + public Map intelligentSearch(String text, String domain) { + Map result = new HashMap<>(); + + try { + // 1. 关键词提取 + List keywords = extractKeywords(text); + + // 2. 在知识图谱中搜索匹配的节点和关系 + Map searchResult = searchInKnowledgeGraph(keywords, domain); + + // 3. 整合结果 + result.put("success", true); + result.put("originalText", text); + result.put("domain", domain); + result.put("extractedKeywords", keywords); + result.put("nodes", searchResult.get("nodes")); + result.put("links", searchResult.get("relationships")); // 前端期望的字段名 + result.put("relationships", searchResult.get("relationships")); // 保持兼容性 + result.put("matchedCount", searchResult.get("matchedCount")); + + } catch (Exception e) { + result.put("success", false); + result.put("message", "搜索过程中发生错误: " + e.getMessage()); + result.put("originalText", text); + result.put("domain", domain); + result.put("nodes", new ArrayList<>()); + result.put("links", new ArrayList<>()); + result.put("relationships", new ArrayList<>()); + } + + return result; + } + + + + /** + * 判断是否为停用词 + */ + private boolean isStopWord(String word) { + Set stopWords = new HashSet<>(Arrays.asList( + "的", "了", "在", "是", "我", "有", "和", "就", "不", "人", "都", "一", "一个", "上", "也", "很", "到", "说", "要", "去", "你", "会", "着", "没有", "看", "好", "自己", "这", + "the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "is", "are", "was", "were", "be", "been", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should" + )); + return stopWords.contains(word.toLowerCase()); + } + + /** + * 在知识图谱中搜索匹配的节点和关系 + */ + private Map searchInKnowledgeGraph(List keywords, String domain) { + Map result = new HashMap<>(); + List> allNodes = new ArrayList<>(); + List> allRelationships = new ArrayList<>(); + Set processedNodeIds = new HashSet<>(); + int matchedCount = 0; + + if (keywords.isEmpty() || StringUtils.isEmpty(domain)) { + result.put("nodes", allNodes); + result.put("relationships", allRelationships); + result.put("matchedCount", matchedCount); + return result; + } + + try { + // 为每个关键词搜索匹配的节点 + for (String keyword : keywords) { + // 构建Cypher查询语句,使用CONTAINS进行模糊匹配 + String cypherSql = String.format( + "MATCH (n:`%s`) WHERE n.name CONTAINS '%s' " + + "OPTIONAL MATCH (n)-[r]-(m:`%s`) " + + "RETURN n, r, m LIMIT 10", + domain, keyword, domain + ); + + // 执行查询 + HashMap searchResult = Neo4jUtil.getGraphNodeAndShip(cypherSql); + + if (searchResult != null) { + // 处理节点 - Neo4jUtil返回的字段是 "node" + @SuppressWarnings("unchecked") + List> nodes = (List>) searchResult.get("node"); + if (nodes != null) { + for (HashMap node : nodes) { + String nodeId = String.valueOf(node.get("uuid")); + if (!processedNodeIds.contains(nodeId)) { + allNodes.add(node); + processedNodeIds.add(nodeId); + matchedCount++; + } + } + } + + // 处理关系 - Neo4jUtil返回的字段是 "relationship" + @SuppressWarnings("unchecked") + List> relationships = (List>) searchResult.get("relationship"); + if (relationships != null) { + allRelationships.addAll(relationships); + } + } + } + + // 如果没有找到匹配的节点,尝试更宽泛的搜索 + if (allNodes.isEmpty() && !keywords.isEmpty()) { + // 搜索包含任意关键词的节点 + String keywordPattern = String.join("|", keywords); + String cypherSql = String.format( + "MATCH (n:`%s`) WHERE n.name =~ '.*(%s).*' " + + "RETURN n LIMIT 20", + domain, keywordPattern + ); + + List> nodes = Neo4jUtil.getGraphNode(cypherSql); + if (nodes != null) { + allNodes.addAll(nodes); + matchedCount = nodes.size(); + } + } + + } catch (Exception e) { + // 记录错误但不抛出异常,返回空结果 + System.err.println("搜索知识图谱时发生错误: " + e.getMessage()); + } + + // 去重关系 + allRelationships = allRelationships.stream() + .collect(Collectors.toMap( + rel -> rel.get("uuid"), + rel -> rel, + (existing, replacement) -> existing + )) + .values() + .stream() + .collect(Collectors.toList()); + + // 确保返回空列表而不是null + result.put("nodes", allNodes != null ? allNodes : new ArrayList<>()); + result.put("relationships", allRelationships != null ? allRelationships : new ArrayList<>()); + result.put("matchedCount", matchedCount); + + return result; + } + + @Override + public Map searchNodesByKeyword(String keyword, String domain) { + try { + // 如果没有指定领域,尝试在所有领域中搜索 + if (domain == null || domain.trim().isEmpty()) { + // 可以在这里实现跨领域搜索逻辑 + // 暂时返回空结果 + return createEmptySearchResult(); + } + + // 构建Cypher查询语句,使用CONTAINS进行模糊匹配 + String cypherSql = String.format( + "MATCH (n:`%s`) WHERE n.name CONTAINS '%s' " + + "OPTIONAL MATCH (n)-[r]-(m:`%s`) " + + "RETURN n, r, m LIMIT 10", + domain, keyword, domain + ); + + // 执行查询 + return Neo4jUtil.getGraphNodeAndShip(cypherSql); + } catch (Exception e) { + System.err.println("搜索节点时出错: " + e.getMessage()); + return createEmptySearchResult(); + } + } + + @Override + public Map createEmptySearchResult() { + Map result = new HashMap<>(); + result.put("nodes", new ArrayList<>()); + result.put("relationships", new ArrayList<>()); + return result; + } + + @Override + public List> removeDuplicateNodes(List> nodes) { + Map> uniqueNodes = new HashMap<>(); + for (Map node : nodes) { + String nodeId = String.valueOf(node.get("uuid")); + if (nodeId != null && !uniqueNodes.containsKey(nodeId)) { + uniqueNodes.put(nodeId, node); + } + } + return new ArrayList<>(uniqueNodes.values()); + } + + @Override + public List> removeDuplicateRelations(List> relations) { + Map> uniqueRelations = new HashMap<>(); + for (Map relation : relations) { + String relationKey = relation.get("sourceId") + "-" + relation.get("targetId") + "-" + relation.get("relation"); + if (!uniqueRelations.containsKey(relationKey)) { + uniqueRelations.put(relationKey, relation); + } + } + return new ArrayList<>(uniqueRelations.values()); + } + + @Override + public GraphQuery processIntelligentSearch(GraphQuery query) { + // 处理智能搜索请求的逻辑 + if (query != null && query.getOriginalText() != null) { + // 提取关键词并设置到查询对象中 + List keywords = extractKeywords(query.getOriginalText()); + // 设置提取的关键词到查询对象中 + if (keywords != null && !keywords.isEmpty()) { + query.setKeywords(keywords.toArray(new String[0])); + } + } + return query; + } + + @Override + public List getKeywordExtractionResult(String text) { + return keywordExtractionService.extractKeywordsWithInfo(text); + } + + @Override + public List getKeywordExtractionResultMap(String text) { + return keywordExtractionService.extractKeywordsWithInfo(text); + } + + @Override + public List extractKeywords(String text) { + // 保障DB词典已加载到HanLP,避免每次都改代码 + customDictService.loadAllToHanLPIfNeeded(); + return keywordExtractionService.extractKeywords(text); + + /* 本地简单实现(如果不想使用KeywordExtractionService可以使用这个) + if (StringUtils.isEmpty(text)) { + return new ArrayList<>(); + } + + // 移除标点符号和特殊字符,保留中文、英文和数字 + String cleanText = text.replaceAll("[\\p{Punct}\\s]+", " "); + + // 分词(简单按空格分割,实际项目中可以使用jieba等分词工具) + String[] words = cleanText.split("\\s+"); + + List keywords = new ArrayList<>(); + for (String word : words) { + word = word.trim(); + if (!StringUtils.isEmpty(word) && word.length() > 1) { + // 过滤掉常见的停用词 + if (!isStopWord(word)) { + keywords.add(word); + } + } + } + + // 去重并返回 + return keywords.stream().distinct().collect(Collectors.toList()); + */ + } + + @Override + public List getSearchSuggestions(String query, String domain) { + List suggestions = new ArrayList<>(); + + try { + if (query == null || query.trim().isEmpty() || domain == null || domain.trim().isEmpty()) { + return suggestions; + } + + // 构建Cypher查询,获取包含查询关键词的节点名称 + String cypherSql = String.format( + "MATCH (n:`%s`) WHERE n.name CONTAINS '%s' " + + "RETURN DISTINCT n.name as name LIMIT 10", + domain, query.trim() + ); + + // 执行查询 + List> results = Neo4jUtil.getGraphTable(cypherSql); + + if (results != null && !results.isEmpty()) { + for (HashMap result : results) { + Object nameObj = result.get("name"); + if (nameObj != null) { + String name = nameObj.toString(); + // 移除可能的引号 + name = name.replaceAll("^\"|\"$", ""); + if (!name.isEmpty()) { + suggestions.add(name); + } + } + } + } + } catch (Exception e) { + System.err.println("获取搜索建议时出错: " + e.getMessage()); + } + + return suggestions; + } +} \ No newline at end of file diff --git a/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/KGGraphServiceImpl.java b/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/KGGraphServiceImpl.java index ed71116..8b4904d 100644 --- a/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/KGGraphServiceImpl.java +++ b/kgBuilder-pro/src/main/java/com/warmer/web/service/impl/KGGraphServiceImpl.java @@ -54,7 +54,11 @@ public class KGGraphServiceImpl implements KGGraphService { public void deleteKGDomain(String domain) { kgRepository.deleteKgDomain(domain); } - + // 新增:重命名领域标签 + @Override + public void renameKGDomain(String oldDomain, String newDomain) { + kgRepository.renameKgDomain(oldDomain, newDomain); + } @Override public HashMap queryGraphResult(GraphQuery query) { return kgRepository.queryGraphResult(query); diff --git a/kgBuilder-pro/src/main/resources/hanlp.properties b/kgBuilder-pro/src/main/resources/hanlp.properties index a758d4e..e69de29 100644 --- a/kgBuilder-pro/src/main/resources/hanlp.properties +++ b/kgBuilder-pro/src/main/resources/hanlp.properties @@ -1,40 +0,0 @@ -#本配置文件中的路径的根目录,根目录+其他路径=完整路径(支持相对路径,请参考:https://github.com/hankcs/HanLP/pull/254) -#Windows用户请注意,路径分隔符统一使用/ -root=D:/HanLP/ -#核心词典路径 -CoreDictionaryPath=data/dictionary/CoreNatureDictionary.txt -#2元语法词典路径 -BiGramDictionaryPath=data/dictionary/CoreNatureDictionary.ngram.txt -#停用词词典路径 -CoreStopWordDictionaryPath=data/dictionary/stopwords.txt -#同义词词典路径 -CoreSynonymDictionaryDictionaryPath=data/dictionary/synonym/CoreSynonym.txt -#人名词典路径 -PersonDictionaryPath=data/dictionary/person/nr.txt -#人名词典转移矩阵路径 -PersonDictionaryTrPath=data/dictionary/person/nr.tr.txt -#繁简词典根目录 -tcDictionaryRoot=data/dictionary/tc -#自定义词典路径,用;隔开多个自定义词典,空格开头表示在同一个目录,使用“文件名 词性”形式则表示这个词典的词性默认是该词性。优先级递减。 -#另外data/dictionary/custom/CustomDictionary.txt是个高质量的词库,请不要删除。所有词典统一使用【UTF-8】编码。 -#注意,每次更新自己定义的新词典myDict.txt的内容时,要删除同目录下的词典缓存文件CustomDictionary.txt.bin -CustomDictionaryPath=data/dictionary/custom/CustomDictionary.txt; 现代汉语补充词库.txt; 全国地名大全.txt ns; 人名词典.txt; 机构名词典.txt; 上海地名.txt ns;data/dictionary/person/nrf.txt nrf; - -#CRF分词模型路径 -CRFSegmentModelPath=data/model/segment/CRFSegmentModel.txt -#HMM分词模型 -HMMSegmentModelPath=data/model/segment/HMMSegmentModel.bin -#分词结果是否展示词性 -ShowTermNature=true -#IO适配器,实现com.hankcs.hanlp.corpus.io.IIOAdapter接口以在不同的平台(Hadoop、Redis等)上运行HanLP -#默认的IO适配器如下,该适配器是基于普通文件系统的。 -#IOAdapter=com.hankcs.hanlp.corpus.io.FileIOAdapter -#感知机词法分析器 -PerceptronCWSModelPath=data/model/perceptron/pku199801/cws.bin -PerceptronPOSModelPath=data/model/perceptron/pku199801/pos.bin -PerceptronNERModelPath=data/model/perceptron/pku199801/ner.bin -#CRF词法分析器 -CRFCWSModelPath=data/model/crf/pku199801/cws.bin -CRFPOSModelPath=data/model/crf/pku199801/pos.bin -CRFNERModelPath=data/model/crf/pku199801/ner.bin -#更多配置项请参考 https://github.com/hankcs/HanLP/blob/master/src/main/java/com/hankcs/hanlp/HanLP.java#L59 自行添加 \ No newline at end of file diff --git a/kgBuilder-pro/src/main/resources/mapping/CorpusDao.xml b/kgBuilder-pro/src/main/resources/mapping/CorpusDao.xml deleted file mode 100644 index 351501e..0000000 --- a/kgBuilder-pro/src/main/resources/mapping/CorpusDao.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - INSERT INTO kg_corpus_document ( - domain_id, title, content, document_type, source, status, - create_user, create_time, update_user, update_time - ) VALUES ( - #{domainId}, #{title}, #{content}, #{documentType}, #{source}, #{status}, - #{createUser}, #{createTime}, #{updateUser}, #{updateTime} - ) - - - - INSERT INTO kg_corpus_document ( - domain_id, title, content, document_type, source, status, - create_user, create_time, update_user, update_time - ) VALUES - - (#{doc.domainId}, #{doc.title}, #{doc.content}, #{doc.documentType}, #{doc.source}, #{doc.status}, - #{doc.createUser}, #{doc.createTime}, #{doc.updateUser}, #{doc.updateTime}) - - - - - UPDATE kg_corpus_document SET - title = #{title}, - content = #{content}, - document_type = #{documentType}, - source = #{source}, - status = #{status}, - update_user = #{updateUser}, - update_time = #{updateTime} - WHERE id = #{id} - - - - DELETE FROM kg_corpus_document WHERE id = #{id} - - - - - - - - - - - - - - INSERT INTO kg_keyword_weight ( - domain_id, keyword, weight, category, description, status, - create_user, create_time, update_user, update_time - ) VALUES ( - #{domainId}, #{keyword}, #{weight}, #{category}, #{description}, #{status}, - #{createUser}, #{createTime}, #{updateUser}, #{updateTime} - ) - - - - INSERT INTO kg_keyword_weight ( - domain_id, keyword, weight, category, description, status, - create_user, create_time, update_user, update_time - ) VALUES - - (#{weight.domainId}, #{weight.keyword}, #{weight.weight}, #{weight.category}, #{weight.description}, #{weight.status}, - #{weight.createUser}, #{weight.createTime}, #{weight.updateUser}, #{weight.updateTime}) - - - - - UPDATE kg_keyword_weight SET - weight = #{weight}, - category = #{category}, - description = #{description}, - status = #{status}, - update_user = #{updateUser}, - update_time = #{updateTime} - WHERE id = #{id} - - - - DELETE FROM kg_keyword_weight WHERE id = #{id} - - - - - - - - - - - - - - - - INSERT INTO kg_term_frequency ( - domain_id, term, document_frequency, total_frequency, idf_value, last_update - ) VALUES ( - #{domainId}, #{term}, #{documentFrequency}, #{totalFrequency}, #{idfValue}, #{lastUpdate} - ) - - - - INSERT INTO kg_term_frequency ( - domain_id, term, document_frequency, total_frequency, idf_value, last_update - ) VALUES - - (#{freq.domainId}, #{freq.term}, #{freq.documentFrequency}, #{freq.totalFrequency}, #{freq.idfValue}, #{freq.lastUpdate}) - - - - - UPDATE kg_term_frequency SET - document_frequency = #{documentFrequency}, - total_frequency = #{totalFrequency}, - idf_value = #{idfValue}, - last_update = #{lastUpdate} - WHERE id = #{id} - - - - - UPDATE kg_term_frequency SET - document_frequency = #{freq.documentFrequency}, - total_frequency = #{freq.totalFrequency}, - idf_value = #{freq.idfValue}, - last_update = #{freq.lastUpdate} - WHERE id = #{freq.id} - - - - - DELETE FROM kg_term_frequency WHERE id = #{id} - - - - - \ No newline at end of file diff --git a/kgBuilder-pro/src/main/resources/mapping/CustomDictDao.xml b/kgBuilder-pro/src/main/resources/mapping/CustomDictDao.xml new file mode 100644 index 0000000..27a6b82 --- /dev/null +++ b/kgBuilder-pro/src/main/resources/mapping/CustomDictDao.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + INSERT INTO kg_custom_dict (word, nature, freq, domain, enabled, source) + VALUES (#{word}, #{nature}, #{freq}, #{domain}, #{enabled}, #{source}) + + + + UPDATE kg_custom_dict SET enabled = 0 WHERE id = #{id} + + \ No newline at end of file diff --git a/kgBuilder-pro/src/main/resources/mapping/KnowledgeGraphDao.xml b/kgBuilder-pro/src/main/resources/mapping/KnowledgeGraphDao.xml index 150a95a..b1a1639 100644 --- a/kgBuilder-pro/src/main/resources/mapping/KnowledgeGraphDao.xml +++ b/kgBuilder-pro/src/main/resources/mapping/KnowledgeGraphDao.xml @@ -50,7 +50,7 @@