Complete comment tree view api

pull/137/head
johnniang 2019-03-25 18:39:12 +08:00
parent 8935f7478a
commit 3a1bf64636
7 changed files with 232 additions and 5 deletions

View File

@ -0,0 +1,34 @@
package cc.ryanc.halo.model.support;
import lombok.Data;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import java.util.List;
/**
* Comment page implementation.
*
* @author johnniang
* @date 3/25/19
*/
@Data
public class CommentPage<T> extends PageImpl<T> {
/**
* Total comment (Contains sub comments)
*/
private final long commentCount;
public CommentPage(List<T> content, Pageable pageable, long topTotal, long commentCount) {
super(content, pageable, topTotal);
this.commentCount = commentCount;
}
public CommentPage(List<T> content, long commentCount) {
super(content);
this.commentCount = commentCount;
}
}

View File

@ -0,0 +1,21 @@
package cc.ryanc.halo.model.vo;
import cc.ryanc.halo.model.dto.CommentOutputDTO;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/**
* Comment vo.
*
* @author johnniang
* @date 3/25/19
*/
@Data
@ToString(callSuper = true)
public class CommentVO extends CommentOutputDTO {
private List<CommentVO> children;
}

View File

@ -48,7 +48,32 @@ public interface CommentRepository extends BaseRepository<Comment, Long> {
@NonNull
List<Comment> findAllByPostId(@NonNull Integer postId);
/**
* Counts comment count by post id collection.
*
* @param postIds post id collection must not be null
* @return a list of comment count
*/
@Query("select new cc.ryanc.halo.model.projection.CommentCountProjection(count(comment.id), comment.postId) from Comment comment where comment.postId in ?1 group by comment.postId")
@NonNull
List<CommentCountProjection> countByPostIds(@NonNull Iterable<Integer> postIds);
/**
* Finds comments by post id, comment status.
*
* @param postId post id must not be null
* @param status comment status must not be null
* @return a list of comment
*/
List<Comment> findAllByPostIdAndStatus(Integer postId, CommentStatus status);
/**
* Finds comments by post id, comment status.
*
* @param postId post id must not be null
* @param status comment status must not be null
* @param pageable page info must not be null
* @return a page of comment
*/
Page<Comment> findAllByPostIdAndStatus(Integer postId, CommentStatus status, Pageable pageable);
}

View File

@ -3,6 +3,7 @@ package cc.ryanc.halo.service;
import cc.ryanc.halo.model.entity.Comment;
import cc.ryanc.halo.model.enums.CommentStatus;
import cc.ryanc.halo.model.vo.CommentListVO;
import cc.ryanc.halo.model.vo.CommentVO;
import cc.ryanc.halo.service.base.CrudService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@ -74,4 +75,14 @@ public interface CommentService extends CrudService<Comment, Long> {
*/
@NonNull
Comment createBy(@NonNull Comment comment, @NonNull HttpServletRequest request);
/**
* Lists comment vos by post id.
*
* @param postId post id must not be null
* @param pageable page info must not be null
* @return a page of comment vo
*/
@NonNull
Page<CommentVO> pageVosBy(@NonNull Integer postId, @NonNull Pageable pageable);
}

View File

