mirror of https://github.com/halo-dev/halo
refactor: optimize comment and reply logic to reduce duplicate code (#6542)
#### What type of PR is this? /kind improvement /area core /milestone 2.19.x #### What this PR does / why we need it: 优化评论和回复的逻辑并减少重复代码 #### Does this PR introduce a user-facing change? ```release-note None ```pull/6548/head
parent
0a13981c0d
commit
ad267ebed7
|
@ -0,0 +1,77 @@
|
|||
package run.halo.app.content.comment;
|
||||
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.User;
|
||||
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.ReactiveExtensionClient;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
import run.halo.app.security.authorization.AuthorityUtils;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractCommentService {
|
||||
protected final RoleService roleService;
|
||||
protected final ReactiveExtensionClient client;
|
||||
protected final UserService userService;
|
||||
protected final CounterService counterService;
|
||||
|
||||
protected Mono<User> fetchCurrentUser() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(securityContext -> securityContext.getAuthentication().getName())
|
||||
.flatMap(username -> client.fetch(User.class, username));
|
||||
}
|
||||
|
||||
Mono<Boolean> hasCommentManagePermission() {
|
||||
return 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));
|
||||
});
|
||||
}
|
||||
|
||||
protected Comment.CommentOwner toCommentOwner(User user) {
|
||||
Comment.CommentOwner owner = new Comment.CommentOwner();
|
||||
owner.setKind(User.KIND);
|
||||
owner.setName(user.getMetadata().getName());
|
||||
owner.setDisplayName(user.getSpec().getDisplayName());
|
||||
return owner;
|
||||
}
|
||||
|
||||
protected Mono<OwnerInfo> getOwnerInfo(Comment.CommentOwner owner) {
|
||||
if (User.KIND.equals(owner.getKind())) {
|
||||
return userService.getUserOrGhost(owner.getName())
|
||||
.map(OwnerInfo::from);
|
||||
}
|
||||
if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) {
|
||||
return Mono.just(OwnerInfo.from(owner));
|
||||
}
|
||||
return Mono.error(new IllegalStateException("Unsupported owner kind: " + owner.getKind()));
|
||||
}
|
||||
|
||||
protected Mono<CommentStats> fetchCommentStats(String commentName) {
|
||||
return this.fetchStats(MeterUtils.nameOf(Comment.class, commentName));
|
||||
}
|
||||
|
||||
protected Mono<CommentStats> fetchReplyStats(String replyName) {
|
||||
return this.fetchStats(MeterUtils.nameOf(Reply.class, replyName));
|
||||
}
|
||||
|
||||
private Mono<CommentStats> fetchStats(String meterName) {
|
||||
Assert.notNull(meterName, "The reply must not be null.");
|
||||
return counterService.getByName(meterName)
|
||||
.map(counter -> CommentStats.builder()
|
||||
.upvote(counter.getUpvote())
|
||||
.build()
|
||||
)
|
||||
.switchIfEmpty(Mono.fromSupplier(CommentStats::empty));
|
||||
}
|
||||
}
|
|
@ -6,19 +6,16 @@ import static run.halo.app.extension.index.query.QueryFactory.isNull;
|
|||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.core.extension.service.UserService;
|
||||
|
@ -33,9 +30,7 @@ import run.halo.app.extension.router.selector.FieldSelector;
|
|||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.exception.AccessDeniedException;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||
import run.halo.app.security.authorization.AuthorityUtils;
|
||||
|
||||
/**
|
||||
* Comment service implementation.
|
||||
|
@ -44,28 +39,17 @@ import run.halo.app.security.authorization.AuthorityUtils;
|
|||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
public class CommentServiceImpl implements CommentService {
|
||||
public class CommentServiceImpl extends AbstractCommentService implements CommentService {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final UserService userService;
|
||||
private final RoleService roleService;
|
||||
private final ExtensionGetter extensionGetter;
|
||||
|
||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
private final CounterService counterService;
|
||||
|
||||
public CommentServiceImpl(ReactiveExtensionClient client,
|
||||
UserService userService,
|
||||
SystemConfigurableEnvironmentFetcher environmentFetcher,
|
||||
CounterService counterService, RoleService roleService,
|
||||
ExtensionGetter extensionGetter
|
||||
) {
|
||||
this.client = client;
|
||||
this.userService = userService;
|
||||
this.environmentFetcher = environmentFetcher;
|
||||
this.counterService = counterService;
|
||||
this.roleService = roleService;
|
||||
public CommentServiceImpl(RoleService roleService, ReactiveExtensionClient client,
|
||||
UserService userService, CounterService counterService, ExtensionGetter extensionGetter,
|
||||
SystemConfigurableEnvironmentFetcher environmentFetcher) {
|
||||
super(roleService, client, userService, counterService);
|
||||
this.extensionGetter = extensionGetter;
|
||||
this.environmentFetcher = environmentFetcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -116,38 +100,36 @@ public class CommentServiceImpl implements CommentService {
|
|||
}
|
||||
|
||||
comment.getSpec().setHidden(false);
|
||||
|
||||
// return if the comment owner is not null
|
||||
if (comment.getSpec().getOwner() != null) {
|
||||
return Mono.just(comment);
|
||||
}
|
||||
// populate owner from current user
|
||||
return fetchCurrentUser()
|
||||
.flatMap(currentUser -> 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) {
|
||||
comment.getSpec().setApproved(true);
|
||||
comment.getSpec().setApprovedTime(Instant.now());
|
||||
}
|
||||
})
|
||||
.thenReturn(toCommentOwner(currentUser));
|
||||
}))
|
||||
.map(owner -> {
|
||||
comment.getSpec().setOwner(owner);
|
||||
return comment;
|
||||
})
|
||||
.switchIfEmpty(
|
||||
Mono.error(new IllegalStateException("The owner must not be null.")));
|
||||
return Mono.just(comment);
|
||||
})
|
||||
.flatMap(populatedComment -> Mono.when(populateOwner(populatedComment),
|
||||
populateApproveState(populatedComment))
|
||||
.thenReturn(populatedComment)
|
||||
)
|
||||
.flatMap(client::create);
|
||||
}
|
||||
|
||||
private Mono<Void> populateApproveState(Comment comment) {
|
||||
return hasCommentManagePermission()
|
||||
.filter(Boolean::booleanValue)
|
||||
.doOnNext(hasPermission -> {
|
||||
comment.getSpec().setApproved(true);
|
||||
comment.getSpec().setApprovedTime(Instant.now());
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
Mono<Void> populateOwner(Comment comment) {
|
||||
if (comment.getSpec().getOwner() != null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return fetchCurrentUser()
|
||||
.switchIfEmpty(Mono.error(new IllegalStateException("The owner must not be null.")))
|
||||
.map(this::toCommentOwner)
|
||||
.doOnNext(owner -> comment.getSpec().setOwner(owner))
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> removeBySubject(@NonNull Ref subjectRef) {
|
||||
Assert.notNull(subjectRef, "The subjectRef must not be null.");
|
||||
|
@ -197,55 +179,19 @@ public class CommentServiceImpl implements CommentService {
|
|||
return false;
|
||||
}
|
||||
|
||||
private Comment.CommentOwner toCommentOwner(User user) {
|
||||
Comment.CommentOwner owner = new Comment.CommentOwner();
|
||||
owner.setKind(User.KIND);
|
||||
owner.setName(user.getMetadata().getName());
|
||||
owner.setDisplayName(user.getSpec().getDisplayName());
|
||||
return owner;
|
||||
}
|
||||
|
||||
private Mono<User> fetchCurrentUser() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(securityContext -> securityContext.getAuthentication().getName())
|
||||
.flatMap(username -> client.fetch(User.class, username));
|
||||
}
|
||||
|
||||
private Mono<ListedComment> toListedComment(Comment comment) {
|
||||
var builder = ListedComment.builder().comment(comment);
|
||||
// not empty
|
||||
var ownerInfoMono = getCommentOwnerInfo(comment.getSpec().getOwner())
|
||||
var ownerInfoMono = getOwnerInfo(comment.getSpec().getOwner())
|
||||
.doOnNext(builder::owner);
|
||||
var subjectMono = getCommentSubject(comment.getSpec().getSubjectRef())
|
||||
.doOnNext(builder::subject);
|
||||
var statsMono = fetchStats(comment.getMetadata().getName())
|
||||
var statsMono = fetchCommentStats(comment.getMetadata().getName())
|
||||
.doOnNext(builder::stats);
|
||||
return Mono.when(ownerInfoMono, subjectMono, statsMono)
|
||||
.then(Mono.fromSupplier(builder::build));
|
||||
}
|
||||
|
||||
Mono<CommentStats> fetchStats(String commentName) {
|
||||
Assert.notNull(commentName, "The commentName must not be null.");
|
||||
return counterService.getByName(MeterUtils.nameOf(Comment.class, commentName))
|
||||
.map(counter -> CommentStats.builder()
|
||||
.upvote(counter.getUpvote())
|
||||
.build()
|
||||
)
|
||||
.defaultIfEmpty(CommentStats.empty());
|
||||
}
|
||||
|
||||
private Mono<OwnerInfo> getCommentOwnerInfo(Comment.CommentOwner owner) {
|
||||
if (User.KIND.equals(owner.getKind())) {
|
||||
return userService.getUserOrGhost(owner.getName())
|
||||
.map(OwnerInfo::from);
|
||||
}
|
||||
if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) {
|
||||
return Mono.just(OwnerInfo.from(owner));
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Unsupported owner kind: " + owner.getKind());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Mono<Extension> getCommentSubject(Ref ref) {
|
||||
return extensionGetter.getExtensions(CommentSubject.class)
|
||||
|
|
|
@ -3,29 +3,22 @@ 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.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.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;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.core.extension.content.Reply;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
|
@ -37,7 +30,6 @@ import run.halo.app.extension.PageRequestImpl;
|
|||
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;
|
||||
|
||||
/**
|
||||
* A default implementation of {@link ReplyService}.
|
||||
|
@ -46,13 +38,12 @@ import run.halo.app.metrics.MeterUtils;
|
|||
* @since 2.0.0
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ReplyServiceImpl implements ReplyService {
|
||||
public class ReplyServiceImpl extends AbstractCommentService implements ReplyService {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final UserService userService;
|
||||
private final RoleService roleService;
|
||||
private final CounterService counterService;
|
||||
public ReplyServiceImpl(RoleService roleService, ReactiveExtensionClient client,
|
||||
UserService userService, CounterService counterService) {
|
||||
super(roleService, client, userService, counterService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Reply> create(String commentName, Reply reply) {
|
||||
|
@ -74,6 +65,12 @@ public class ReplyServiceImpl implements ReplyService {
|
|||
}
|
||||
|
||||
private Mono<Comment> approveComment(Comment comment) {
|
||||
return hasCommentManagePermission()
|
||||
.filter(Boolean::booleanValue)
|
||||
.flatMap(hasPermission -> doApproveComment(comment));
|
||||
}
|
||||
|
||||
private Mono<Comment> doApproveComment(Comment comment) {
|
||||
UnaryOperator<Comment> updateFunc = commentToUpdate -> {
|
||||
commentToUpdate.getSpec().setApproved(true);
|
||||
commentToUpdate.getSpec().setApprovedTime(Instant.now());
|
||||
|
@ -85,6 +82,12 @@ public class ReplyServiceImpl implements ReplyService {
|
|||
}
|
||||
|
||||
private Mono<Void> approveReply(String replyName) {
|
||||
return hasCommentManagePermission()
|
||||
.filter(Boolean::booleanValue)
|
||||
.flatMap(hasPermission -> doApproveReply(replyName));
|
||||
}
|
||||
|
||||
private Mono<Void> doApproveReply(String replyName) {
|
||||
return Mono.defer(() -> client.fetch(Reply.class, replyName)
|
||||
.flatMap(reply -> {
|
||||
reply.getSpec().setApproved(true);
|
||||
|
@ -144,15 +147,6 @@ public class ReplyServiceImpl implements ReplyService {
|
|||
return Mono.when(steps).thenReturn(reply);
|
||||
}
|
||||
|
||||
Mono<Boolean> 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
|
||||
public Mono<ListResult<ListedReply>> list(ReplyQuery query) {
|
||||
return client.listBy(Reply.class, query.toListOptions(), query.toPageRequest())
|
||||
|
@ -209,52 +203,14 @@ public class ReplyServiceImpl implements ReplyService {
|
|||
private Mono<ListedReply> toListedReply(Reply reply) {
|
||||
ListedReply.ListedReplyBuilder builder = ListedReply.builder()
|
||||
.reply(reply);
|
||||
return getOwnerInfo(reply)
|
||||
return getOwnerInfo(reply.getSpec().getOwner())
|
||||
.map(ownerInfo -> {
|
||||
builder.owner(ownerInfo);
|
||||
return builder;
|
||||
})
|
||||
.map(ListedReply.ListedReplyBuilder::build)
|
||||
.flatMap(listedReply -> fetchStats(reply)
|
||||
.flatMap(listedReply -> fetchReplyStats(reply.getMetadata().getName())
|
||||
.doOnNext(listedReply::setStats)
|
||||
.thenReturn(listedReply));
|
||||
}
|
||||
|
||||
Mono<CommentStats> fetchStats(Reply reply) {
|
||||
Assert.notNull(reply, "The reply must not be null.");
|
||||
String name = reply.getMetadata().getName();
|
||||
return counterService.getByName(MeterUtils.nameOf(Reply.class, name))
|
||||
.map(counter -> CommentStats.builder()
|
||||
.upvote(counter.getUpvote())
|
||||
.build()
|
||||
)
|
||||
.defaultIfEmpty(CommentStats.empty());
|
||||
}
|
||||
|
||||
private Mono<OwnerInfo> getOwnerInfo(Reply reply) {
|
||||
Comment.CommentOwner owner = reply.getSpec().getOwner();
|
||||
if (User.KIND.equals(owner.getKind())) {
|
||||
return userService.getUserOrGhost(owner.getName())
|
||||
.map(OwnerInfo::from);
|
||||
}
|
||||
if (Comment.CommentOwner.KIND_EMAIL.equals(owner.getKind())) {
|
||||
return Mono.just(OwnerInfo.from(owner));
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Unsupported owner kind: " + owner.getKind());
|
||||
}
|
||||
|
||||
private Comment.CommentOwner toCommentOwner(User user) {
|
||||
Comment.CommentOwner owner = new Comment.CommentOwner();
|
||||
owner.setKind(User.KIND);
|
||||
owner.setName(user.getMetadata().getName());
|
||||
owner.setDisplayName(user.getSpec().getDisplayName());
|
||||
return owner;
|
||||
}
|
||||
|
||||
private Mono<User> fetchCurrentUser() {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(securityContext -> securityContext.getAuthentication().getName())
|
||||
.flatMap(username -> client.fetch(User.class, username));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue