简介
有了文章的CRUD,现在来写文章的标签和分类相关的接口
标签
首先我们要明确需求,现在我们需要的是:
- 查询出全部的标签
- 根据标签找到对应的文章列表
1、新建 TagController
package pers.qianyucc.qblog.controller;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.web.bind.annotation.*;
import pers.qianyucc.qblog.model.vo.*;
import pers.qianyucc.qblog.service.*;
import java.util.*;
@RestController
public class TagController {
@Autowired
private TagService tagService;
@ApiOperation("获取所有的标签")
@GetMapping("/tags")
public Results<Set<String>> getTags() {
return Results.ok(tagService.getAllTags());
}
}
说明:
- 这部分的业务还是和文章有关的,也可以写到 ArticleController 中,这里为了防止 ArticleController 过于臃肿就新建了一个 Controller
2、新建 TagService
package pers.qianyucc.qblog.service;
import com.baomidou.mybatisplus.core.conditions.query.*;
import lombok.extern.slf4j.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import pers.qianyucc.qblog.dao.*;
import pers.qianyucc.qblog.model.entity.*;
import java.util.*;
import java.util.stream.*;
@Service
@Slf4j
public class TagService {
@Autowired
private ArticleMapper articleMapper;
/***
* 查询所有标签
* @return 标签集合
*/
public Set<String> getAllTags() {
QueryWrapper<ArticlePO> wrapper = new QueryWrapper<>();
wrapper.select("tags");
List<ArticlePO> articles = articleMapper.selectList(wrapper);
Set<String> tags = articles.stream()
.map(ArticlePO::getTags)
.flatMap(s -> Arrays.stream(s.split(",")))
.collect(Collectors.toSet());
log.info("tags : {}", tags);
return tags.isEmpty() ? new HashSet<>() : tags;
}
}
说明:
- Set 用于去重
- flatMap 方法可以将多个流合并成一个流
3、使用 Swagger 测试接口
4、在 TagController 中编写根据标签查询的 api
@GetMapping("/tag/{name}")
@ApiOperation("根据标签查询文章集合")
@ApiImplicitParam(name = "name", value = "标签名称", required = true, dataType = "String", paramType = "path")
public Results<PageVO<ArticleVO>> getArticle(
@PathVariable("name") String tagName,
@ApiParam("页码")
@RequestParam(required = false, defaultValue = "1") Integer page,
@ApiParam("每页存放的记录数")
@RequestParam(required = false, defaultValue = "5") Integer limit) {
PageVO<ArticleVO> pv = tagService.getArticleByTag(tagName, page, limit);
return Results.ok(pv);
}
说明:
- 标签名称使用路径参数
- 为适应文章数量比较多的情况,这里提供分页功能
5、对应 TagService 如下
public PageVO<ArticleVO> getArticleByTag(String tagName, Integer page, Integer limit) {
QueryWrapper<ArticlePO> wrapper = new QueryWrapper<>();
wrapper.select(ArticlePO.class, i -> !"content".equals(i.getColumn()))
.like("tags", tagName);
Page<ArticlePO> res = articleMapper.selectPage(new Page<>(page, limit), wrapper);
List<ArticleVO> articleVOS = res.getRecords().stream()
.map(ArticleVO::fromArticlePO)
.collect(Collectors.toList());
PageVO<ArticleVO> pv = PageVO.<ArticleVO>builder()
.records(articleVOS.isEmpty() ? new ArrayList<>() : articleVOS)
.total(res.getTotal())
.current(res.getCurrent())
.size(res.getSize())
.build();
return pv;
}
说明:
- 因为查找文章列表的时候通常就不需要看到文章内容,这里排除 content 字段
- 这里其实使用 like 来查询时存在问题的,在一个标签内容包含另一个标签的时候会出现问题,但是由于出现这种情况的概率不大,这里就使用 like 了,有更好想法的朋友可以自行优化
6、测试结果
分类
由于分类和标签的需求基本相同,这里就直接上代码了
1、CategoryController
package pers.qianyucc.qblog.controller;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.web.bind.annotation.*;
import pers.qianyucc.qblog.model.vo.*;
import pers.qianyucc.qblog.service.*;
import java.util.*;
@RestController
public class CategoryController {
@Autowired
private CategoryService categoryService;
@ApiOperation("获取所有的文章分类以及对应分类文章数")
@GetMapping("/categories")
public Results<List<CategoryVO>> getCategories() {
return Results.ok(categoryService.getAllCategories());
}
@GetMapping("/category/{name}")
@ApiOperation("根据分类查询文章集合")
@ApiImplicitParam(name = "name", value = "分类名称", required = true, dataType = "String", paramType = "path")
public Results<PageVO<ArticleVO>> getArticle(
@PathVariable("name") String categoryName,
@ApiParam("页码")
@RequestParam(required = false, defaultValue = "1") Integer page,
@ApiParam("每页存放的记录数")
@RequestParam(required = false, defaultValue = "5") Integer limit) {
PageVO<ArticleVO> pv = categoryService.getArticleByCategory(categoryName, page, limit);
return Results.ok(pv);
}
}
2、CategoryService
package pers.qianyucc.qblog.service;
import com.baomidou.mybatisplus.core.conditions.query.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import pers.qianyucc.qblog.dao.*;
import pers.qianyucc.qblog.model.entity.*;
import pers.qianyucc.qblog.model.vo.*;
import java.util.*;
import java.util.stream.*;
@Service
public class CategoryService {
@Autowired
private ArticleMapper articleMapper;
public List<CategoryVO> getAllCategories() {
QueryWrapper<ArticlePO> wrapper = new QueryWrapper<>();
wrapper.select("category as `name`", "count(1) as `count`").groupBy("category");
List<CategoryVO> list = articleMapper.selectMaps(wrapper).stream()
.map(CategoryVO::fromMap)
.collect(Collectors.toList());
return list.isEmpty() ? new ArrayList<>() : list;
}
public PageVO<ArticleVO> getArticleByCategory(String categoryName, Integer page, Integer limit) {
QueryWrapper<ArticlePO> wrapper = new QueryWrapper<>();
wrapper.select(ArticlePO.class, i -> !"content".equals(i.getColumn()))
.eq("category", categoryName);
Page<ArticlePO> res = articleMapper.selectPage(new Page<>(page, limit), wrapper);
List<ArticleVO> articleVOS = res.getRecords().stream()
.map(ArticleVO::fromArticlePO)
.collect(Collectors.toList());
PageVO<ArticleVO> pv = PageVO.<ArticleVO>builder()
.records(articleVOS.isEmpty() ? new ArrayList<>() : articleVOS)
.total(res.getTotal())
.current(res.getCurrent())
.size(res.getSize())
.build();
return pv;
}
}
在查询所有分类的时候我们要连带查询出分类对应的文章数,所以封装了一个 CategoryVO 类,这里也可以使用 Map ,但是个人感觉封装对象的可读性会更好
CategoryVO
package pers.qianyucc.qblog.model.vo;
import lombok.*;
import java.util.*;
@Data
@Builder
public class CategoryVO {
private String name;
private String count;
public static CategoryVO fromMap(Map<String, Object> map) {
return new CategoryVO.Converter().convertToVO(map);
}
private static class Converter implements IConverter<Map<String,Object>, CategoryVO> {
@Override
public CategoryVO convertToVO(Map<String, Object> map) {
CategoryVO vo = CategoryVO.builder()
.name(String.valueOf(map.get("name")))
.count(String.valueOf(map.get("count")))
.build();
return vo;
}
}
}
说明:
- 这里转成 String 的时候尽量使用 String.valueOf 而不要使用强制类型转换,因为包装类直接转成 String 类会报错
参考:https://gitee.com/qianyucc/QBlog2/tree/v-4.0