@ -6,7 +6,9 @@ import cc.ryanc.halo.model.entity.Post;
import cc.ryanc.halo.model.enums.BlogProperties;
import cc.ryanc.halo.model.enums.CommentStatus;
import cc.ryanc.halo.model.projection.CommentCountProjection;
import cc.ryanc.halo.model.support.CommentPage;
import cc.ryanc.halo.model.vo.CommentListVO;
import cc.ryanc.halo.model.vo.CommentVO;
import cc.ryanc.halo.repository.CommentRepository;
import cc.ryanc.halo.repository.PostRepository;
import cc.ryanc.halo.service.CommentService;
@ -20,6 +22,7 @@ import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.*;
import org.springframework.data.domain.Sort.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
@ -131,6 +134,113 @@ public class CommentServiceImpl extends AbstractCrudService<Comment, Long> imple
return createdComment;
}
@Override
public Page<CommentVO> pageVosBy(Integer postId, Pageable pageable) {
Assert.notNull(postId, "Post id must not be null");
Assert.notNull(pageable, "Page info must not be null");
log.debug("Getting comment of post: [{}], page info: [{}]", postId, pageable);
// List all the top comments (Caution: This list will be cleared)
List<Comment> comments = commentRepository.findAllByPostIdAndStatus(postId, CommentStatus.PUBLISHED);
// Init the top virtual comment
CommentVO topVirtualComment = new CommentVO();
topVirtualComment.setId(0L);
topVirtualComment.setChildren(new LinkedList<>());
// Concrete the comment tree
concreteTree(topVirtualComment, new LinkedList<>(comments), buildCommentComparator(pageable.getSortOr(Sort.by(Sort.Direction.DESC, "createTime"))));
List<CommentVO> topComments = topVirtualComment.getChildren();
List<CommentVO> pageContent = null;
// Calc the shear index
int startIndex = pageable.getPageNumber() * pageable.getPageSize();
if (startIndex >= topComments.size() || startIndex < 0) {
pageContent = Collections.emptyList();
} else {
int endIndex = startIndex + pageable.getPageSize();
if (endIndex > topComments.size()) {
endIndex = topComments.size();
}
log.debug("Top comments size: [{}]", topComments.size());
log.debug("Start index: [{}]", startIndex);
log.debug("End index: [{}]", endIndex);
pageContent = topComments.subList(startIndex, endIndex);
}
return new CommentPage<>(pageContent, pageable, topComments.size(), comments.size());
}
/**
* Builds a comment comparator.
*
* @param sort sort info
* @return comment comparator
*/
private Comparator<CommentVO> buildCommentComparator(Sort sort) {
return (currentComment, toCompareComment) -> {
Assert.notNull(currentComment, "Current comment must not be null");
Assert.notNull(toCompareComment, "Comment to compare must not be null");
// Get sort order
Order order = sort.filter(anOrder -> anOrder.getProperty().equals("createTime"))
.get()
.findFirst()
.orElseGet(() -> Order.desc("createTime"));
// Init sign
int sign = order.getDirection().isAscending() ? 1 : -1;
// Compare createTime property
return sign * currentComment.getCreateTime().compareTo(toCompareComment.getCreateTime());
};
}
private void concreteTree(CommentVO parentComment, List<Comment> comments, Comparator<CommentVO> commentComparator) {
Assert.notNull(parentComment, "Parent comment must not be null");
Assert.notNull(commentComparator, "Comment comparator must not be null");
if (CollectionUtils.isEmpty(comments)) {
return;
}
List<Comment> children = new LinkedList<>();
comments.forEach(comment -> {
if (parentComment.getId().equals(comment.getParentId())) {
// Stage the child comment
children.add(comment);
// Convert to comment vo
CommentVO commentVO = new CommentVO().convertFrom(comment);
// TODO Add template
// Init children container
if (parentComment.getChildren() == null) {
parentComment.setChildren(new LinkedList<>());
}
parentComment.getChildren().add(commentVO);
}
});
// Remove all children
comments.removeAll(children);
if (!CollectionUtils.isEmpty(parentComment.getChildren())) {
// Recursively concrete the children
parentComment.getChildren().forEach(childComment -> concreteTree(childComment, comments, commentComparator));
// Sort the children
parentComment.getChildren().sort(commentComparator);
}
}
/**
* Converts to comment vo page.
*

View File

@ -4,16 +4,17 @@ import cc.ryanc.halo.model.dto.post.PostMinimalOutputDTO;
import cc.ryanc.halo.model.dto.post.PostSimpleOutputDTO;
import cc.ryanc.halo.model.entity.Post;
import cc.ryanc.halo.model.enums.PostStatus;
import cc.ryanc.halo.model.enums.PostType;
import cc.ryanc.halo.model.params.PostParam;
import cc.ryanc.halo.model.vo.CommentVO;
import cc.ryanc.halo.model.vo.PostDetailVO;
import cc.ryanc.halo.service.PostCategoryService;
import cc.ryanc.halo.service.PostService;
import cc.ryanc.halo.service.PostTagService;
import cc.ryanc.halo.service.*;
import io.swagger.annotations.ApiOperation;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.SortDefault;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@ -37,10 +38,20 @@ public class PostController {
private final PostTagService postTagService;
public PostController(PostService postService, PostCategoryService postCategoryService, PostTagService postTagService) {
private final CommentService commentService;
private final OptionService optionService;
public PostController(PostService postService,
PostCategoryService postCategoryService,
PostTagService postTagService,
CommentService commentService,
OptionService optionService) {
this.postService = postService;
this.postCategoryService = postCategoryService;
this.postTagService = postTagService;
this.commentService = commentService;
this.optionService = optionService;
}
@GetMapping("latest")
@ -91,4 +102,11 @@ public class PostController {
postCategoryService.removeByPostId(postId);
postTagService.removeByPostId(postId);
}
@GetMapping("{postId:\\d+}/comments")
public Page<CommentVO> listComments(@PathVariable("postId") Integer postId,
@RequestParam(name = "page", required = false, defaultValue = "0") int page,
@SortDefault Sort sort) {
return commentService.pageVosBy(postId, PageRequest.of(page, optionService.getCommentPageSize(), sort));
}
}

View File

@ -1,5 +1,6 @@
package cc.ryanc.halo.web.controller.support;
import cc.ryanc.halo.model.support.CommentPage;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
@ -30,6 +31,13 @@ public class PageJacksonSerializer extends JsonSerializer<Page> {
generator.writeBooleanField("isLast", page.isLast());
generator.writeBooleanField("isEmpty", page.isEmpty());
generator.writeBooleanField("hasContent", page.hasContent());
// Handle comment page
if (page instanceof CommentPage) {
CommentPage commentPage = (CommentPage) page;
generator.writeNumberField("commentCount", commentPage.getCommentCount());
}
generator.writeEndObject();
}
}