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 完成批量写入。 *