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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import run.halo.app.core.extension.Post;
|
import run.halo.app.core.extension.Post;
|
||||||
|
@ -39,7 +40,41 @@ public class PostQuery extends IListRequest.QueryListRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@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);
|
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.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.stereotype.Component;
|
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.PostQuery;
|
||||||
import run.halo.app.content.PostRequest;
|
import run.halo.app.content.PostRequest;
|
||||||
import run.halo.app.content.PostService;
|
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.Category;
|
||||||
import run.halo.app.core.extension.Post;
|
import run.halo.app.core.extension.Post;
|
||||||
import run.halo.app.core.extension.Snapshot;
|
import run.halo.app.core.extension.Snapshot;
|
||||||
|
@ -40,8 +42,6 @@ import run.halo.app.infra.ConditionStatus;
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
public class PostServiceImpl implements PostService {
|
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 ContentService contentService;
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
|
|
||||||
|
@ -52,8 +52,10 @@ public class PostServiceImpl implements PostService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
public Mono<ListResult<ListedPost>> listPost(PostQuery query) {
|
||||||
|
Comparator<Post> comparator =
|
||||||
|
PostSorter.from(query.getSort(), query.getSortOrder());
|
||||||
return client.list(Post.class, postListPredicate(query),
|
return client.list(Post.class, postListPredicate(query),
|
||||||
DEFAULT_POST_COMPARATOR.reversed(), query.getPage(), query.getSize())
|
comparator, query.getPage(), query.getSize())
|
||||||
.flatMap(listResult -> Flux.fromStream(
|
.flatMap(listResult -> Flux.fromStream(
|
||||||
listResult.get().map(this::getListedPost)
|
listResult.get().map(this::getListedPost)
|
||||||
)
|
)
|
||||||
|
@ -70,6 +72,40 @@ public class PostServiceImpl implements PostService {
|
||||||
contains(query.getCategories(), post.getSpec().getCategories())
|
contains(query.getCategories(), post.getSpec().getCategories())
|
||||||
&& contains(query.getTags(), post.getSpec().getTags())
|
&& contains(query.getTags(), post.getSpec().getTags())
|
||||||
&& contains(query.getContributors(), post.getStatus().getContributors());
|
&& 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(),
|
Predicate<Post> predicate = labelAndFieldSelectorToPredicate(query.getLabelSelector(),
|
||||||
query.getFieldSelector());
|
query.getFieldSelector());
|
||||||
return predicate.and(paramPredicate);
|
return predicate.and(paramPredicate);
|
||||||
|
|
|
@ -130,6 +130,8 @@ public class Post extends AbstractExtension {
|
||||||
|
|
||||||
private Boolean inProgress;
|
private Boolean inProgress;
|
||||||
|
|
||||||
|
private Integer commentsCount;
|
||||||
|
|
||||||
private List<String> contributors;
|
private List<String> contributors;
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
@ -153,13 +155,43 @@ public class Post extends AbstractExtension {
|
||||||
public enum PostPhase {
|
public enum PostPhase {
|
||||||
DRAFT,
|
DRAFT,
|
||||||
PENDING_APPROVAL,
|
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 enum VisibleEnum {
|
||||||
PUBLIC,
|
PUBLIC,
|
||||||
INTERNAL,
|
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
|
@Data
|
||||||
|
|
Loading…
Reference in New Issue