From 1b36067e6c5bf202f7b13bd7f76f3c32d7f2057a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com> Date: Tue, 1 Sep 2026 15:11:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20Lucene=20=E7=89=B9?= =?UTF-8?q?=E6=AE=8A=E5=AD=97=E7=AC=A6=E6=9F=A5=E8=AF=A2=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将用户关键词按普通文本转义后再解析 - 避免空查询和解析失败触发二次空指针 - 补充特殊字符与空查询回归测试 --- .../search/engine/lucene/LuceneSearcher.java | 38 +++++++++--------- .../engine/lucene/LuceneSearcherTest.java | 39 +++++++++++++++++++ 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/easy-agents-search-engine/easy-agents-search-engine-lucene/src/main/java/com/easyagents/search/engine/lucene/LuceneSearcher.java b/easy-agents-search-engine/easy-agents-search-engine-lucene/src/main/java/com/easyagents/search/engine/lucene/LuceneSearcher.java index 84c4a7c..af22791 100644 --- a/easy-agents-search-engine/easy-agents-search-engine-lucene/src/main/java/com/easyagents/search/engine/lucene/LuceneSearcher.java +++ b/easy-agents-search-engine/easy-agents-search-engine-lucene/src/main/java/com/easyagents/search/engine/lucene/LuceneSearcher.java @@ -199,10 +199,13 @@ public class LuceneSearcher implements DocumentSearcher, AutoCloseable { @Override public List searchDocuments(KeywordSearchRequest request) { List results = new ArrayList<>(); + if (request == null || request.getKeyword() == null || request.getKeyword().trim().isEmpty()) { + return results; + } try (IndexReader reader = DirectoryReader.open(directory)) { IndexSearcher searcher = new IndexSearcher(reader); Query query = buildQuery(request); - TopDocs topDocs = searcher.search(query, request == null ? 10 : request.getCount()); + TopDocs topDocs = searcher.search(query, request.getCount()); for (ScoreDoc scoreDoc : topDocs.scoreDocs) { org.apache.lucene.document.Document doc = searcher.doc(scoreDoc.doc); Document resultDoc = new Document(); @@ -224,29 +227,24 @@ public class LuceneSearcher implements DocumentSearcher, AutoCloseable { return results; } - Query buildQuery(KeywordSearchRequest request) { - try { - String keyword = request == null ? null : request.getKeyword(); + Query buildQuery(KeywordSearchRequest request) throws ParseException { + String escapedKeyword = QueryParser.escape(request.getKeyword()); - QueryParser titleQueryParser = new QueryParser("title", analyzer); - Query titleQuery = titleQueryParser.parse(keyword); - BooleanClause titleBooleanClause = new BooleanClause(titleQuery, BooleanClause.Occur.SHOULD); + QueryParser titleQueryParser = new QueryParser("title", analyzer); + Query titleQuery = titleQueryParser.parse(escapedKeyword); + BooleanClause titleBooleanClause = new BooleanClause(titleQuery, BooleanClause.Occur.SHOULD); - QueryParser contentQueryParser = new QueryParser("content", analyzer); - Query contentQuery = contentQueryParser.parse(keyword); - BooleanClause contentBooleanClause = new BooleanClause(contentQuery, BooleanClause.Occur.SHOULD); + QueryParser contentQueryParser = new QueryParser("content", analyzer); + Query contentQuery = contentQueryParser.parse(escapedKeyword); + BooleanClause contentBooleanClause = new BooleanClause(contentQuery, BooleanClause.Occur.SHOULD); - BooleanQuery.Builder builder = new BooleanQuery.Builder(); - builder.add(titleBooleanClause) - .add(contentBooleanClause); - if (request != null && request.getKnowledgeId() != null && !request.getKnowledgeId().trim().isEmpty()) { - builder.add(new TermQuery(new Term(KeywordSearchMetadataKeys.KNOWLEDGE_ID, request.getKnowledgeId().trim())), BooleanClause.Occur.MUST); - } - return builder.build(); - } catch (ParseException e) { - LOG.error(e.toString(), e); + BooleanQuery.Builder builder = new BooleanQuery.Builder(); + builder.add(titleBooleanClause) + .add(contentBooleanClause); + if (request.getKnowledgeId() != null && !request.getKnowledgeId().trim().isEmpty()) { + builder.add(new TermQuery(new Term(KeywordSearchMetadataKeys.KNOWLEDGE_ID, request.getKnowledgeId().trim())), BooleanClause.Occur.MUST); } - return null; + return builder.build(); } private static Analyzer createAnalyzer() { diff --git a/easy-agents-search-engine/easy-agents-search-engine-lucene/src/test/java/com/easyagents/search/engine/lucene/LuceneSearcherTest.java b/easy-agents-search-engine/easy-agents-search-engine-lucene/src/test/java/com/easyagents/search/engine/lucene/LuceneSearcherTest.java index e7804dc..98e025e 100644 --- a/easy-agents-search-engine/easy-agents-search-engine-lucene/src/test/java/com/easyagents/search/engine/lucene/LuceneSearcherTest.java +++ b/easy-agents-search-engine/easy-agents-search-engine-lucene/src/test/java/com/easyagents/search/engine/lucene/LuceneSearcherTest.java @@ -57,6 +57,45 @@ public class LuceneSearcherTest { } } + /** + * 验证用户输入中的 Lucene 特殊字符按普通文本检索。 + * + * @throws Exception 临时目录或 Lucene 资源操作失败时抛出 + */ + @Test + public void shouldTreatLuceneSpecialCharactersAsPlainText() throws Exception { + Path tempDir = Files.createTempDirectory("lucene-searcher-special-character-test"); + LuceneConfig config = new LuceneConfig(); + config.setIndexDirPath(tempDir.toString()); + try (LuceneSearcher searcher = new LuceneSearcher(config)) { + Document document = new Document(); + document.setId("special-character"); + document.setContent("A/C.\\nPLEASE"); + Assert.assertTrue(searcher.addDocument(document)); + + List results = searcher.searchDocuments("A/C.\\nPLEASE", 10); + + Assert.assertEquals(1, results.size()); + Assert.assertEquals("special-character", String.valueOf(results.get(0).getId())); + } + } + + /** + * 验证空查询直接返回空结果。 + * + * @throws Exception 临时目录或 Lucene 资源操作失败时抛出 + */ + @Test + public void shouldReturnEmptyForMissingKeyword() throws Exception { + Path tempDir = Files.createTempDirectory("lucene-searcher-empty-keyword-test"); + LuceneConfig config = new LuceneConfig(); + config.setIndexDirPath(tempDir.toString()); + try (LuceneSearcher searcher = new LuceneSearcher(config)) { + Assert.assertTrue(searcher.searchDocuments((KeywordSearchRequest) null).isEmpty()); + Assert.assertTrue(searcher.searchDocuments(" ", 10).isEmpty()); + } + } + /** * 验证多个导入线程可共享同一个 IndexWriter 完成批量写入。 *