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 id
pull/1604/head
guqing 2021-12-21 23:35:42 +08:00 committed by GitHub
parent f2395b7b5f
commit 9ddd74b74b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 341 additions and 174 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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.
*

View File

@ -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);
}

View File

@ -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> {

View File

@ -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,93 +136,43 @@ 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<>());
}
StringBuilder fullPath = new StringBuilder();
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append(URL_SEPARATOR)
.append(optionService.getCategoriesPrefix())
.append(URL_SEPARATOR)
.append(child.getSlug())
.append(optionService.getPathSuffix());
child.setFullPath(fullPath.toString());
if (!fillPassword) {
child.setPassword(null);
}
// Add child
parentCategory.getChildren().add(child);
private void desensitizePassword(List<Category> categories) {
Assert.notNull(categories, "The categories must not be null.");
categories.forEach(category -> {
category.setPassword(null);
});
// 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);
@Override
public String buildCategoryFullPath(@NonNull String slug) {
Assert.notNull(slug, "The slug must not be null.");
StringBuilder fullPath = new StringBuilder();
return topCategory;
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
fullPath.append(URL_SEPARATOR)
.append(optionService.getCategoriesPrefix())
.append(URL_SEPARATOR)
.append(slug)
.append(optionService.getPathSuffix());
return fullPath.toString();
}
@NonNull
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.
*

View File

@ -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));
return flatTreeToList(categoryTreeVo);
}
StringBuilder fullPath = new StringBuilder();
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;
}
if (optionService.isEnabledAbsolutePath()) {
fullPath.append(optionService.getBlogBaseUrl());
}
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())));
fullPath.append(URL_SEPARATOR)
.append(optionService.getCategoriesPrefix())
.append(URL_SEPARATOR)
.append(category.getSlug())
.append(optionService.getPathSuffix());
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);
}
categoryWithPostCountDTO.setFullPath(fullPath.toString());
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());
}
}
return categoryWithPostCountDTO;
})
.collect(Collectors.toList());
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);
}
}

View File

@ -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));
}

View File

@ -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);
}
}