mirror of https://github.com/halo-dev/halo
feat: add stats to the user-sdie comments api (#3366)
#### What type of PR is this? /milestone 2.3.x /kind feature #### What this PR does / why we need it: 为访客端的评论和回复接口 聚合点赞数 #### Which issue(s) this PR fixes: Fixes #3347 #### Special notes for your reviewer: 同步修改了 finder API 及 console 位置的接口 如何测试: 1. 调用接口 `/apis/api.halo.run/v1alpha1/trackers/upvote` 增加点赞数。 2. 使用 console 接口 `/apis/api.halo.run/v1alpha1/comments/{name}` 查看目标评论是否增加点赞数。 #### Does this PR introduce a user-facing change? ```release-note 访客端评论及回复列表支持返回点赞数据 ```pull/3390/head^2
parent
848857fbfd
commit
1a9e2f046a
|
@ -11,6 +11,7 @@ import org.apache.commons.lang3.BooleanUtils;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
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 run.halo.app.core.extension.User;
|
||||
|
@ -22,6 +23,8 @@ import run.halo.app.extension.ReactiveExtensionClient;
|
|||
import run.halo.app.extension.Ref;
|
||||
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.ExtensionComponentsFinder;
|
||||
|
||||
/**
|
||||
|
@ -38,14 +41,17 @@ public class CommentServiceImpl implements CommentService {
|
|||
private final ExtensionComponentsFinder extensionComponentsFinder;
|
||||
|
||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
private final CounterService counterService;
|
||||
|
||||
public CommentServiceImpl(ReactiveExtensionClient client,
|
||||
UserService userService, ExtensionComponentsFinder extensionComponentsFinder,
|
||||
SystemConfigurableEnvironmentFetcher environmentFetcher) {
|
||||
SystemConfigurableEnvironmentFetcher environmentFetcher,
|
||||
CounterService counterService) {
|
||||
this.client = client;
|
||||
this.userService = userService;
|
||||
this.extensionComponentsFinder = extensionComponentsFinder;
|
||||
this.environmentFetcher = environmentFetcher;
|
||||
this.counterService = counterService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -151,7 +157,21 @@ public class CommentServiceImpl implements CommentService {
|
|||
})
|
||||
.switchIfEmpty(Mono.just(builder))
|
||||
)
|
||||
.map(ListedComment.ListedCommentBuilder::build);
|
||||
.map(ListedComment.ListedCommentBuilder::build)
|
||||
.flatMap(lc -> fetchStats(comment)
|
||||
.doOnNext(lc::setStats)
|
||||
.thenReturn(lc));
|
||||
}
|
||||
|
||||
Mono<CommentStats> fetchStats(Comment comment) {
|
||||
Assert.notNull(comment, "The comment must not be null.");
|
||||
String name = comment.getMetadata().getName();
|
||||
return counterService.getByName(MeterUtils.nameOf(Comment.class, name))
|
||||
.map(counter -> CommentStats.builder()
|
||||
.upvote(counter.getUpvote())
|
||||
.build()
|
||||
)
|
||||
.defaultIfEmpty(CommentStats.empty());
|
||||
}
|
||||
|
||||
private Mono<OwnerInfo> getCommentOwnerInfo(Comment.CommentOwner owner) {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package run.halo.app.content.comment;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* comment stats value object.
|
||||
*
|
||||
* @author LIlGG
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class CommentStats {
|
||||
|
||||
Integer upvote;
|
||||
|
||||
public static CommentStats empty() {
|
||||
return CommentStats.builder()
|
||||
.upvote(0)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ package run.halo.app.content.comment;
|
|||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.Data;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.extension.Extension;
|
||||
|
||||
|
@ -12,15 +12,18 @@ import run.halo.app.extension.Extension;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Data
|
||||
@Builder
|
||||
public class ListedComment {
|
||||
|
||||
@Schema(required = true)
|
||||
Comment comment;
|
||||
private Comment comment;
|
||||
|
||||
@Schema(required = true)
|
||||
OwnerInfo owner;
|
||||
private OwnerInfo owner;
|
||||
|
||||
Extension subject;
|
||||
private Extension subject;
|
||||
|
||||
@Schema(required = true)
|
||||
private CommentStats stats;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package run.halo.app.content.comment;
|
|||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import lombok.Data;
|
||||
import run.halo.app.core.extension.content.Reply;
|
||||
|
||||
/**
|
||||
|
@ -11,13 +11,16 @@ import run.halo.app.core.extension.content.Reply;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Data
|
||||
@Builder
|
||||
public class ListedReply {
|
||||
|
||||
@Schema(required = true)
|
||||
Reply reply;
|
||||
private Reply reply;
|
||||
|
||||
@Schema(required = true)
|
||||
OwnerInfo owner;
|
||||
private OwnerInfo owner;
|
||||
|
||||
@Schema(required = true)
|
||||
private CommentStats stats;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import lombok.RequiredArgsConstructor;
|
|||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.User;
|
||||
|
@ -18,6 +19,8 @@ import run.halo.app.core.extension.service.UserService;
|
|||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
|
||||
/**
|
||||
* A default implementation of {@link ReplyService}.
|
||||
|
@ -31,6 +34,7 @@ public class ReplyServiceImpl implements ReplyService {
|
|||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final UserService userService;
|
||||
private final CounterService counterService;
|
||||
|
||||
@Override
|
||||
public Mono<Reply> create(String commentName, Reply reply) {
|
||||
|
@ -95,7 +99,21 @@ public class ReplyServiceImpl implements ReplyService {
|
|||
builder.owner(ownerInfo);
|
||||
return builder;
|
||||
})
|
||||
.map(ListedReply.ListedReplyBuilder::build);
|
||||
.map(ListedReply.ListedReplyBuilder::build)
|
||||
.flatMap(listedReply -> fetchStats(reply)
|
||||
.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) {
|
||||
|
|
|
@ -20,13 +20,18 @@ 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.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;
|
||||
|
||||
/**
|
||||
|
@ -41,6 +46,7 @@ public class CommentFinderImpl implements CommentFinder {
|
|||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final UserService userService;
|
||||
private final CounterService counterService;
|
||||
|
||||
@Override
|
||||
public Mono<CommentVo> getByName(String name) {
|
||||
|
@ -88,17 +94,34 @@ public class CommentFinderImpl implements CommentFinder {
|
|||
private Mono<CommentVo> 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)
|
||||
.map(commentVo::withOwner)
|
||||
.defaultIfEmpty(commentVo)
|
||||
.doOnNext(commentVo::setOwner)
|
||||
.thenReturn(commentVo)
|
||||
);
|
||||
}
|
||||
|
||||
private <E extends AbstractExtension, T extends ExtensionVoOperator> Mono<CommentStatsVo>
|
||||
populateStats(Class<E> 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<ReplyVo> 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())
|
||||
.map(replyVo::withOwner)
|
||||
.defaultIfEmpty(replyVo)
|
||||
.doOnNext(replyVo::setOwner)
|
||||
.thenReturn(replyVo)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package run.halo.app.theme.finders.vo;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
|
||||
/**
|
||||
* comment stats value object.
|
||||
*
|
||||
* @author LIlGG
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class CommentStatsVo {
|
||||
Integer upvote;
|
||||
|
||||
public static CommentStatsVo empty() {
|
||||
return CommentStatsVo.builder()
|
||||
.upvote(0)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -2,9 +2,8 @@ package run.halo.app.theme.finders.vo;
|
|||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
import run.halo.app.content.comment.OwnerInfo;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
|
@ -15,22 +14,24 @@ import run.halo.app.extension.MetadataOperator;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Data
|
||||
@Builder
|
||||
@EqualsAndHashCode
|
||||
public class CommentVo implements ExtensionVoOperator {
|
||||
|
||||
@Schema(required = true)
|
||||
MetadataOperator metadata;
|
||||
private MetadataOperator metadata;
|
||||
|
||||
@Schema(required = true)
|
||||
Comment.CommentSpec spec;
|
||||
private Comment.CommentSpec spec;
|
||||
|
||||
Comment.CommentStatus status;
|
||||
private Comment.CommentStatus status;
|
||||
|
||||
@With
|
||||
@Schema(required = true)
|
||||
OwnerInfo owner;
|
||||
private OwnerInfo owner;
|
||||
|
||||
@Schema(required = true)
|
||||
private CommentStatsVo stats;
|
||||
|
||||
/**
|
||||
* Convert {@link Comment} to {@link CommentVo}.
|
||||
|
|
|
@ -2,10 +2,9 @@ package run.halo.app.theme.finders.vo;
|
|||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
import run.halo.app.content.comment.OwnerInfo;
|
||||
import run.halo.app.core.extension.content.Reply;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
|
@ -16,21 +15,23 @@ import run.halo.app.extension.MetadataOperator;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Data
|
||||
@Builder
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
public class ReplyVo implements ExtensionVoOperator {
|
||||
|
||||
@Schema(required = true)
|
||||
MetadataOperator metadata;
|
||||
private MetadataOperator metadata;
|
||||
|
||||
@Schema(required = true)
|
||||
Reply.ReplySpec spec;
|
||||
private Reply.ReplySpec spec;
|
||||
|
||||
@With
|
||||
@Schema(required = true)
|
||||
OwnerInfo owner;
|
||||
private OwnerInfo owner;
|
||||
|
||||
@Schema(required = true)
|
||||
private CommentStatsVo stats;
|
||||
|
||||
/**
|
||||
* Convert {@link Reply} to {@link ReplyVo}.
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.springframework.util.MultiValueMap;
|
|||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.content.TestPost;
|
||||
import run.halo.app.core.extension.Counter;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
|
@ -39,6 +40,8 @@ import run.halo.app.extension.Ref;
|
|||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
import run.halo.app.metrics.MeterUtils;
|
||||
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||
|
||||
/**
|
||||
|
@ -65,6 +68,9 @@ class CommentServiceImplTest {
|
|||
@InjectMocks
|
||||
private CommentServiceImpl commentService;
|
||||
|
||||
@Mock
|
||||
private CounterService counterService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
SystemSetting.Comment commentSetting = getCommentSetting();
|
||||
|
@ -111,6 +117,21 @@ class CommentServiceImplTest {
|
|||
|
||||
Mono<ListResult<ListedComment>> listResultMono =
|
||||
commentService.listComment(new CommentQuery(new LinkedMultiValueMap<>()));
|
||||
Counter counterA = new Counter();
|
||||
counterA.setUpvote(3);
|
||||
String commentACounter = MeterUtils.nameOf(Comment.class, "A");
|
||||
when(counterService.getByName(eq(commentACounter))).thenReturn(Mono.just(counterA));
|
||||
|
||||
Counter counterB = new Counter();
|
||||
counterB.setUpvote(9);
|
||||
String commentBCounter = MeterUtils.nameOf(Comment.class, "B");
|
||||
when(counterService.getByName(eq(commentBCounter))).thenReturn(Mono.just(counterB));
|
||||
|
||||
Counter counterC = new Counter();
|
||||
counterC.setUpvote(0);
|
||||
String commentCCounter = MeterUtils.nameOf(Comment.class, "C");
|
||||
when(counterService.getByName(eq(commentCCounter))).thenReturn(Mono.just(counterC));
|
||||
|
||||
StepVerifier.create(listResultMono)
|
||||
.consumeNextWith(result -> {
|
||||
try {
|
||||
|
@ -320,6 +341,9 @@ class CommentServiceImplTest {
|
|||
"metadata": {
|
||||
"name": "fake-post"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"upvote": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -362,6 +386,9 @@ class CommentServiceImplTest {
|
|||
"metadata": {
|
||||
"name": "fake-post"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"upvote": 9
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -403,6 +430,9 @@ class CommentServiceImplTest {
|
|||
"metadata": {
|
||||
"name": "fake-post"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"upvote": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.springframework.security.test.context.support.WithMockUser;
|
|||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.Counter;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.content.Comment;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
|
@ -33,6 +34,7 @@ import run.halo.app.extension.MetadataOperator;
|
|||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.Ref;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.metrics.CounterService;
|
||||
|
||||
/**
|
||||
* Tests for {@link CommentFinderImpl}.
|
||||
|
@ -48,6 +50,9 @@ class CommentFinderImplTest {
|
|||
@Mock
|
||||
private UserService userService;
|
||||
|
||||
@Mock
|
||||
private CounterService counterService;
|
||||
|
||||
@InjectMocks
|
||||
private CommentFinderImpl commentFinder;
|
||||
|
||||
|
@ -92,6 +97,7 @@ class CommentFinderImplTest {
|
|||
assertThat(listResult.getItems().size()).isEqualTo(2);
|
||||
assertThat(listResult.getItems().get(0).getMetadata().getName())
|
||||
.isEqualTo("comment-approved");
|
||||
assertThat(listResult.getItems().get(0).getStats().getUpvote()).isEqualTo(9);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
@ -225,6 +231,10 @@ class CommentFinderImplTest {
|
|||
|
||||
extractedUser();
|
||||
when(client.fetch(eq(User.class), any())).thenReturn(Mono.just(createUser()));
|
||||
|
||||
Counter counter = new Counter();
|
||||
counter.setUpvote(9);
|
||||
when(counterService.getByName(any())).thenReturn(Mono.just(counter));
|
||||
}
|
||||
|
||||
Comment createComment() {
|
||||
|
@ -262,6 +272,7 @@ class CommentFinderImplTest {
|
|||
assertThat(listResult.getItems().size()).isEqualTo(2);
|
||||
assertThat(listResult.getItems().get(0).getMetadata().getName())
|
||||
.isEqualTo("reply-approved");
|
||||
assertThat(listResult.getItems().get(0).getStats().getUpvote()).isEqualTo(9);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
@ -355,6 +366,10 @@ class CommentFinderImplTest {
|
|||
|
||||
extractedUser();
|
||||
when(client.fetch(eq(User.class), any())).thenReturn(Mono.just(createUser()));
|
||||
|
||||
Counter counter = new Counter();
|
||||
counter.setUpvote(9);
|
||||
when(counterService.getByName(any())).thenReturn(Mono.just(counter));
|
||||
}
|
||||
|
||||
Reply createReply() {
|
||||
|
|
Loading…
Reference in New Issue