diff --git a/application/src/main/java/run/halo/app/theme/endpoint/CommentFinderEndpoint.java b/application/src/main/java/run/halo/app/theme/endpoint/CommentFinderEndpoint.java index 3cfd091ee..15a284512 100644 --- a/application/src/main/java/run/halo/app/theme/endpoint/CommentFinderEndpoint.java +++ b/application/src/main/java/run/halo/app/theme/endpoint/CommentFinderEndpoint.java @@ -1,6 +1,7 @@ package run.halo.app.theme.endpoint; import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import static java.util.Comparator.comparing; import static org.apache.commons.lang3.BooleanUtils.isFalse; import static org.apache.commons.lang3.BooleanUtils.isTrue; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; @@ -10,18 +11,23 @@ import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuil import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springdoc.core.fn.builders.schema.Builder; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; +import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -32,6 +38,8 @@ import run.halo.app.content.comment.ReplyService; import run.halo.app.core.extension.content.Comment; import run.halo.app.core.extension.content.Reply; import run.halo.app.core.extension.endpoint.CustomEndpoint; +import run.halo.app.core.extension.endpoint.SortResolver; +import run.halo.app.extension.Comparators; import run.halo.app.extension.GroupVersion; import run.halo.app.extension.ListResult; import run.halo.app.extension.Ref; @@ -42,6 +50,7 @@ import run.halo.app.infra.exception.AccessDeniedException; import run.halo.app.infra.utils.HaloUtils; import run.halo.app.infra.utils.IpAddressUtils; import run.halo.app.theme.finders.CommentFinder; +import run.halo.app.theme.finders.CommentPublicQueryService; import run.halo.app.theme.finders.vo.CommentVo; import run.halo.app.theme.finders.vo.ReplyVo; @@ -52,7 +61,7 @@ import run.halo.app.theme.finders.vo.ReplyVo; @RequiredArgsConstructor public class CommentFinderEndpoint implements CustomEndpoint { - private final CommentFinder commentFinder; + private final CommentPublicQueryService commentPublicQueryService; private final CommentService commentService; private final ReplyService replyService; private final SystemConfigurableEnvironmentFetcher environmentFetcher; @@ -185,15 +194,16 @@ public class CommentFinderEndpoint implements CustomEndpoint { } Mono listComments(ServerRequest request) { - CommentQuery commentQuery = new CommentQuery(request.queryParams()); - return commentFinder.list(commentQuery.toRef(), commentQuery.getPage(), - commentQuery.getSize()) + CommentQuery commentQuery = new CommentQuery(request); + var comparator = commentQuery.toComparator(); + return commentPublicQueryService.list(commentQuery.toRef(), commentQuery.getPage(), + commentQuery.getSize(), comparator) .flatMap(list -> ServerResponse.ok().bodyValue(list)); } Mono getComment(ServerRequest request) { String name = request.pathVariable("name"); - return Mono.defer(() -> Mono.justOrEmpty(commentFinder.getByName(name))) + return Mono.defer(() -> Mono.justOrEmpty(commentPublicQueryService.getByName(name))) .subscribeOn(Schedulers.boundedElastic()) .flatMap(comment -> ServerResponse.ok().bodyValue(comment)); } @@ -202,14 +212,18 @@ public class CommentFinderEndpoint implements CustomEndpoint { String commentName = request.pathVariable("name"); IListRequest.QueryListRequest queryParams = new IListRequest.QueryListRequest(request.queryParams()); - return commentFinder.listReply(commentName, queryParams.getPage(), queryParams.getSize()) + return commentPublicQueryService.listReply(commentName, queryParams.getPage(), + queryParams.getSize()) .flatMap(list -> ServerResponse.ok().bodyValue(list)); } public static class CommentQuery extends PageableRequest { - public CommentQuery(MultiValueMap queryParams) { - super(queryParams); + private final ServerWebExchange exchange; + + public CommentQuery(ServerRequest request) { + super(request.queryParams()); + this.exchange = request.exchange(); } @Schema(description = "The comment subject group.") @@ -250,6 +264,17 @@ public class CommentFinderEndpoint implements CustomEndpoint { return name; } + @ArraySchema(uniqueItems = true, + arraySchema = @Schema(name = "sort", + description = "Sort property and direction of the list result. Supported fields: " + + "creationTimestamp"), + schema = @Schema(description = "like field,asc or field,desc", + implementation = String.class, + example = "creationTimestamp,desc")) + public Sort getSort() { + return SortResolver.defaultInstance.resolve(exchange); + } + Ref toRef() { Ref ref = new Ref(); ref.setGroup(getGroup()); @@ -262,6 +287,24 @@ public class CommentFinderEndpoint implements CustomEndpoint { String emptyToNull(String str) { return StringUtils.isBlank(str) ? null : str; } + + public Comparator toComparator() { + var sort = getSort(); + var ctOrder = sort.getOrderFor("creationTimestamp"); + List> comparators = new ArrayList<>(); + if (ctOrder != null) { + Comparator comparator = + comparing(comment -> comment.getMetadata().getCreationTimestamp()); + if (ctOrder.isDescending()) { + comparator = comparator.reversed(); + } + comparators.add(comparator); + comparators.add(Comparators.compareName(true)); + } + return comparators.stream() + .reduce(Comparator::thenComparing) + .orElse(null); + } } public static class PageableRequest extends IListRequest.QueryListRequest { diff --git a/application/src/main/java/run/halo/app/theme/finders/CommentPublicQueryService.java b/application/src/main/java/run/halo/app/theme/finders/CommentPublicQueryService.java new file mode 100644 index 000000000..0876681e8 --- /dev/null +++ b/application/src/main/java/run/halo/app/theme/finders/CommentPublicQueryService.java @@ -0,0 +1,32 @@ +package run.halo.app.theme.finders; + +import java.util.Comparator; +import org.springframework.lang.Nullable; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.content.Comment; +import run.halo.app.core.extension.content.Reply; +import run.halo.app.extension.ListResult; +import run.halo.app.extension.Ref; +import run.halo.app.theme.finders.vo.CommentVo; +import run.halo.app.theme.finders.vo.ReplyVo; + +/** + * comment finder. + * + * @author LIlGG + */ +public interface CommentPublicQueryService { + Mono getByName(String name); + + Mono> list(Ref ref, @Nullable Integer page, + @Nullable Integer size); + + Mono> list(Ref ref, @Nullable Integer page, + @Nullable Integer size, @Nullable Comparator comparator); + + Mono> listReply(String commentName, @Nullable Integer page, + @Nullable Integer size); + + Mono> listReply(String commentName, @Nullable Integer page, + @Nullable Integer size, @Nullable Comparator comparator); +} diff --git a/application/src/main/java/run/halo/app/theme/finders/impl/CommentFinderImpl.java b/application/src/main/java/run/halo/app/theme/finders/impl/CommentFinderImpl.java index 1fbe160f1..184537a81 100644 --- a/application/src/main/java/run/halo/app/theme/finders/impl/CommentFinderImpl.java +++ b/application/src/main/java/run/halo/app/theme/finders/impl/CommentFinderImpl.java @@ -1,37 +1,13 @@ package run.halo.app.theme.finders.impl; -import java.security.Principal; -import java.util.Comparator; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.util.Assert; -import org.springframework.util.comparator.Comparators; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import run.halo.app.content.comment.OwnerInfo; -import run.halo.app.content.comment.ReplyService; -import run.halo.app.core.extension.content.Comment; -import run.halo.app.core.extension.content.Reply; -import run.halo.app.core.extension.service.UserService; -import run.halo.app.extension.AbstractExtension; import run.halo.app.extension.ListResult; -import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Ref; -import run.halo.app.infra.AnonymousUserConst; -import run.halo.app.metrics.CounterService; -import run.halo.app.metrics.MeterUtils; import run.halo.app.theme.finders.CommentFinder; +import run.halo.app.theme.finders.CommentPublicQueryService; import run.halo.app.theme.finders.Finder; -import run.halo.app.theme.finders.vo.CommentStatsVo; import run.halo.app.theme.finders.vo.CommentVo; -import run.halo.app.theme.finders.vo.ExtensionVoOperator; import run.halo.app.theme.finders.vo.ReplyVo; /** @@ -44,185 +20,20 @@ import run.halo.app.theme.finders.vo.ReplyVo; @RequiredArgsConstructor public class CommentFinderImpl implements CommentFinder { - private final ReactiveExtensionClient client; - private final UserService userService; - private final CounterService counterService; + private final CommentPublicQueryService commentPublicQueryService; @Override public Mono getByName(String name) { - return client.fetch(Comment.class, name) - .flatMap(this::toCommentVo); + return commentPublicQueryService.getByName(name); } @Override public Mono> list(Ref ref, Integer page, Integer size) { - return fixedCommentPredicate(ref) - .flatMap(fixedPredicate -> - client.list(Comment.class, fixedPredicate, - defaultComparator(), - pageNullSafe(page), sizeNullSafe(size)) - .flatMap(list -> Flux.fromStream(list.get().map(this::toCommentVo)) - .concatMap(Function.identity()) - .collectList() - .map(commentVos -> new ListResult<>(list.getPage(), list.getSize(), - list.getTotal(), - commentVos) - ) - ) - .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of())) - ); + return commentPublicQueryService.list(ref, page, size); } @Override public Mono> listReply(String commentName, Integer page, Integer size) { - return fixedReplyPredicate(commentName) - .flatMap(fixedPredicate -> - client.list(Reply.class, fixedPredicate, - ReplyService.creationTimeAscComparator(), pageNullSafe(page), - sizeNullSafe(size)) - .flatMap(list -> Flux.fromStream(list.get().map(this::toReplyVo)) - .concatMap(Function.identity()) - .collectList() - .map(replyVos -> new ListResult<>(list.getPage(), list.getSize(), - list.getTotal(), - replyVos)) - ) - .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of())) - ); - } - - private Mono toCommentVo(Comment comment) { - Comment.CommentOwner owner = comment.getSpec().getOwner(); - return Mono.just(CommentVo.from(comment)) - .flatMap(commentVo -> populateStats(Comment.class, commentVo) - .doOnNext(commentVo::setStats) - .thenReturn(commentVo)) - .flatMap(commentVo -> getOwnerInfo(owner) - .doOnNext(commentVo::setOwner) - .thenReturn(commentVo) - ); - } - - private Mono - populateStats(Class clazz, T vo) { - return counterService.getByName(MeterUtils.nameOf(clazz, vo.getMetadata() - .getName())) - .map(counter -> CommentStatsVo.builder() - .upvote(counter.getUpvote()) - .build() - ) - .defaultIfEmpty(CommentStatsVo.empty()); - } - - private Mono toReplyVo(Reply reply) { - return Mono.just(ReplyVo.from(reply)) - .flatMap(replyVo -> populateStats(Reply.class, replyVo) - .doOnNext(replyVo::setStats) - .thenReturn(replyVo)) - .flatMap(replyVo -> getOwnerInfo(reply.getSpec().getOwner()) - .doOnNext(replyVo::setOwner) - .thenReturn(replyVo) - ); - } - - private Mono getOwnerInfo(Comment.CommentOwner owner) { - if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) { - return Mono.just(OwnerInfo.from(owner)); - } - return userService.getUserOrGhost(owner.getName()) - .map(OwnerInfo::from); - } - - private Mono> fixedCommentPredicate(Ref ref) { - Assert.notNull(ref, "Comment subject reference must not be null"); - // Ref must be equal to the comment subject - Predicate refPredicate = comment -> comment.getSpec().getSubjectRef().equals(ref) - && comment.getMetadata().getDeletionTimestamp() == null; - - // is approved and not hidden - Predicate approvedPredicate = - comment -> BooleanUtils.isFalse(comment.getSpec().getHidden()) - && BooleanUtils.isTrue(comment.getSpec().getApproved()); - return getCurrentUserWithoutAnonymous() - .map(username -> { - Predicate isOwner = comment -> { - Comment.CommentOwner owner = comment.getSpec().getOwner(); - return owner != null && StringUtils.equals(username, owner.getName()); - }; - return approvedPredicate.or(isOwner); - }) - .defaultIfEmpty(approvedPredicate) - .map(refPredicate::and); - } - - private Mono> fixedReplyPredicate(String commentName) { - Assert.notNull(commentName, "The commentName must not be null"); - // The comment name must be equal to the comment name of the reply - Predicate commentNamePredicate = - reply -> reply.getSpec().getCommentName().equals(commentName) - && reply.getMetadata().getDeletionTimestamp() == null; - - // is approved and not hidden - Predicate approvedPredicate = - reply -> BooleanUtils.isFalse(reply.getSpec().getHidden()) - && BooleanUtils.isTrue(reply.getSpec().getApproved()); - return getCurrentUserWithoutAnonymous() - .map(username -> { - Predicate isOwner = reply -> { - Comment.CommentOwner owner = reply.getSpec().getOwner(); - return owner != null && StringUtils.equals(username, owner.getName()); - }; - return approvedPredicate.or(isOwner); - }) - .defaultIfEmpty(approvedPredicate) - .map(commentNamePredicate::and); - } - - Mono getCurrentUserWithoutAnonymous() { - return ReactiveSecurityContextHolder.getContext() - .mapNotNull(SecurityContext::getAuthentication) - .map(Principal::getName) - .filter(username -> !AnonymousUserConst.PRINCIPAL.equals(username)); - } - - static Comparator defaultComparator() { - return new CommentComparator(); - } - - static class CommentComparator implements Comparator { - @Override - public int compare(Comment c1, Comment c2) { - boolean c1Top = BooleanUtils.isTrue(c1.getSpec().getTop()); - boolean c2Top = BooleanUtils.isTrue(c2.getSpec().getTop()); - if (c1Top == c2Top) { - int c1Priority = ObjectUtils.defaultIfNull(c1.getSpec().getPriority(), 0); - int c2Priority = ObjectUtils.defaultIfNull(c2.getSpec().getPriority(), 0); - if (c1Top) { - // 都置顶 - return Integer.compare(c1Priority, c2Priority); - } - - // 两个评论不置顶根据 creationTime 降序排列 - return Comparator.comparing( - (Comment comment) -> comment.getSpec().getCreationTime(), - Comparators.nullsLow()) - .thenComparing((Comment comment) -> comment.getMetadata().getName()) - .compare(c2, c1); - } else if (c1Top) { - // 只有 c1 置顶,c1 排前面 - return -1; - } else { - // 只有c2置顶, c2排在前面 - return 1; - } - } - } - - int pageNullSafe(Integer page) { - return ObjectUtils.defaultIfNull(page, 1); - } - - int sizeNullSafe(Integer size) { - return ObjectUtils.defaultIfNull(size, 10); + return commentPublicQueryService.listReply(commentName, page, size); } } diff --git a/application/src/main/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImpl.java b/application/src/main/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImpl.java new file mode 100644 index 000000000..4f5be5cbf --- /dev/null +++ b/application/src/main/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImpl.java @@ -0,0 +1,244 @@ +package run.halo.app.theme.finders.impl; + + +import java.security.Principal; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; +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; +import org.springframework.util.Assert; +import org.springframework.util.comparator.Comparators; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.halo.app.content.comment.OwnerInfo; +import run.halo.app.content.comment.ReplyService; +import run.halo.app.core.extension.content.Comment; +import run.halo.app.core.extension.content.Reply; +import run.halo.app.core.extension.service.UserService; +import run.halo.app.extension.AbstractExtension; +import run.halo.app.extension.ListResult; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.Ref; +import run.halo.app.infra.AnonymousUserConst; +import run.halo.app.metrics.CounterService; +import run.halo.app.metrics.MeterUtils; +import run.halo.app.theme.finders.CommentPublicQueryService; +import run.halo.app.theme.finders.vo.CommentStatsVo; +import run.halo.app.theme.finders.vo.CommentVo; +import run.halo.app.theme.finders.vo.ExtensionVoOperator; +import run.halo.app.theme.finders.vo.ReplyVo; + +/** + * comment public query service implementation. + * + * @author LIlGG + */ +@Component +@RequiredArgsConstructor +public class CommentPublicQueryServiceImpl implements CommentPublicQueryService { + + private final ReactiveExtensionClient client; + private final UserService userService; + private final CounterService counterService; + + @Override + public Mono getByName(String name) { + return client.fetch(Comment.class, name) + .flatMap(this::toCommentVo); + } + + @Override + public Mono> list(Ref ref, Integer page, Integer size) { + return list(ref, page, size, defaultComparator()); + } + + @Override + public Mono> list(Ref ref, Integer page, Integer size, + Comparator comparator) { + final Comparator commentComparator = + Objects.isNull(comparator) ? defaultComparator() + : comparator.thenComparing(defaultComparator()); + return fixedCommentPredicate(ref) + .flatMap(fixedPredicate -> + client.list(Comment.class, fixedPredicate, + commentComparator, + pageNullSafe(page), sizeNullSafe(size)) + .flatMap(list -> Flux.fromStream(list.get().map(this::toCommentVo)) + .concatMap(Function.identity()) + .collectList() + .map(commentVos -> new ListResult<>(list.getPage(), list.getSize(), + list.getTotal(), + commentVos) + ) + ) + .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of())) + ); + } + + @Override + public Mono> listReply(String commentName, Integer page, Integer size) { + return listReply(commentName, page, size, ReplyService.creationTimeAscComparator()); + } + + @Override + public Mono> listReply(String commentName, Integer page, Integer size, + Comparator comparator) { + return fixedReplyPredicate(commentName) + .flatMap(fixedPredicate -> + client.list(Reply.class, fixedPredicate, + comparator, + pageNullSafe(page), sizeNullSafe(size)) + .flatMap(list -> Flux.fromStream(list.get().map(this::toReplyVo)) + .concatMap(Function.identity()) + .collectList() + .map(replyVos -> new ListResult<>(list.getPage(), list.getSize(), + list.getTotal(), + replyVos)) + ) + .defaultIfEmpty(new ListResult<>(page, size, 0L, List.of())) + ); + } + + private Mono toCommentVo(Comment comment) { + Comment.CommentOwner owner = comment.getSpec().getOwner(); + return Mono.just(CommentVo.from(comment)) + .flatMap(commentVo -> populateStats(Comment.class, commentVo) + .doOnNext(commentVo::setStats) + .thenReturn(commentVo)) + .flatMap(commentVo -> getOwnerInfo(owner) + .doOnNext(commentVo::setOwner) + .thenReturn(commentVo) + ); + } + + private Mono + populateStats(Class clazz, T vo) { + return counterService.getByName(MeterUtils.nameOf(clazz, vo.getMetadata() + .getName())) + .map(counter -> CommentStatsVo.builder() + .upvote(counter.getUpvote()) + .build() + ) + .defaultIfEmpty(CommentStatsVo.empty()); + } + + private Mono toReplyVo(Reply reply) { + return Mono.just(ReplyVo.from(reply)) + .flatMap(replyVo -> populateStats(Reply.class, replyVo) + .doOnNext(replyVo::setStats) + .thenReturn(replyVo)) + .flatMap(replyVo -> getOwnerInfo(reply.getSpec().getOwner()) + .doOnNext(replyVo::setOwner) + .thenReturn(replyVo) + ); + } + + private Mono getOwnerInfo(Comment.CommentOwner owner) { + if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) { + return Mono.just(OwnerInfo.from(owner)); + } + return userService.getUserOrGhost(owner.getName()) + .map(OwnerInfo::from); + } + + private Mono> fixedCommentPredicate(Ref ref) { + Assert.notNull(ref, "Comment subject reference must not be null"); + // Ref must be equal to the comment subject + Predicate refPredicate = comment -> comment.getSpec().getSubjectRef().equals(ref) + && comment.getMetadata().getDeletionTimestamp() == null; + + // is approved and not hidden + Predicate approvedPredicate = + comment -> BooleanUtils.isFalse(comment.getSpec().getHidden()) + && BooleanUtils.isTrue(comment.getSpec().getApproved()); + return getCurrentUserWithoutAnonymous() + .map(username -> { + Predicate isOwner = comment -> { + Comment.CommentOwner owner = comment.getSpec().getOwner(); + return owner != null && StringUtils.equals(username, owner.getName()); + }; + return approvedPredicate.or(isOwner); + }) + .defaultIfEmpty(approvedPredicate) + .map(refPredicate::and); + } + + private Mono> fixedReplyPredicate(String commentName) { + Assert.notNull(commentName, "The commentName must not be null"); + // The comment name must be equal to the comment name of the reply + Predicate commentNamePredicate = + reply -> reply.getSpec().getCommentName().equals(commentName) + && reply.getMetadata().getDeletionTimestamp() == null; + + // is approved and not hidden + Predicate approvedPredicate = + reply -> BooleanUtils.isFalse(reply.getSpec().getHidden()) + && BooleanUtils.isTrue(reply.getSpec().getApproved()); + return getCurrentUserWithoutAnonymous() + .map(username -> { + Predicate isOwner = reply -> { + Comment.CommentOwner owner = reply.getSpec().getOwner(); + return owner != null && StringUtils.equals(username, owner.getName()); + }; + return approvedPredicate.or(isOwner); + }) + .defaultIfEmpty(approvedPredicate) + .map(commentNamePredicate::and); + } + + Mono getCurrentUserWithoutAnonymous() { + return ReactiveSecurityContextHolder.getContext() + .mapNotNull(SecurityContext::getAuthentication) + .map(Principal::getName) + .filter(username -> !AnonymousUserConst.PRINCIPAL.equals(username)); + } + + static Comparator defaultComparator() { + return new CommentComparator(); + } + + static class CommentComparator implements Comparator { + @Override + public int compare(Comment c1, Comment c2) { + boolean c1Top = BooleanUtils.isTrue(c1.getSpec().getTop()); + boolean c2Top = BooleanUtils.isTrue(c2.getSpec().getTop()); + if (c1Top == c2Top) { + int c1Priority = ObjectUtils.defaultIfNull(c1.getSpec().getPriority(), 0); + int c2Priority = ObjectUtils.defaultIfNull(c2.getSpec().getPriority(), 0); + if (c1Top) { + // 都置顶 + return Integer.compare(c1Priority, c2Priority); + } + + // 两个评论不置顶根据 creationTime 降序排列 + return Comparator.comparing( + (Comment comment) -> comment.getSpec().getCreationTime(), + Comparators.nullsLow()) + .thenComparing((Comment comment) -> comment.getMetadata().getName()) + .compare(c2, c1); + } else if (c1Top) { + // 只有 c1 置顶,c1 排前面 + return -1; + } else { + // 只有c2置顶, c2排在前面 + return 1; + } + } + } + + int pageNullSafe(Integer page) { + return ObjectUtils.defaultIfNull(page, 1); + } + + int sizeNullSafe(Integer size) { + return ObjectUtils.defaultIfNull(size, 10); + } +} diff --git a/application/src/test/java/run/halo/app/theme/endpoint/CommentFinderEndpointTest.java b/application/src/test/java/run/halo/app/theme/endpoint/CommentFinderEndpointTest.java index 7d5b19e55..598b4db9b 100644 --- a/application/src/test/java/run/halo/app/theme/endpoint/CommentFinderEndpointTest.java +++ b/application/src/test/java/run/halo/app/theme/endpoint/CommentFinderEndpointTest.java @@ -29,6 +29,7 @@ import run.halo.app.extension.ListResult; import run.halo.app.extension.Ref; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.theme.finders.CommentFinder; +import run.halo.app.theme.finders.CommentPublicQueryService; /** * Tests for {@link CommentFinderEndpoint}. @@ -41,6 +42,9 @@ class CommentFinderEndpointTest { @Mock private CommentFinder commentFinder; + @Mock + private CommentPublicQueryService commentPublicQueryService; + @Mock private CommentService commentService; @@ -65,7 +69,7 @@ class CommentFinderEndpointTest { @Test void listComments() { - when(commentFinder.list(any(), anyInt(), anyInt())) + when(commentPublicQueryService.list(any(), anyInt(), anyInt(), any())) .thenReturn(Mono.just(new ListResult<>(1, 10, 0, List.of()))); Ref ref = new Ref(); @@ -88,14 +92,14 @@ class CommentFinderEndpointTest { .expectStatus() .isOk(); ArgumentCaptor refCaptor = ArgumentCaptor.forClass(Ref.class); - verify(commentFinder, times(1)).list(refCaptor.capture(), eq(1), eq(10)); + verify(commentPublicQueryService, times(1)).list(refCaptor.capture(), eq(1), eq(10), any()); Ref value = refCaptor.getValue(); assertThat(value).isEqualTo(ref); } @Test void getComment() { - when(commentFinder.getByName(any())) + when(commentPublicQueryService.getByName(any())) .thenReturn(null); webTestClient.get() @@ -104,12 +108,12 @@ class CommentFinderEndpointTest { .expectStatus() .isOk(); - verify(commentFinder, times(1)).getByName(eq("test-comment")); + verify(commentPublicQueryService, times(1)).getByName(eq("test-comment")); } @Test void listCommentReplies() { - when(commentFinder.listReply(any(), anyInt(), anyInt())) + when(commentPublicQueryService.listReply(any(), anyInt(), anyInt())) .thenReturn(Mono.just(new ListResult<>(2, 20, 0, List.of()))); webTestClient.get() @@ -121,7 +125,7 @@ class CommentFinderEndpointTest { .expectStatus() .isOk(); - verify(commentFinder, times(1)).listReply(eq("test-comment"), eq(2), eq(20)); + verify(commentPublicQueryService, times(1)).listReply(eq("test-comment"), eq(2), eq(20)); } @Test diff --git a/application/src/test/java/run/halo/app/theme/finders/impl/CommentFinderImplTest.java b/application/src/test/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImplTest.java similarity index 96% rename from application/src/test/java/run/halo/app/theme/finders/impl/CommentFinderImplTest.java rename to application/src/test/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImplTest.java index 2cd4308f1..5ccc83366 100644 --- a/application/src/test/java/run/halo/app/theme/finders/impl/CommentFinderImplTest.java +++ b/application/src/test/java/run/halo/app/theme/finders/impl/CommentPublicQueryServiceImplTest.java @@ -43,7 +43,7 @@ import run.halo.app.metrics.CounterService; * @since 2.0.0 */ @ExtendWith(SpringExtension.class) -class CommentFinderImplTest { +class CommentPublicQueryServiceImplTest { @Mock private ReactiveExtensionClient client; @@ -54,7 +54,7 @@ class CommentFinderImplTest { private CounterService counterService; @InjectMocks - private CommentFinderImpl commentFinder; + private CommentPublicQueryServiceImpl commentPublicQueryService; @BeforeEach void setUp() { @@ -72,7 +72,7 @@ class CommentFinderImplTest { mockWhenListComment(); Ref ref = Ref.of("fake-post", GroupVersionKind.fromExtension(Post.class)); - commentFinder.list(ref, 1, 10) + commentPublicQueryService.list(ref, 1, 10) .as(StepVerifier::create) .consumeNextWith(listResult -> { assertThat(listResult.getTotal()).isEqualTo(2); @@ -90,7 +90,7 @@ class CommentFinderImplTest { mockWhenListComment(); Ref ref = Ref.of("fake-post", GroupVersionKind.fromExtension(Post.class)); - commentFinder.list(ref, 1, 10) + commentPublicQueryService.list(ref, 1, 10) .as(StepVerifier::create) .consumeNextWith(listResult -> { assertThat(listResult.getTotal()).isEqualTo(2); @@ -108,7 +108,7 @@ class CommentFinderImplTest { mockWhenListComment(); Ref ref = Ref.of("fake-post", GroupVersionKind.fromExtension(Post.class)); - commentFinder.list(ref, 1, 10) + commentPublicQueryService.list(ref, 1, 10) .as(StepVerifier::create) .consumeNextWith(listResult -> { assertThat(listResult.getTotal()).isEqualTo(3); @@ -156,7 +156,7 @@ class CommentFinderImplTest { var result = Stream.of(comment1, comment2, comment3, comment4, comment5, comment6, comment7, comment8, comment9, comment10, comment11, comment12, comment13, comment14) - .sorted(CommentFinderImpl.defaultComparator()) + .sorted(CommentPublicQueryServiceImpl.defaultComparator()) .map(Comment::getMetadata) .map(MetadataOperator::getName) .collect(Collectors.joining(", ")); @@ -265,7 +265,7 @@ class CommentFinderImplTest { // Mock mockWhenListRely(); - commentFinder.listReply("fake-comment", 1, 10) + commentPublicQueryService.listReply("fake-comment", 1, 10) .as(StepVerifier::create) .consumeNextWith(listResult -> { assertThat(listResult.getTotal()).isEqualTo(2); @@ -283,7 +283,7 @@ class CommentFinderImplTest { // Mock mockWhenListRely(); - commentFinder.listReply("fake-comment", 1, 10) + commentPublicQueryService.listReply("fake-comment", 1, 10) .as(StepVerifier::create) .consumeNextWith(listResult -> { assertThat(listResult.getTotal()).isEqualTo(2); @@ -299,7 +299,7 @@ class CommentFinderImplTest { void listWhenUserLoggedIn() { mockWhenListRely(); - commentFinder.listReply("fake-comment", 1, 10) + commentPublicQueryService.listReply("fake-comment", 1, 10) .as(StepVerifier::create) .consumeNextWith(listResult -> { assertThat(listResult.getTotal()).isEqualTo(3);