Support filtering search result by types, ownerNames, categoryNames and tagNames (#6442)

#### What type of PR is this?

/kind improvement
/area core

#### What this PR does / why we need it:

This PR allows users to filter search result by types, owner names, category names and tag names.

#### Does this PR introduce a user-facing change?

```release-note
完善搜索引擎过滤功能
```
pull/6453/head
John Niang 2024-08-08 12:58:37 +08:00 committed by GitHub
parent 457508f1cd
commit 40386557a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 4 deletions

View File

@ -53,22 +53,22 @@ public class SearchOption {
private Boolean filterPublished;
/**
* Types to include. If null, it will include all types.
* Types to include(or). If null, it will include all types.
*/
private List<String> includeTypes;
/**
* Owner names to include. If null, it will include all owners.
* Owner names to include(or). If null, it will include all owners.
*/
private List<String> includeOwnerNames;
/**
* Category names to include. If null, it will include all categories.
* Category names to include(and). If null, it will include all categories.
*/
private List<String> includeCategoryNames;
/**
* Tag names to include. If null, it will include all tags.
* Tag names to include(and). If null, it will include all tags.
*/
private List<String> includeTagNames;

View File

@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.charfilter.HTMLStripCharFilterFactory;
@ -170,6 +171,45 @@ public class LuceneSearchEngine implements SearchEngine, InitializingBean, Dispo
new TermQuery(new Term("published", filterPublished.toString())), FILTER
);
}
Optional.ofNullable(option.getIncludeTypes())
.filter(types -> !types.isEmpty())
.ifPresent(types -> {
var typeTerms = types.stream()
.distinct()
.map(BytesRef::new)
.toList();
queryBuilder.add(new TermInSetQuery("type", typeTerms), FILTER);
});
Optional.ofNullable(option.getIncludeOwnerNames())
.filter(ownerNames -> !ownerNames.isEmpty())
.ifPresent(ownerNames -> {
var ownerTerms = ownerNames.stream()
.distinct()
.map(BytesRef::new)
.toList();
queryBuilder.add(new TermInSetQuery("ownerName", ownerTerms), FILTER);
});
Optional.ofNullable(option.getIncludeTagNames())
.filter(tagNames -> !tagNames.isEmpty())
.ifPresent(tagNames -> tagNames
.stream()
.distinct()
.forEach(tagName ->
queryBuilder.add(new TermQuery(new Term("tag", tagName)), FILTER)
));
Optional.ofNullable(option.getIncludeCategoryNames())
.filter(categoryNames -> !categoryNames.isEmpty())
.ifPresent(categoryNames -> categoryNames
.stream()
.distinct()
.forEach(categoryName ->
queryBuilder.add(new TermQuery(new Term("category", categoryName)), FILTER)
));
var finalQuery = queryBuilder.build();
var limit = option.getLimit();

View File

@ -6,6 +6,7 @@ import static run.halo.app.core.extension.content.Post.VisibleEnum.PRIVATE;
import static run.halo.app.core.extension.content.Post.VisibleEnum.PUBLIC;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -122,6 +123,9 @@ public class LuceneSearchEngineIntegrationTest {
option.setKeyword("halo");
option.setHighlightPreTag("<my-tag>");
option.setHighlightPostTag("</my-tag>");
option.setIncludeTagNames(List.of("search"));
option.setIncludeCategoryNames(List.of("halo"));
option.setIncludeOwnerNames(List.of("admin"));
retryTemplate.execute(context -> {
webClient.post().uri("/apis/api.halo.run/v1alpha1/indices/-/search")
.bodyValue(option)
@ -218,6 +222,8 @@ public class LuceneSearchEngineIntegrationTest {
spec.setPriority(0);
spec.setSlug("/first-post");
spec.setDeleted(false);
spec.setTags(List.of("search"));
spec.setCategories(List.of("halo"));
var excerpt = new Post.Excerpt();
excerpt.setRaw("first post description");
excerpt.setAutoGenerate(false);