From 94d625fbb06b57ae50d3a035a52d82215d05b882 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Mon, 20 May 2024 16:10:41 +0800 Subject: [PATCH] refactor: automatically approve comments or replies after admin replied it (#5903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.16.x #### What this PR does / why we need it: 管理员回复评论或回复后自动通过审核 #### Which issue(s) this PR fixes: Fixes #5870 #### Does this PR introduce a user-facing change? ```release-note 管理员回复评论或回复后自动通过审核 ``` --- .../app/content/comment/ReplyServiceImpl.java | 166 +++++++++++------- 1 file changed, 100 insertions(+), 66 deletions(-) diff --git a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java index cb86677d0..5c32c966f 100644 --- a/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java +++ b/application/src/main/java/run/halo/app/content/comment/ReplyServiceImpl.java @@ -3,18 +3,23 @@ package run.halo.app.content.comment; import static run.halo.app.extension.index.query.QueryFactory.and; import static run.halo.app.extension.index.query.QueryFactory.equal; import static run.halo.app.extension.index.query.QueryFactory.isNull; -import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate; +import static run.halo.app.security.authorization.AuthorityUtils.COMMENT_MANAGEMENT_ROLE_NAME; +import static run.halo.app.security.authorization.AuthorityUtils.authoritiesToRoles; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.Set; import java.util.function.Function; -import java.util.function.Predicate; +import java.util.function.UnaryOperator; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.reactivestreams.Publisher; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Sort; import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import reactor.core.publisher.Flux; @@ -25,7 +30,6 @@ import run.halo.app.core.extension.content.Comment; import run.halo.app.core.extension.content.Reply; import run.halo.app.core.extension.service.RoleService; import run.halo.app.core.extension.service.UserService; -import run.halo.app.extension.Extension; import run.halo.app.extension.ListOptions; import run.halo.app.extension.ListResult; import run.halo.app.extension.PageRequest; @@ -34,7 +38,6 @@ import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.router.selector.FieldSelector; import run.halo.app.metrics.CounterService; import run.halo.app.metrics.MeterUtils; -import run.halo.app.security.authorization.AuthorityUtils; /** * A default implementation of {@link ReplyService}. @@ -54,56 +57,100 @@ public class ReplyServiceImpl implements ReplyService { @Override public Mono create(String commentName, Reply reply) { return client.get(Comment.class, commentName) - .map(comment -> { - // Boolean allowNotification = reply.getSpec().getAllowNotification(); - // TODO send notification if allowNotification is true - reply.getSpec().setCommentName(commentName); - if (reply.getSpec().getTop() == null) { - reply.getSpec().setTop(false); - } - if (reply.getSpec().getPriority() == null) { - reply.getSpec().setPriority(0); - } - if (reply.getSpec().getCreationTime() == null) { - reply.getSpec().setCreationTime(Instant.now()); - } - if (reply.getSpec().getApproved() == null) { - reply.getSpec().setApproved(false); - } - if (BooleanUtils.isTrue(reply.getSpec().getApproved()) - && reply.getSpec().getApprovedTime() == null) { + .flatMap(comment -> prepareReply(commentName, reply) + .flatMap(client::create) + .flatMap(createdReply -> { + var quotedReply = createdReply.getSpec().getQuoteReply(); + if (StringUtils.isBlank(quotedReply)) { + return Mono.just(createdReply); + } + return approveReply(quotedReply) + .thenReturn(createdReply); + }) + .flatMap(createdReply -> approveComment(comment) + .thenReturn(createdReply) + ) + ); + } + + private Mono approveComment(Comment comment) { + UnaryOperator updateFunc = commentToUpdate -> { + commentToUpdate.getSpec().setApproved(true); + commentToUpdate.getSpec().setApprovedTime(Instant.now()); + return commentToUpdate; + }; + return client.update(updateFunc.apply(comment)) + .onErrorResume(OptimisticLockingFailureException.class, + e -> updateCommentWithRetry(comment.getMetadata().getName(), updateFunc)); + } + + private Mono approveReply(String replyName) { + return Mono.defer(() -> client.fetch(Reply.class, replyName) + .flatMap(reply -> { + reply.getSpec().setApproved(true); reply.getSpec().setApprovedTime(Instant.now()); - } - return reply; - }) - .flatMap(replyToUse -> { - if (replyToUse.getSpec().getOwner() != null) { - return Mono.just(replyToUse); - } - // populate owner from current user - return fetchCurrentUser() - .flatMap(user -> - ReactiveSecurityContextHolder.getContext() - .flatMap(securityContext -> { - var authentication = securityContext.getAuthentication(); - var roles = AuthorityUtils.authoritiesToRoles( - authentication.getAuthorities()); - return roleService.contains(roles, - Set.of(AuthorityUtils.COMMENT_MANAGEMENT_ROLE_NAME)) - .doOnNext(result -> { - if (result) { - reply.getSpec().setApproved(true); - reply.getSpec().setApprovedTime(Instant.now()); - } - replyToUse.getSpec().setOwner(toCommentOwner(user)); - }) - .thenReturn(replyToUse); - }) - ) - .switchIfEmpty( - Mono.error(new IllegalArgumentException("Reply owner must not be null."))); - }) - .flatMap(client::create); + return client.update(reply); + }) + ) + .retryWhen(Retry.backoff(8, Duration.ofMillis(100)) + .filter(OptimisticLockingFailureException.class::isInstance)) + .then(); + } + + private Mono updateCommentWithRetry(String name, UnaryOperator updateFunc) { + return Mono.defer(() -> client.get(Comment.class, name) + .map(updateFunc) + .flatMap(client::update) + ) + .retryWhen(Retry.backoff(8, Duration.ofMillis(100)) + .filter(OptimisticLockingFailureException.class::isInstance)); + } + + private Mono prepareReply(String commentName, Reply reply) { + reply.getSpec().setCommentName(commentName); + if (reply.getSpec().getTop() == null) { + reply.getSpec().setTop(false); + } + if (reply.getSpec().getPriority() == null) { + reply.getSpec().setPriority(0); + } + if (reply.getSpec().getCreationTime() == null) { + reply.getSpec().setCreationTime(Instant.now()); + } + if (reply.getSpec().getApproved() == null) { + reply.getSpec().setApproved(false); + } + if (BooleanUtils.isTrue(reply.getSpec().getApproved()) + && reply.getSpec().getApprovedTime() == null) { + reply.getSpec().setApprovedTime(Instant.now()); + } + + var steps = new ArrayList>(); + var approveItMono = hasCommentManagePermission() + .filter(Boolean::booleanValue) + .doOnNext(hasPermission -> { + reply.getSpec().setApproved(true); + reply.getSpec().setApprovedTime(Instant.now()); + }); + steps.add(approveItMono); + + var populateOwnerMono = fetchCurrentUser() + .switchIfEmpty( + Mono.error(new IllegalArgumentException("Reply owner must not be null."))) + .doOnNext(user -> reply.getSpec().setOwner(toCommentOwner(user))); + if (reply.getSpec().getOwner() == null) { + steps.add(populateOwnerMono); + } + return Mono.when(steps).thenReturn(reply); + } + + Mono hasCommentManagePermission() { + return ReactiveSecurityContextHolder.getContext() + .map(SecurityContext::getAuthentication) + .flatMap(authentication -> { + var roles = authoritiesToRoles(authentication.getAuthorities()); + return roleService.contains(roles, Set.of(COMMENT_MANAGEMENT_ROLE_NAME)); + }); } @Override @@ -197,19 +244,6 @@ public class ReplyServiceImpl implements ReplyService { "Unsupported owner kind: " + owner.getKind()); } - Predicate getReplyPredicate(ReplyQuery query) { - Predicate predicate = reply -> true; - if (query.getCommentName() != null) { - predicate = predicate.and( - reply -> query.getCommentName().equals(reply.getSpec().getCommentName())); - } - - Predicate labelAndFieldSelectorPredicate = - labelAndFieldSelectorToPredicate(query.getLabelSelector(), - query.getFieldSelector()); - return predicate.and(labelAndFieldSelectorPredicate); - } - private Comment.CommentOwner toCommentOwner(User user) { Comment.CommentOwner owner = new Comment.CommentOwner(); owner.setKind(User.KIND);