mirror of https://github.com/halo-dev/halo
feat: add more query predicates for post list (#2436)
#### What type of PR is this? /kind improvement /area core /milestone 2.0 #### What this PR does / why we need it: 文章列表新增更过的查询条件 - publishPhase 状态 - visible 可见性 - keyword 关键词 新增排序(sort)(可逆序 sortOrder) - 创建时间(默认创建时间逆序) - 发布时间 关键词过滤暂不管文章内容,否则需要查询所有文章内容判断是否包含字符串 排序暂无法支持评论数量和阅读量,这两个属性属于文章统计需要 #2430 的支撑 #### Which issue(s) this PR fixes: Fixes #2424 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note None ```pull/2456/head
parent
eee81e78f1
commit
bfbc4ec70a
|
@ -3,6 +3,7 @@ package run.halo.app.content;
|
|||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import run.halo.app.core.extension.Post;
|
||||
|
@ -39,7 +40,41 @@ public class PostQuery extends IListRequest.QueryListRequest {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
Set<String> listToSet(List<String> param) {
|
||||
public Post.PostPhase getPublishPhase() {
|
||||
String publishPhase = queryParams.getFirst("publishPhase");
|
||||
return Post.PostPhase.from(publishPhase);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Post.VisibleEnum getVisible() {
|
||||
String visible = queryParams.getFirst("visible");
|
||||
return Post.VisibleEnum.from(visible);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Schema(description = "Posts filtered by keyword.")
|
||||
public String getKeyword() {
|
||||
return StringUtils.defaultIfBlank(queryParams.getFirst("keyword"), null);
|
||||
}
|
||||
|
||||
@Schema(description = "Post collation.")
|
||||
public PostSorter getSort() {
|
||||
String sort = queryParams.getFirst("sort");
|
||||
return PostSorter.convertFrom(sort);
|
||||
}
|
||||
|
||||
@Schema(description = "ascending order If it is true; otherwise, it is in descending order.")
|
||||
public Boolean getSortOrder() {
|
||||
String sortOrder = queryParams.getFirst("sortOrder");
|
||||
return convertBooleanOrNull(sortOrder);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Set<String> listToSet(List<String> param) {
|
||||
return param == null ? null : Set.copyOf(param);
|
||||
}
|
||||
|
||||
private Boolean convertBooleanOrNull(String value) {
|
||||
return StringUtils.isBlank(value) ? null : Boolean.parseBoolean(value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
import run.halo.app.core.extension.Post;
|
||||
|
||||
/**
|
||||
* A sorter for {@link Post}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public enum PostSorter {
|
||||
PUBLISH_TIME,
|
||||
CREATE_TIME;
|
||||
|
||||
static final Function<Post, String> name = post -> post.getMetadata().getName();
|
||||
|
||||
/**
|
||||
* Converts {@link Comparator} from {@link PostSorter} and ascending.
|
||||
*
|
||||
* @param sorter a {@link PostSorter}
|
||||
* @param ascending ascending if true, otherwise descending
|
||||
* @return a {@link Comparator} of {@link Post}
|
||||
*/
|
||||
public static Comparator<Post> from(PostSorter sorter, Boolean ascending) {
|
||||
if (Objects.equals(true, ascending)) {
|
||||
return from(sorter);
|
||||
}
|
||||
return from(sorter).reversed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts {@link Comparator} from {@link PostSorter}.
|
||||
*
|
||||
* @param sorter a {@link PostSorter}
|
||||
* @return a {@link Comparator} of {@link Post}
|
||||
*/
|
||||
public static Comparator<Post> from(PostSorter sorter) {
|
||||
if (sorter == null) {
|
||||
return defaultComparator();
|
||||
}
|
||||
if (CREATE_TIME.equals(sorter)) {
|
||||
Function<Post, Instant> comparatorFunc =
|
||||
post -> post.getMetadata().getCreationTimestamp();
|
||||
return Comparator.comparing(comparatorFunc)
|
||||
.thenComparing(name);
|
||||
}
|
||||
|
||||
if (PUBLISH_TIME.equals(sorter)) {
|
||||
Function<Post, Instant> comparatorFunc =
|
||||
post -> post.getSpec().getPublishTime();
|
||||
return Comparator.comparing(comparatorFunc, Comparators.nullsLow())
|
||||
.thenComparing(name);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unsupported sort value: " + sorter);
|
||||
}
|
||||
|
||||
static PostSorter convertFrom(String sort) {
|
||||
for (PostSorter sorter : values()) {
|
||||
if (sorter.name().equalsIgnoreCase(sort)) {
|
||||
return sorter;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Comparator<Post> defaultComparator() {
|
||||
Function<Post, Instant> createTime =
|
||||
post -> post.getMetadata().getCreationTimestamp();
|
||||
return Comparator.comparing(createTime)
|
||||
.thenComparing(name)
|
||||
.reversed();
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import java.util.List;
|
|||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -22,6 +23,7 @@ import run.halo.app.content.ListedPost;
|
|||
import run.halo.app.content.PostQuery;
|
||||
import run.halo.app.content.PostRequest;
|
||||
import run.halo.app.content.PostService;
|
||||
import run.halo.app.content.PostSorter;
|
||||
import run.halo.app.core.extension.Category;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.Snapshot;
|
||||
|
@ -40,8 +42,6 @@ import run.halo.app.infra.ConditionStatus;
|
|||
*/
|
||||
@Component
|
||||
public class PostServiceImpl implements PostService {
|
||||
private static final Comparator<Post> DEFAULT_POST_COMPARATOR =
|
||||
Comparator.comparing(post -> post.getMetadata().getCreationTimestamp());
|
||||
private final ContentService contentService;
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
|
@ -52,8 +52,10 @@ public class PostServiceImpl implements PostService {
|
|||
|
||||
@Override
|
||||
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
||||
Comparator<Post> comparator =
|
||||
PostSorter.from(query.getSort(), query.getSortOrder());
|
||||
return client.list(Post.class, postListPredicate(query),
|
||||
DEFAULT_POST_COMPARATOR.reversed(), query.getPage(), query.getSize())
|
||||
comparator, query.getPage(), query.getSize())
|
||||
.flatMap(listResult -> Flux.fromStream(
|
||||
listResult.get().map(this::getListedPost)
|
||||
)
|
||||
|
@ -70,6 +72,40 @@ public class PostServiceImpl implements PostService {
|
|||
contains(query.getCategories(), post.getSpec().getCategories())
|
||||
&& contains(query.getTags(), post.getSpec().getTags())
|
||||
&& contains(query.getContributors(), post.getStatus().getContributors());
|
||||
|
||||
String keyword = query.getKeyword();
|
||||
if (keyword != null) {
|
||||
paramPredicate = paramPredicate.and(post -> {
|
||||
String excerpt = post.getStatusOrDefault().getExcerpt();
|
||||
return StringUtils.containsIgnoreCase(excerpt, keyword)
|
||||
|| StringUtils.containsIgnoreCase(post.getSpec().getSlug(), keyword)
|
||||
|| StringUtils.containsIgnoreCase(post.getSpec().getTitle(), keyword);
|
||||
});
|
||||
}
|
||||
|
||||
Post.PostPhase publishPhase = query.getPublishPhase();
|
||||
if (publishPhase != null) {
|
||||
paramPredicate = paramPredicate.and(post -> {
|
||||
if (Post.PostPhase.PENDING_APPROVAL.equals(publishPhase)) {
|
||||
return !post.isPublished()
|
||||
&& Post.PostPhase.PENDING_APPROVAL.name()
|
||||
.equalsIgnoreCase(post.getStatusOrDefault().getPhase());
|
||||
}
|
||||
// published
|
||||
if (Post.PostPhase.PUBLISHED.equals(publishPhase)) {
|
||||
return post.isPublished();
|
||||
}
|
||||
// draft
|
||||
return !post.isPublished();
|
||||
});
|
||||
}
|
||||
|
||||
Post.VisibleEnum visible = query.getVisible();
|
||||
if (visible != null) {
|
||||
paramPredicate =
|
||||
paramPredicate.and(post -> visible.equals(post.getSpec().getVisible()));
|
||||
}
|
||||
|
||||
Predicate<Post> predicate = labelAndFieldSelectorToPredicate(query.getLabelSelector(),
|
||||
query.getFieldSelector());
|
||||
return predicate.and(paramPredicate);
|
||||
|
|
|
@ -130,6 +130,8 @@ public class Post extends AbstractExtension {
|
|||
|
||||
private Boolean inProgress;
|
||||
|
||||
private Integer commentsCount;
|
||||
|
||||
private List<String> contributors;
|
||||
|
||||
@JsonIgnore
|
||||
|
@ -153,13 +155,43 @@ public class Post extends AbstractExtension {
|
|||
public enum PostPhase {
|
||||
DRAFT,
|
||||
PENDING_APPROVAL,
|
||||
PUBLISHED
|
||||
PUBLISHED;
|
||||
|
||||
/**
|
||||
* Convert string value to {@link PostPhase}.
|
||||
*
|
||||
* @param value enum value string
|
||||
* @return {@link PostPhase} if found, otherwise null
|
||||
*/
|
||||
public static PostPhase from(String value) {
|
||||
for (PostPhase phase : PostPhase.values()) {
|
||||
if (phase.name().equalsIgnoreCase(value)) {
|
||||
return phase;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum VisibleEnum {
|
||||
PUBLIC,
|
||||
INTERNAL,
|
||||
PRIVATE
|
||||
PRIVATE;
|
||||
|
||||
/**
|
||||
* Convert value string to {@link VisibleEnum}.
|
||||
*
|
||||
* @param value enum value string
|
||||
* @return {@link VisibleEnum} if found, otherwise null
|
||||
*/
|
||||
public static VisibleEnum from(String value) {
|
||||
for (VisibleEnum visible : VisibleEnum.values()) {
|
||||
if (visible.name().equalsIgnoreCase(value)) {
|
||||
return visible;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
|
|
Loading…
Reference in New Issue