fix: 修复 Lucene 特殊字符查询失败
- 将用户关键词按普通文本转义后再解析 - 避免空查询和解析失败触发二次空指针 - 补充特殊字符与空查询回归测试
This commit is contained in:
@@ -199,10 +199,13 @@ public class LuceneSearcher implements DocumentSearcher, AutoCloseable {
|
|||||||
@Override
|
@Override
|
||||||
public List<Document> searchDocuments(KeywordSearchRequest request) {
|
public List<Document> searchDocuments(KeywordSearchRequest request) {
|
||||||
List<Document> results = new ArrayList<>();
|
List<Document> results = new ArrayList<>();
|
||||||
|
if (request == null || request.getKeyword() == null || request.getKeyword().trim().isEmpty()) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
try (IndexReader reader = DirectoryReader.open(directory)) {
|
try (IndexReader reader = DirectoryReader.open(directory)) {
|
||||||
IndexSearcher searcher = new IndexSearcher(reader);
|
IndexSearcher searcher = new IndexSearcher(reader);
|
||||||
Query query = buildQuery(request);
|
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) {
|
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
|
||||||
org.apache.lucene.document.Document doc = searcher.doc(scoreDoc.doc);
|
org.apache.lucene.document.Document doc = searcher.doc(scoreDoc.doc);
|
||||||
Document resultDoc = new Document();
|
Document resultDoc = new Document();
|
||||||
@@ -224,29 +227,24 @@ public class LuceneSearcher implements DocumentSearcher, AutoCloseable {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
Query buildQuery(KeywordSearchRequest request) {
|
Query buildQuery(KeywordSearchRequest request) throws ParseException {
|
||||||
try {
|
String escapedKeyword = QueryParser.escape(request.getKeyword());
|
||||||
String keyword = request == null ? null : request.getKeyword();
|
|
||||||
|
|
||||||
QueryParser titleQueryParser = new QueryParser("title", analyzer);
|
QueryParser titleQueryParser = new QueryParser("title", analyzer);
|
||||||
Query titleQuery = titleQueryParser.parse(keyword);
|
Query titleQuery = titleQueryParser.parse(escapedKeyword);
|
||||||
BooleanClause titleBooleanClause = new BooleanClause(titleQuery, BooleanClause.Occur.SHOULD);
|
BooleanClause titleBooleanClause = new BooleanClause(titleQuery, BooleanClause.Occur.SHOULD);
|
||||||
|
|
||||||
QueryParser contentQueryParser = new QueryParser("content", analyzer);
|
QueryParser contentQueryParser = new QueryParser("content", analyzer);
|
||||||
Query contentQuery = contentQueryParser.parse(keyword);
|
Query contentQuery = contentQueryParser.parse(escapedKeyword);
|
||||||
BooleanClause contentBooleanClause = new BooleanClause(contentQuery, BooleanClause.Occur.SHOULD);
|
BooleanClause contentBooleanClause = new BooleanClause(contentQuery, BooleanClause.Occur.SHOULD);
|
||||||
|
|
||||||
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
BooleanQuery.Builder builder = new BooleanQuery.Builder();
|
||||||
builder.add(titleBooleanClause)
|
builder.add(titleBooleanClause)
|
||||||
.add(contentBooleanClause);
|
.add(contentBooleanClause);
|
||||||
if (request != null && request.getKnowledgeId() != null && !request.getKnowledgeId().trim().isEmpty()) {
|
if (request.getKnowledgeId() != null && !request.getKnowledgeId().trim().isEmpty()) {
|
||||||
builder.add(new TermQuery(new Term(KeywordSearchMetadataKeys.KNOWLEDGE_ID, request.getKnowledgeId().trim())), BooleanClause.Occur.MUST);
|
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);
|
|
||||||
}
|
}
|
||||||
return null;
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Analyzer createAnalyzer() {
|
private static Analyzer createAnalyzer() {
|
||||||
|
|||||||
@@ -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<Document> 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 完成批量写入。
|
* 验证多个导入线程可共享同一个 IndexWriter 完成批量写入。
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user