mirror of https://github.com/halo-dev/halo
Include posts that under the subcategories when getting posts b… (#1567)
* feat: include posts that under the subcategories when getting posts by category * refactor: list as tree * fix: can nott query the problem of encrypted category * refactor: post count under the category * fix: list all by parent idpull/1604/head
parent
f2395b7b5f
commit
9ddd74b74b
|
@ -1,21 +0,0 @@
|
|||
package run.halo.app.model.projection;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* Category post count projection.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 19-4-23
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CategoryPostCountProjection {
|
||||
|
||||
private Long postCount;
|
||||
|
||||
private Integer categoryId;
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package run.halo.app.model.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
@ -10,6 +12,7 @@ import run.halo.app.model.dto.CategoryDTO;
|
|||
* Category vo.
|
||||
*
|
||||
* @author johnniang
|
||||
* @author guqing
|
||||
* @date 3/21/19
|
||||
*/
|
||||
@Data
|
||||
|
@ -17,5 +20,8 @@ import run.halo.app.model.dto.CategoryDTO;
|
|||
@EqualsAndHashCode(callSuper = true)
|
||||
public class CategoryVO extends CategoryDTO {
|
||||
|
||||
@JsonIgnore
|
||||
private Set<Integer> postIds;
|
||||
|
||||
private List<CategoryVO> children;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import org.springframework.data.jpa.repository.Query;
|
|||
import org.springframework.lang.NonNull;
|
||||
import run.halo.app.model.entity.PostCategory;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.projection.CategoryPostCountProjection;
|
||||
import run.halo.app.repository.base.BaseRepository;
|
||||
|
||||
|
||||
|
@ -113,11 +112,6 @@ public interface PostCategoryRepository extends BaseRepository<PostCategory, Int
|
|||
@NonNull
|
||||
List<PostCategory> deleteByCategoryId(@NonNull Integer categoryId);
|
||||
|
||||
@Query("select new run.halo.app.model.projection.CategoryPostCountProjection(count(pc.postId)"
|
||||
+ ", pc.categoryId) from PostCategory pc group by pc.categoryId")
|
||||
@NonNull
|
||||
List<CategoryPostCountProjection> findPostCount();
|
||||
|
||||
/**
|
||||
* Finds all post categories by category id list.
|
||||
*
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.service;
|
||||
|
||||
import io.swagger.models.auth.In;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
@ -16,6 +17,7 @@ import run.halo.app.service.base.CrudService;
|
|||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2019-03-14
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
|
@ -31,7 +33,16 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
List<CategoryVO> listAsTree(@NonNull Sort sort);
|
||||
|
||||
/**
|
||||
* Get category by slug
|
||||
* Build category full path.
|
||||
*
|
||||
* @param slug category slug name.
|
||||
* @return full path of category.
|
||||
*/
|
||||
@NonNull
|
||||
String buildCategoryFullPath(@NonNull String slug);
|
||||
|
||||
/**
|
||||
* Get category by slug.
|
||||
*
|
||||
* @param slug slug
|
||||
* @return Category
|
||||
|
@ -90,6 +101,14 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
*/
|
||||
List<Category> listByParentId(@NonNull Integer id);
|
||||
|
||||
/**
|
||||
* List all child categories and current category by parent id.
|
||||
*
|
||||
* @param id parent id.
|
||||
* @return a list of category that contain current id.
|
||||
*/
|
||||
List<Category> listAllByParentId(@NonNull Integer id);
|
||||
|
||||
/**
|
||||
* List all category not encrypt.
|
||||
*
|
||||
|
@ -153,4 +172,12 @@ public interface CategoryService extends CrudService<Category, Integer> {
|
|||
*/
|
||||
@NonNull
|
||||
Boolean categoryHasEncrypt(Integer categoryId);
|
||||
|
||||
/**
|
||||
* Use <code>categories</code> to build a category tree.
|
||||
*
|
||||
* @param categories categories to build a tree.
|
||||
* @return a tree of category.
|
||||
*/
|
||||
List<CategoryVO> listToTree(List<Category> categories);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import run.halo.app.service.base.CrudService;
|
|||
*
|
||||
* @author johnniang
|
||||
* @author ryanwang
|
||||
* @author guqing
|
||||
* @date 2019-03-19
|
||||
*/
|
||||
public interface PostCategoryService extends CrudService<PostCategory, Integer> {
|
||||
|
|
|
@ -3,6 +3,7 @@ package run.halo.app.service.impl;
|
|||
import static run.halo.app.model.support.HaloConst.URL_SEPARATOR;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -10,6 +11,7 @@ import java.util.LinkedList;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -20,6 +22,7 @@ import org.springframework.context.annotation.Lazy;
|
|||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -51,6 +54,7 @@ import run.halo.app.utils.ServiceUtils;
|
|||
*
|
||||
* @author ryanwang
|
||||
* @author johnniang
|
||||
* @author guqing
|
||||
* @date 2019-03-14
|
||||
*/
|
||||
@Slf4j
|
||||
|
@ -132,47 +136,23 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
if (CollectionUtils.isEmpty(categories)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// Do not return category password.
|
||||
desensitizePassword(categories);
|
||||
|
||||
// Create top category
|
||||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
// Concrete the tree
|
||||
concreteTree(topLevelCategory, categories, false);
|
||||
|
||||
return topLevelCategory.getChildren();
|
||||
return listToTree(categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Concrete category tree.
|
||||
*
|
||||
* @param parentCategory parent category vo must not be null
|
||||
* @param categories a list of category
|
||||
* @param fillPassword whether to fill in the password
|
||||
*/
|
||||
private void concreteTree(
|
||||
CategoryVO parentCategory,
|
||||
List<Category> categories,
|
||||
Boolean fillPassword
|
||||
) {
|
||||
Assert.notNull(parentCategory, "Parent category must not be null");
|
||||
|
||||
if (CollectionUtils.isEmpty(categories)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get children for removing after
|
||||
List<Category> children = categories.stream()
|
||||
.filter(category -> Objects.equal(parentCategory.getId(), category.getParentId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
children.forEach(category -> {
|
||||
// Convert to child category vo
|
||||
CategoryVO child = new CategoryVO().convertFrom(category);
|
||||
// Init children if absent
|
||||
if (parentCategory.getChildren() == null) {
|
||||
parentCategory.setChildren(new LinkedList<>());
|
||||
private void desensitizePassword(List<Category> categories) {
|
||||
Assert.notNull(categories, "The categories must not be null.");
|
||||
categories.forEach(category -> {
|
||||
category.setPassword(null);
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String buildCategoryFullPath(@NonNull String slug) {
|
||||
Assert.notNull(slug, "The slug must not be null.");
|
||||
StringBuilder fullPath = new StringBuilder();
|
||||
|
||||
if (optionService.isEnabledAbsolutePath()) {
|
||||
|
@ -182,43 +162,17 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getCategoriesPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(child.getSlug())
|
||||
.append(slug)
|
||||
.append(optionService.getPathSuffix());
|
||||
|
||||
child.setFullPath(fullPath.toString());
|
||||
|
||||
if (!fillPassword) {
|
||||
child.setPassword(null);
|
||||
return fullPath.toString();
|
||||
}
|
||||
|
||||
// Add child
|
||||
parentCategory.getChildren().add(child);
|
||||
});
|
||||
|
||||
// Remove all child categories
|
||||
categories.removeAll(children);
|
||||
|
||||
// Foreach children vos
|
||||
if (!CollectionUtils.isEmpty(parentCategory.getChildren())) {
|
||||
parentCategory.getChildren()
|
||||
.forEach(childCategory -> concreteTree(childCategory, categories, fillPassword));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a top level category.
|
||||
*
|
||||
* @return top level category with id 0
|
||||
*/
|
||||
@NonNull
|
||||
private CategoryVO createTopLevelCategory() {
|
||||
CategoryVO topCategory = new CategoryVO();
|
||||
// Set default value
|
||||
topCategory.setId(0);
|
||||
topCategory.setChildren(new LinkedList<>());
|
||||
topCategory.setParentId(-1);
|
||||
|
||||
return topCategory;
|
||||
private CategoryVO convertToCategoryVo(Category category) {
|
||||
Assert.notNull(category, "The category must not be null.");
|
||||
CategoryVO categoryVo = new CategoryVO().convertFrom(category);
|
||||
categoryVo.setFullPath(buildCategoryFullPath(categoryVo.getSlug()));
|
||||
return categoryVo;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -328,32 +282,79 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listByParentId(Integer id) {
|
||||
public List<Category> listByParentId(@NonNull Integer id) {
|
||||
Assert.notNull(id, "Parent id must not be null");
|
||||
return categoryRepository.findByParentId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Category> listAllByParentId(@NonNull Integer id) {
|
||||
Assert.notNull(id, "Parent id must not be null");
|
||||
List<Category> categories = super.listAll(Sort.by(Order.asc("name")));
|
||||
List<CategoryVO> categoryTree = listToTree(categories);
|
||||
return findCategoryTreeNodeById(categoryTree, id)
|
||||
.map(this::walkCategoryTree)
|
||||
.orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk a category tree with root node.
|
||||
*
|
||||
* @param root a root node of category tree.
|
||||
* @return a flattened category list
|
||||
*/
|
||||
@NonNull
|
||||
private List<Category> walkCategoryTree(CategoryVO root) {
|
||||
Assert.notNull(root, "The category 'root' must not be null");
|
||||
List<Category> categories = new LinkedList<>();
|
||||
Queue<CategoryVO> queue = new ArrayDeque<>();
|
||||
queue.add(root);
|
||||
while (!queue.isEmpty()) {
|
||||
CategoryVO categoryNode = queue.poll();
|
||||
|
||||
Category category = new Category();
|
||||
BeanUtils.updateProperties(categoryNode, category);
|
||||
categories.add(category);
|
||||
|
||||
if (HaloUtils.isNotEmpty(categoryNode.getChildren())) {
|
||||
queue.addAll(categoryNode.getChildren());
|
||||
}
|
||||
}
|
||||
return categories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the node with id equal to <code>categoryId</code> from the multi-tree of category.
|
||||
*
|
||||
* @param categoryVos source category multi-tree vos to walk.
|
||||
* @param categoryId category id to be found.
|
||||
* @return the root node of the subtree.
|
||||
*/
|
||||
private Optional<CategoryVO> findCategoryTreeNodeById(List<CategoryVO> categoryVos,
|
||||
Integer categoryId) {
|
||||
Assert.notNull(categoryId, "categoryId id must not be null");
|
||||
Queue<CategoryVO> queue = new ArrayDeque<>(categoryVos);
|
||||
while (!queue.isEmpty()) {
|
||||
CategoryVO category = queue.poll();
|
||||
if (Objects.equal(category.getId(), categoryId)) {
|
||||
return Optional.of(category);
|
||||
}
|
||||
if (HaloUtils.isNotEmpty(category.getChildren())) {
|
||||
queue.addAll(category.getChildren());
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CategoryDTO convertTo(Category category) {
|
||||
Assert.notNull(category, "Category must not be null");
|
||||
|
||||
CategoryDTO categoryDTO = new CategoryDTO().convertFrom(category);
|
||||
CategoryDTO categoryDto = new CategoryDTO().convertFrom(category);
|
||||
|
||||
StringBuilder fullPath = new StringBuilder();
|
||||
categoryDto.setFullPath(buildCategoryFullPath(category.getSlug()));
|
||||
|
||||
if (optionService.isEnabledAbsolutePath()) {
|
||||
fullPath.append(optionService.getBlogBaseUrl());
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getCategoriesPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(category.getSlug())
|
||||
.append(optionService.getPathSuffix());
|
||||
|
||||
categoryDTO.setFullPath(fullPath.toString());
|
||||
|
||||
return categoryDTO;
|
||||
return categoryDto;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -373,19 +374,15 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Concrete the tree
|
||||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
concreteTree(topLevelCategory, categories, true);
|
||||
|
||||
List<CategoryVO> childrenList = topLevelCategory.getChildren();
|
||||
// list to tree, no password desensitise is required here
|
||||
List<CategoryVO> categoryTree = listToTree(categories);
|
||||
|
||||
// filter encrypt category
|
||||
doFilterEncryptCategory(childrenList);
|
||||
doFilterEncryptCategory(categoryTree);
|
||||
|
||||
List<Category> collectorList = new ArrayList<>();
|
||||
|
||||
collectAllChild(collectorList, childrenList, true);
|
||||
collectAllChild(collectorList, categoryTree, true);
|
||||
|
||||
for (Category category : collectorList) {
|
||||
category.setPassword(null);
|
||||
|
@ -551,14 +548,13 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
* @param category need encrypt category
|
||||
*/
|
||||
private void doEncryptPost(Category category) {
|
||||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
concreteTree(topLevelCategory, super.listAll(), true);
|
||||
// list to tree with password
|
||||
List<CategoryVO> categoryTree = listToTree(super.listAll());
|
||||
|
||||
List<Category> collectorList = new ArrayList<>();
|
||||
|
||||
collectAllChildByCategoryId(collectorList,
|
||||
topLevelCategory.getChildren(), category.getId(), true);
|
||||
categoryTree, category.getId(), true);
|
||||
|
||||
Optional.of(collectorList.stream().map(Category::getId).collect(Collectors.toList()))
|
||||
.map(categoryIdList -> {
|
||||
|
@ -594,16 +590,14 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
// If the parent category is encrypted, there is no need to update the encryption status
|
||||
return;
|
||||
}
|
||||
|
||||
CategoryVO topLevelCategory = createTopLevelCategory();
|
||||
|
||||
concreteTree(topLevelCategory, allCategoryList, true);
|
||||
// with password
|
||||
List<CategoryVO> categoryTree = listToTree(allCategoryList);
|
||||
|
||||
List<Category> collectorList = new ArrayList<>();
|
||||
|
||||
// Only collect unencrypted sub-categories under the category.
|
||||
collectAllChildByCategoryId(collectorList,
|
||||
topLevelCategory.getChildren(), category.getId(), false);
|
||||
categoryTree, category.getId(), false);
|
||||
// Collect the currently decrypted category
|
||||
collectorList.add(category);
|
||||
|
||||
|
@ -637,6 +631,33 @@ public class CategoryServiceImpl extends AbstractCrudService<Category, Integer>
|
|||
return doCategoryHasEncrypt(idToCategoryMap, categoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CategoryVO> listToTree(List<Category> categories) {
|
||||
Assert.notNull(categories, "The categories must not be null.");
|
||||
// batch convert category to categoryVo
|
||||
List<CategoryVO> categoryVoList = categories.stream()
|
||||
.map(this::convertToCategoryVo)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// build a tree, the time complexity is O(n)
|
||||
Map<Integer, List<CategoryVO>> parentIdMap = categoryVoList.stream()
|
||||
.collect(Collectors.groupingBy(CategoryVO::getParentId));
|
||||
|
||||
// set children
|
||||
categoryVoList.forEach(category -> {
|
||||
List<CategoryVO> children = parentIdMap.get(category.getId());
|
||||
if (CollectionUtils.isEmpty(children)) {
|
||||
category.setChildren(Collections.emptyList());
|
||||
} else {
|
||||
category.setChildren(children);
|
||||
}
|
||||
});
|
||||
|
||||
return categoryVoList.stream()
|
||||
.filter(category -> category.getParentId() == null || category.getParentId() == 0)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find whether the parent category is encrypted.
|
||||
*
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import static run.halo.app.model.support.HaloConst.URL_SEPARATOR;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
@ -25,13 +29,14 @@ import run.halo.app.model.entity.Category;
|
|||
import run.halo.app.model.entity.Post;
|
||||
import run.halo.app.model.entity.PostCategory;
|
||||
import run.halo.app.model.enums.PostStatus;
|
||||
import run.halo.app.model.projection.CategoryPostCountProjection;
|
||||
import run.halo.app.model.vo.CategoryVO;
|
||||
import run.halo.app.repository.PostCategoryRepository;
|
||||
import run.halo.app.repository.PostRepository;
|
||||
import run.halo.app.service.CategoryService;
|
||||
import run.halo.app.service.OptionService;
|
||||
import run.halo.app.service.PostCategoryService;
|
||||
import run.halo.app.service.base.AbstractCrudService;
|
||||
import run.halo.app.utils.HaloUtils;
|
||||
import run.halo.app.utils.ServiceUtils;
|
||||
|
||||
/**
|
||||
|
@ -304,44 +309,80 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<CategoryWithPostCountDTO> listCategoryWithPostCountDto(
|
||||
Sort sort, boolean queryEncryptCategory) {
|
||||
public List<CategoryWithPostCountDTO> listCategoryWithPostCountDto(@NonNull Sort sort,
|
||||
boolean queryEncryptCategory) {
|
||||
Assert.notNull(sort, "Sort info must not be null");
|
||||
List<Category> categories = categoryService.listAll(sort, queryEncryptCategory);
|
||||
|
||||
// Query category post count
|
||||
Map<Integer, Long> categoryPostCountMap = ServiceUtils
|
||||
.convertToMap(postCategoryRepository.findPostCount(),
|
||||
CategoryPostCountProjection::getCategoryId,
|
||||
CategoryPostCountProjection::getPostCount);
|
||||
List<CategoryVO> categoryTreeVo = categoryService.listToTree(categories);
|
||||
populatePostIds(categoryTreeVo);
|
||||
|
||||
// Convert and return
|
||||
return categories.stream()
|
||||
.map(category -> {
|
||||
// Create category post count dto
|
||||
CategoryWithPostCountDTO categoryWithPostCountDTO =
|
||||
new CategoryWithPostCountDTO().convertFrom(category);
|
||||
// Set post count
|
||||
categoryWithPostCountDTO
|
||||
.setPostCount(categoryPostCountMap.getOrDefault(category.getId(), 0L));
|
||||
|
||||
StringBuilder fullPath = new StringBuilder();
|
||||
|
||||
if (optionService.isEnabledAbsolutePath()) {
|
||||
fullPath.append(optionService.getBlogBaseUrl());
|
||||
return flatTreeToList(categoryTreeVo);
|
||||
}
|
||||
|
||||
fullPath.append(URL_SEPARATOR)
|
||||
.append(optionService.getCategoriesPrefix())
|
||||
.append(URL_SEPARATOR)
|
||||
.append(category.getSlug())
|
||||
.append(optionService.getPathSuffix());
|
||||
private List<CategoryWithPostCountDTO> flatTreeToList(List<CategoryVO> categoryTree) {
|
||||
Assert.notNull(categoryTree, "The categoryTree must not be null.");
|
||||
List<CategoryWithPostCountDTO> result = new LinkedList<>();
|
||||
walkCategoryTree(categoryTree, category -> {
|
||||
CategoryWithPostCountDTO categoryWithPostCountDto =
|
||||
new CategoryWithPostCountDTO();
|
||||
BeanUtils.copyProperties(category, categoryWithPostCountDto);
|
||||
String fullPath = categoryService.buildCategoryFullPath(category.getSlug());
|
||||
categoryWithPostCountDto.setFullPath(fullPath);
|
||||
// populate post count.
|
||||
int postCount = Objects.requireNonNullElse(category.getPostIds(),
|
||||
Collections.emptySet()).size();
|
||||
categoryWithPostCountDto.setPostCount((long) postCount);
|
||||
result.add(categoryWithPostCountDto);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
categoryWithPostCountDTO.setFullPath(fullPath.toString());
|
||||
private void populatePostIds(List<CategoryVO> categoryTree) {
|
||||
Assert.notNull(categoryTree, "The categoryTree must not be null.");
|
||||
Map<Integer, Set<Integer>> categoryPostIdsMap = postCategoryRepository.findAll()
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(PostCategory::getCategoryId,
|
||||
Collectors.mapping(PostCategory::getPostId, Collectors.toSet())));
|
||||
|
||||
return categoryWithPostCountDTO;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
walkCategoryTree(categoryTree, category -> {
|
||||
// Set post count
|
||||
Set<Integer> postIds =
|
||||
categoryPostIdsMap.getOrDefault(category.getId(), new LinkedHashSet<>());
|
||||
category.setPostIds(postIds);
|
||||
});
|
||||
CategoryVO categoryTreeRootNode = new CategoryVO();
|
||||
categoryTreeRootNode.setChildren(categoryTree);
|
||||
mergePostIdsFromBottomToTop(categoryTreeRootNode);
|
||||
}
|
||||
|
||||
private void mergePostIdsFromBottomToTop(CategoryVO root) {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
List<CategoryVO> children = root.getChildren();
|
||||
if (CollectionUtils.isEmpty(children)) {
|
||||
return;
|
||||
}
|
||||
for (CategoryVO category : children) {
|
||||
mergePostIdsFromBottomToTop(category);
|
||||
if (root.getPostIds() == null) {
|
||||
root.setPostIds(new LinkedHashSet<>());
|
||||
}
|
||||
// merge post ids.
|
||||
root.getPostIds().addAll(category.getPostIds());
|
||||
}
|
||||
}
|
||||
|
||||
private void walkCategoryTree(List<CategoryVO> categoryTree, Consumer<CategoryVO> consumer) {
|
||||
Queue<CategoryVO> queue = new ArrayDeque<>(categoryTree);
|
||||
while (!queue.isEmpty()) {
|
||||
CategoryVO category = queue.poll();
|
||||
consumer.accept(category);
|
||||
if (HaloUtils.isNotEmpty(category.getChildren())) {
|
||||
queue.addAll(category.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -349,4 +390,5 @@ public class PostCategoryServiceImpl extends AbstractCrudService<PostCategory, I
|
|||
Assert.notEmpty(categoryIdList, "category id list not empty");
|
||||
return postCategoryRepository.findAllByCategoryIdList(categoryIdList);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -816,13 +816,17 @@ public class PostServiceImpl extends BasePostServiceImpl<Post> implements PostSe
|
|||
}
|
||||
|
||||
if (postQuery.getCategoryId() != null) {
|
||||
List<Integer> categoryIds =
|
||||
categoryService.listAllByParentId(postQuery.getCategoryId())
|
||||
.stream()
|
||||
.map(Category::getId)
|
||||
.collect(Collectors.toList());
|
||||
Subquery<Post> postSubquery = query.subquery(Post.class);
|
||||
Root<PostCategory> postCategoryRoot = postSubquery.from(PostCategory.class);
|
||||
postSubquery.select(postCategoryRoot.get("postId"));
|
||||
postSubquery.where(
|
||||
criteriaBuilder.equal(root.get("id"), postCategoryRoot.get("postId")),
|
||||
criteriaBuilder
|
||||
.equal(postCategoryRoot.get("categoryId"), postQuery.getCategoryId()));
|
||||
postCategoryRoot.get("categoryId").in(categoryIds));
|
||||
predicates.add(criteriaBuilder.exists(postSubquery));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import run.halo.app.model.entity.Category;
|
||||
import run.halo.app.model.vo.CategoryVO;
|
||||
import run.halo.app.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* Category service test.
|
||||
*
|
||||
* @author guqing
|
||||
* @date 2021-12-04
|
||||
*/
|
||||
@ActiveProfiles("test")
|
||||
@SpringBootTest
|
||||
public class CategoryServiceTest {
|
||||
|
||||
@Autowired
|
||||
private CategoryServiceImpl categoryService;
|
||||
|
||||
@Test
|
||||
void listToTree() throws JsonProcessingException {
|
||||
List<Category> categories = mockCategories();
|
||||
List<CategoryVO> categoryVoList = categoryService.listToTree(categories);
|
||||
assertEquals(
|
||||
"[{\"id\":1,\"name\":\"分类-1\",\"slug\":\"category1\",\"description\":null,"
|
||||
+ "\"thumbnail\":null,\"parentId\":0,\"password\":null,\"createTime\":null,"
|
||||
+ "\"fullPath\":\"http://127.0.0.1:8090/categories/category1\","
|
||||
+ "\"children\":[]},{\"id\":2,\"name\":\"分类-2\",\"slug\":\"category2\","
|
||||
+ "\"description\":null,\"thumbnail\":null,\"parentId\":0,\"password\":null,"
|
||||
+ "\"createTime\":null,\"fullPath\":\"http://127.0.0.1:8090/categories/category2\","
|
||||
+ "\"children\":[{\"id\":3,\"name\":\"分类-2-1\",\"slug\":\"category21\","
|
||||
+ "\"description\":null,\"thumbnail\":null,\"parentId\":2,\"password\":null,"
|
||||
+ "\"createTime\":null,\"fullPath\":\"http://127.0.0.1:8090/categories/category21\","
|
||||
+ "\"children\":[{\"id\":5,\"name\":\"分类-2-1-1\",\"slug\":\"category211\","
|
||||
+ "\"description\":null,\"thumbnail\":null,\"parentId\":3,\"password\":null,"
|
||||
+ "\"createTime\":null,\"fullPath\":\"http://127.0.0.1:8090/categories/category211\","
|
||||
+ "\"children\":[{\"id\":6,\"name\":\"分类-2-1-1-1\",\"slug\":\"category2111\","
|
||||
+ "\"description\":null,\"thumbnail\":null,\"parentId\":5,\"password\":null,"
|
||||
+ "\"createTime\":null,\"fullPath\":\"http://127.0.0.1:8090/categories/category2111\","
|
||||
+ "\"children\":[]}]}]},{\"id\":4,\"name\":\"分类-2-2\",\"slug\":\"category22\","
|
||||
+ "\"description\":null,\"thumbnail\":null,\"parentId\":2,\"password\":null,"
|
||||
+ "\"createTime\":null,\"fullPath\":\"http://127.0.0.1:8090/categories/category22\","
|
||||
+ "\"children\":[]}]}]",
|
||||
JsonUtils.objectToJson(categoryVoList));
|
||||
}
|
||||
|
||||
private List<Category> mockCategories() {
|
||||
Category category1 = new Category();
|
||||
category1.setId(1);
|
||||
category1.setName("分类-1");
|
||||
category1.setSlug("category1");
|
||||
category1.setParentId(0);
|
||||
|
||||
Category category2 = new Category();
|
||||
category2.setId(2);
|
||||
category2.setName("分类-2");
|
||||
category2.setSlug("category2");
|
||||
category2.setParentId(0);
|
||||
|
||||
Category category3 = new Category();
|
||||
category3.setId(3);
|
||||
category3.setName("分类-2-1");
|
||||
category3.setSlug("category21");
|
||||
category3.setParentId(2);
|
||||
|
||||
Category category4 = new Category();
|
||||
category4.setId(4);
|
||||
category4.setName("分类-2-2");
|
||||
category4.setSlug("category22");
|
||||
category4.setParentId(2);
|
||||
|
||||
Category category5 = new Category();
|
||||
category5.setId(5);
|
||||
category5.setName("分类-2-1-1");
|
||||
category5.setSlug("category211");
|
||||
category5.setParentId(3);
|
||||
|
||||
Category category6 = new Category();
|
||||
category6.setId(6);
|
||||
category6.setName("分类-2-1-1-1");
|
||||
category6.setSlug("category2111");
|
||||
category6.setParentId(5);
|
||||
return List.of(category1, category2, category3, category4, category5, category6);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue