Skip to content

Commit 983b37d

Browse files
author
yqz
committed
增加目录树功能
1 parent dd07a44 commit 983b37d

27 files changed

Lines changed: 1632 additions & 248 deletions

OpenBlog-business/src/main/java/com/yqz/openblog/article/controller/ArticleController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ public ArticleController(ArticleService articleService, CurrentUser currentUser)
3333
@GetMapping("/articles")
3434
public ApiResponse<PageResult<ArticleListItemResponse>> listPublished(
3535
@RequestParam(defaultValue = "0") int page,
36-
@RequestParam(defaultValue = "20") int size) {
37-
return ApiResponse.ok(articleService.listPublished(page, size));
36+
@RequestParam(defaultValue = "20") int size,
37+
@RequestParam(required = false) Long categoryId) {
38+
return ApiResponse.ok(articleService.listPublished(page, size, categoryId));
3839
}
3940

4041
@GetMapping("/articles/{articleId}")

OpenBlog-business/src/main/java/com/yqz/openblog/article/dto/ArticleDetailResponse.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public class ArticleDetailResponse {
2020
private Long commentCount;
2121
private Instant createdAt;
2222
private Instant updatedAt;
23+
private Long categoryId;
24+
private String categoryName;
25+
private java.util.List<String> categoryPath;
2326

2427
public Long getId() {
2528
return id;
@@ -140,5 +143,29 @@ public Instant getUpdatedAt() {
140143
public void setUpdatedAt(Instant updatedAt) {
141144
this.updatedAt = updatedAt;
142145
}
146+
147+
public Long getCategoryId() {
148+
return categoryId;
149+
}
150+
151+
public void setCategoryId(Long categoryId) {
152+
this.categoryId = categoryId;
153+
}
154+
155+
public String getCategoryName() {
156+
return categoryName;
157+
}
158+
159+
public void setCategoryName(String categoryName) {
160+
this.categoryName = categoryName;
161+
}
162+
163+
public java.util.List<String> getCategoryPath() {
164+
return categoryPath;
165+
}
166+
167+
public void setCategoryPath(java.util.List<String> categoryPath) {
168+
this.categoryPath = categoryPath;
169+
}
143170
}
144171

OpenBlog-business/src/main/java/com/yqz/openblog/article/dto/ArticleListItemResponse.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public class ArticleListItemResponse {
1717
private Long viewCount;
1818
private Long favoriteCount;
1919
private Long commentCount;
20+
private Long categoryId;
21+
private String categoryName;
22+
private java.util.List<String> categoryPath;
2023

2124
public Long getId() {
2225
return id;
@@ -113,5 +116,29 @@ public Long getCommentCount() {
113116
public void setCommentCount(Long commentCount) {
114117
this.commentCount = commentCount;
115118
}
119+
120+
public Long getCategoryId() {
121+
return categoryId;
122+
}
123+
124+
public void setCategoryId(Long categoryId) {
125+
this.categoryId = categoryId;
126+
}
127+
128+
public String getCategoryName() {
129+
return categoryName;
130+
}
131+
132+
public void setCategoryName(String categoryName) {
133+
this.categoryName = categoryName;
134+
}
135+
136+
public java.util.List<String> getCategoryPath() {
137+
return categoryPath;
138+
}
139+
140+
public void setCategoryPath(java.util.List<String> categoryPath) {
141+
this.categoryPath = categoryPath;
142+
}
116143
}
117144

OpenBlog-business/src/main/java/com/yqz/openblog/article/dto/ArticlePublishedContentCachePayload.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public class ArticlePublishedContentCachePayload {
2020
private ArticleStatus status;
2121
private Instant createdAt;
2222
private Instant updatedAt;
23+
private Long categoryId;
24+
private String categoryName;
25+
private java.util.List<String> categoryPath;
2326

2427
public static ArticlePublishedContentCachePayload fromDetail(ArticleDetailResponse d) {
2528
ArticlePublishedContentCachePayload p = new ArticlePublishedContentCachePayload();
@@ -34,6 +37,9 @@ public static ArticlePublishedContentCachePayload fromDetail(ArticleDetailRespon
3437
p.setStatus(d.getStatus());
3538
p.setCreatedAt(d.getCreatedAt());
3639
p.setUpdatedAt(d.getUpdatedAt());
40+
p.setCategoryId(d.getCategoryId());
41+
p.setCategoryName(d.getCategoryName());
42+
p.setCategoryPath(d.getCategoryPath());
3743
return p;
3844
}
3945

@@ -124,4 +130,28 @@ public Instant getUpdatedAt() {
124130
public void setUpdatedAt(Instant updatedAt) {
125131
this.updatedAt = updatedAt;
126132
}
133+
134+
public Long getCategoryId() {
135+
return categoryId;
136+
}
137+
138+
public void setCategoryId(Long categoryId) {
139+
this.categoryId = categoryId;
140+
}
141+
142+
public String getCategoryName() {
143+
return categoryName;
144+
}
145+
146+
public void setCategoryName(String categoryName) {
147+
this.categoryName = categoryName;
148+
}
149+
150+
public java.util.List<String> getCategoryPath() {
151+
return categoryPath;
152+
}
153+
154+
public void setCategoryPath(java.util.List<String> categoryPath) {
155+
this.categoryPath = categoryPath;
156+
}
127157
}

OpenBlog-business/src/main/java/com/yqz/openblog/article/service/ArticleService.java

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,36 +9,42 @@
99
import com.yqz.openblog.article.repo.ArticleMapper;
1010
import com.yqz.openblog.common.BizException;
1111
import com.yqz.openblog.common.PageResult;
12+
import com.yqz.openblog.category.service.CategoryService;
1213
import com.yqz.openblog.user.entity.User;
1314
import com.yqz.openblog.user.repo.UserMapper;
14-
import org.springframework.stereotype.Service;
15-
16-
import java.time.Instant;
17-
import java.util.List;
18-
import java.util.Optional;
1915

2016
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
2117
import com.baomidou.mybatisplus.core.metadata.IPage;
2218
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
2319
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
2420

21+
import java.time.Instant;
22+
import java.util.List;
23+
import java.util.Optional;
24+
import java.util.Set;
25+
26+
import org.springframework.stereotype.Service;
27+
2528
@Service
2629
public class ArticleService {
2730

2831
private final ArticleMapper articleMapper;
2932
private final UserMapper userMapper;
3033
private final ArticleViewService articleViewService;
3134
private final ArticlePublishedContentCacheService publishedContentCache;
35+
private final CategoryService categoryService;
3236

3337
public ArticleService(
3438
ArticleMapper articleMapper,
3539
UserMapper userMapper,
3640
ArticleViewService articleViewService,
37-
ArticlePublishedContentCacheService publishedContentCache) {
41+
ArticlePublishedContentCacheService publishedContentCache,
42+
CategoryService categoryService) {
3843
this.articleMapper = articleMapper;
3944
this.userMapper = userMapper;
4045
this.articleViewService = articleViewService;
4146
this.publishedContentCache = publishedContentCache;
47+
this.categoryService = categoryService;
4248
}
4349

4450
public ArticleListItemResponse mapListItem(Article a) {
@@ -56,6 +62,7 @@ public ArticleListItemResponse mapListItem(Article a) {
5662
resp.setViewCount(a.getViewCount() == null ? 0L : a.getViewCount());
5763
resp.setFavoriteCount(a.getFavoriteCount());
5864
resp.setCommentCount(a.getCommentCount());
65+
applyCategory(resp, a.getCategoryId());
5966
return resp;
6067
}
6168

@@ -77,9 +84,24 @@ public ArticleDetailResponse mapDetail(Article a) {
7784
resp.setCommentCount(a.getCommentCount());
7885
resp.setCreatedAt(a.getCreatedAt());
7986
resp.setUpdatedAt(a.getUpdatedAt());
87+
applyCategory(resp, a.getCategoryId());
8088
return resp;
8189
}
8290

91+
private void applyCategory(ArticleListItemResponse resp, Long categoryId) {
92+
CategoryService.CategoryMeta meta = categoryService.resolveMeta(categoryId);
93+
resp.setCategoryId(meta.getCategoryId());
94+
resp.setCategoryName(meta.getCategoryName());
95+
resp.setCategoryPath(meta.getCategoryPath());
96+
}
97+
98+
private void applyCategory(ArticleDetailResponse resp, Long categoryId) {
99+
CategoryService.CategoryMeta meta = categoryService.resolveMeta(categoryId);
100+
resp.setCategoryId(meta.getCategoryId());
101+
resp.setCategoryName(meta.getCategoryName());
102+
resp.setCategoryPath(meta.getCategoryPath());
103+
}
104+
83105
/**
84106
* 缓存命中时:正文等用缓存,计数类字段始终用当前数据库行(避免阅读量/评论数长期失真)。
85107
*/
@@ -96,18 +118,28 @@ private ArticleDetailResponse mergePublishedDetailFromCache(ArticlePublishedCont
96118
r.setStatus(p.getStatus());
97119
r.setCreatedAt(p.getCreatedAt());
98120
r.setUpdatedAt(p.getUpdatedAt());
121+
r.setCategoryId(p.getCategoryId());
122+
r.setCategoryName(p.getCategoryName());
123+
r.setCategoryPath(p.getCategoryPath());
99124
r.setLikeCount(a.getLikeCount() == null ? 0L : a.getLikeCount());
100125
r.setViewCount(a.getViewCount() == null ? 0L : a.getViewCount());
101126
r.setFavoriteCount(a.getFavoriteCount() == null ? 0L : a.getFavoriteCount());
102127
r.setCommentCount(a.getCommentCount() == null ? 0L : a.getCommentCount());
103128
return r;
104129
}
105130

106-
public PageResult<ArticleListItemResponse> listPublished(int page, int size) {
131+
public PageResult<ArticleListItemResponse> listPublished(int page, int size, Long categoryId) {
107132
Page<Article> mpPage = new Page<>(page + 1L, size);
108133
LambdaQueryWrapper<Article> w = Wrappers.lambdaQuery();
109134
w.eq(Article::getStatus, ArticleStatus.PUBLISHED)
110135
.orderByDesc(Article::getPublishedAt);
136+
if (categoryId != null) {
137+
Set<Long> ids = categoryService.collectSelfAndDescendantIds(categoryId);
138+
if (ids.isEmpty()) {
139+
return new PageResult<>(List.of(), page, size, 0L);
140+
}
141+
w.in(Article::getCategoryId, ids);
142+
}
111143
IPage<Article> p = articleMapper.selectPage(mpPage, w);
112144
List<ArticleListItemResponse> items = p.getRecords().stream().map(this::mapListItem).toList();
113145
return new PageResult<>(items, page, size, p.getTotal());
@@ -124,6 +156,7 @@ public ArticleDetailResponse detailPublished(Long id, String clientIp) {
124156
Optional<ArticlePublishedContentCachePayload> cached = publishedContentCache.get(id);
125157
if (cached.isPresent()) {
126158
resp = mergePublishedDetailFromCache(cached.get(), a);
159+
applyCategory(resp, a.getCategoryId());
127160
} else {
128161
resp = mapDetail(a);
129162
publishedContentCache.put(id, ArticlePublishedContentCachePayload.fromDetail(resp));
@@ -162,6 +195,7 @@ public ArticleListItemResponse createDraft(Long authorId, ArticleCreateRequest r
162195
a.setSummary(req.getSummary());
163196
a.setContentMarkdown(req.getContentMarkdown());
164197
a.setCoverMediaKey(req.getCoverMediaKey());
198+
categoryService.validateCategoryId(req.getCategoryId());
165199
a.setCategoryId(req.getCategoryId());
166200
a.setStatus(ArticleStatus.DRAFT);
167201
a.setLikeCount(0L);
@@ -187,6 +221,7 @@ public ArticleListItemResponse updateArticle(Long authorId, Long articleId, Arti
187221
a.setSummary(req.getSummary());
188222
a.setContentMarkdown(req.getContentMarkdown());
189223
a.setCoverMediaKey(req.getCoverMediaKey());
224+
categoryService.validateCategoryId(req.getCategoryId());
190225
a.setCategoryId(req.getCategoryId());
191226
articleMapper.updateById(a);
192227
if (a.getStatus() == ArticleStatus.PUBLISHED) {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.yqz.openblog.category.controller;
2+
3+
import com.yqz.openblog.category.dto.CategoryFlatItemResponse;
4+
import com.yqz.openblog.category.dto.CategoryTreeNodeResponse;
5+
import com.yqz.openblog.category.dto.CategoryUpsertRequest;
6+
import com.yqz.openblog.category.service.CategoryService;
7+
import com.yqz.openblog.common.ApiResponse;
8+
import jakarta.validation.Valid;
9+
import org.springframework.security.access.prepost.PreAuthorize;
10+
import org.springframework.web.bind.annotation.*;
11+
12+
import java.util.List;
13+
14+
@RestController
15+
@RequestMapping("/api/v1/categories")
16+
@CrossOrigin(origins = "*")
17+
public class CategoryController {
18+
19+
private final CategoryService categoryService;
20+
21+
public CategoryController(CategoryService categoryService) {
22+
this.categoryService = categoryService;
23+
}
24+
25+
@GetMapping("/tree")
26+
public ApiResponse<List<CategoryTreeNodeResponse>> tree() {
27+
return ApiResponse.ok(categoryService.listTree());
28+
}
29+
30+
@GetMapping
31+
public ApiResponse<List<CategoryFlatItemResponse>> listFlat() {
32+
return ApiResponse.ok(categoryService.listFlat());
33+
}
34+
35+
@PostMapping
36+
@PreAuthorize("hasRole('ADMIN')")
37+
public ApiResponse<CategoryFlatItemResponse> create(@RequestBody @Valid CategoryUpsertRequest req) {
38+
return ApiResponse.ok(categoryService.create(req));
39+
}
40+
41+
@PutMapping("/{id}")
42+
@PreAuthorize("hasRole('ADMIN')")
43+
public ApiResponse<CategoryFlatItemResponse> update(
44+
@PathVariable Long id,
45+
@RequestBody @Valid CategoryUpsertRequest req) {
46+
return ApiResponse.ok(categoryService.update(id, req));
47+
}
48+
49+
@DeleteMapping("/{id}")
50+
@PreAuthorize("hasRole('ADMIN')")
51+
public ApiResponse<Void> delete(@PathVariable Long id) {
52+
categoryService.delete(id);
53+
return ApiResponse.ok();
54+
}
55+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.yqz.openblog.category.dto;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
6+
public class CategoryFlatItemResponse {
7+
8+
private Long id;
9+
private String name;
10+
private Long parentId;
11+
private Integer sortOrder;
12+
private List<String> path = new ArrayList<>();
13+
14+
public Long getId() {
15+
return id;
16+
}
17+
18+
public void setId(Long id) {
19+
this.id = id;
20+
}
21+
22+
public String getName() {
23+
return name;
24+
}
25+
26+
public void setName(String name) {
27+
this.name = name;
28+
}
29+
30+
public Long getParentId() {
31+
return parentId;
32+
}
33+
34+
public void setParentId(Long parentId) {
35+
this.parentId = parentId;
36+
}
37+
38+
public Integer getSortOrder() {
39+
return sortOrder;
40+
}
41+
42+
public void setSortOrder(Integer sortOrder) {
43+
this.sortOrder = sortOrder;
44+
}
45+
46+
public List<String> getPath() {
47+
return path;
48+
}
49+
50+
public void setPath(List<String> path) {
51+
this.path = path;
52+
}
53+
}

0 commit comments

Comments
 (0)