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; 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; 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; 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; 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; private List<String> includeTagNames;

View File

@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.charfilter.HTMLStripCharFilterFactory; 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 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 finalQuery = queryBuilder.build();
var limit = option.getLimit(); 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 static run.halo.app.core.extension.content.Post.VisibleEnum.PUBLIC;
import java.time.Duration; import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -122,6 +123,9 @@ public class LuceneSearchEngineIntegrationTest {
option.setKeyword("halo"); option.setKeyword("halo");
option.setHighlightPreTag("<my-tag>"); option.setHighlightPreTag("<my-tag>");
option.setHighlightPostTag("</my-tag>"); option.setHighlightPostTag("</my-tag>");
option.setIncludeTagNames(List.of("search"));
option.setIncludeCategoryNames(List.of("halo"));
option.setIncludeOwnerNames(List.of("admin"));
retryTemplate.execute(context -> { retryTemplate.execute(context -> {
webClient.post().uri("/apis/api.halo.run/v1alpha1/indices/-/search") webClient.post().uri("/apis/api.halo.run/v1alpha1/indices/-/search")
.bodyValue(option) .bodyValue(option)
@ -218,6 +222,8 @@ public class LuceneSearchEngineIntegrationTest {
spec.setPriority(0); spec.setPriority(0);
spec.setSlug("/first-post"); spec.setSlug("/first-post");
spec.setDeleted(false); spec.setDeleted(false);
spec.setTags(List.of("search"));
spec.setCategories(List.of("halo"));
var excerpt = new Post.Excerpt(); var excerpt = new Post.Excerpt();
excerpt.setRaw("first post description"); excerpt.setRaw("first post description");
excerpt.setAutoGenerate(false); excerpt.setAutoGenerate(false);