1. 工程搭建

前端的工程运行流程:

进入项目目录执行cmd命令:

若是第一次启动需要依次输入如下命令:

npm install
npm run build
npm run dev

之后直接执行 npm run dev 即可!

1.1 新建maven工程

新建maven工程blog作为父工程,然后在父工程中创建子工程blog-api

向父工程的pom.xml文件中导入依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cherriesovo</groupId>
    <artifactId>blog</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>blog-api</module>
        <module>blog-admin</module>
    </modules>
    <packaging>pom</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencyManagement>
    <dependencies>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.10</version>
        </dependency>
    </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.2 配置

  • 在子工程的resources文件夹下创建application.properties文件:

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl:打印日志

mybatis-plus.global-config.db-config.table-prefix=ms_:标识数据库中表名的前缀为 ms_

#server 端口号与前端对应
server.port= 8888
spring.application.name=cherriesovo_blog
# datasource
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#mybatis-plus
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.global-config.db-config.table-prefix=ms_
  • 在com.cherriesovo.blog下创建config包,创建MybatisPlusConfig配置类,用于配置MyBatis-Plus 的分页插件,使得在使用 MyBatis-Plus 进行数据库操作时能够支持分页查询功能:
package com.cherriesovo.blog.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration	//注解表示这是一个配置类
//扫描指定的包路径下的 MyBatis Mapper 接口,并将其注册到 Spring 容器中
@MapperScan("com.cherriesovo.blog.dao.mapper")
public class MybatisplusConfig {

    //配置MyBatis-Plus 的分页插件,使得在使用 MyBatis-Plus 进行数据库操作时能够支持分页查询功能
    
    @Bean	//表示一个 Spring Bean 的定义
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //PaginationInnerInterceptor 是内部拦截器,用于实现分页功能
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

跨域配置是指在 Web 开发中处理跨域资源共享的设置和策略。当一个网页通过 AJAX 请求获取其他域名下的资源时,如果目标资源所在的域名、协议、端口与当前页面不一致,就会出现跨域请求。为了加强安全性,浏览器会阻止跨域请求,除非目标服务器允许来自其他域的请求。

在 Vue 项目中,当前端代码部署在一个域名下,而后端 API 服务部署在另一个域名下时,就会涉及到跨域请求。为了解决跨域问题,需要在后端服务器上进行跨域配置,以允许特定来源(origin)的请求访问资源。

常见的跨域配置包括:

  1. 设置响应头:后端服务器可以在 HTTP 响应头中添加特定的字段,如 Access-Control-Allow-OriginAccess-Control-Allow-Methods 等,来指定允许跨域请求的来源、请求方法等信息。
  2. 使用代理:在开发环境中,可以配置代理服务器来转发 API 请求,使得前端代码和后端 API 请求处于同一个域名下,避免跨域问题。
  3. JSONP 跨域请求:JSONP 是一种跨域请求的方式,通过动态创建 <script> 标签实现跨域数据获取,不受同源策略的限制。
  4. CORS 中间件:一些后端框架提供了专门用于处理 CORS 的中间件或插件,通过简单的配置即可实现跨域资源共享。
  • 在config包下,创建WebMVCConfig跨域配置类:
package com.cherriesovo.blog.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//WebMvcConfigurer 是 Spring MVC 的配置接口,通过实现该接口可以对 Spring MVC 进行配置。
@Configuration
public class WebMVCConfig  implements WebMvcConfigurer {

    /*
    * 该方法用于配置跨域请求的规则。在这里,它指定了允许来自 http://localhost:8080 的请求访问所有的资源(/**),
    *并且允许跨域请求的请求头和方法。
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //跨域配置,不可设置为*,不安全, 前后端分离项目,可能域名不一致
        //本地测试 端口不一致 也算跨域
        //允许http://localhost:8080访问所有端口
        registry.addMapping("/**").allowedOrigins("http://localhost:8080");
    }
}

1.3 启动类

package com.cherriesovo.blog;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BlogApp {

    public static void main(String[] args) {
        SpringApplication.run(BlogApp.class,args);
    }
}

2. 首页-文章列表

2.1 接口说明

接口url:/articles

请求方式:POST

请求参数:

参数名称 参数类型 说明
page int 当前页数
pageSize int 每页显示的数量

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id": 1,
            "title": "springboot介绍以及入门案例",
            "summary": "通过Spring Boot实现的服务,只需要依靠一个Java类,把它打包成jar,并通过`java -jar`命令就可以运行起来。\r\n\r\n这一切相较于传统Spring应用来说,已经变得非常的轻便、简单。",
            "commentCounts": 2,
            "viewCounts": 54,
            "weight": 1,
            "createDate": "2609-06-26 15:58",
            "author": "12",
            "body": null,
            "tags": [
                {
                    "id": 5,
                    "avatar": null,
                    "tagName": "444"
                },
                {
                    "id": 7,
                    "avatar": null,
                    "tagName": "22"
                },
                {
                    "id": 8,
                    "avatar": null,
                    "tagName": "11"
                }
            ],
            "categorys": null
        },
        {
            "id": 9,
            "title": "Vue.js 是什么",
            "summary": "Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。",
            "commentCounts": 0,
            "viewCounts": 3,
            "weight": 0,
            "createDate": "2609-06-27 11:25",
            "author": "12",
            "body": null,
            "tags": [
                {
                    "id": 7,
                    "avatar": null,
                    "tagName": "22"
                }
            ],
            "categorys": null
        },
        {
            "id": 10,
            "title": "Element相关",
            "summary": "本节将介绍如何在项目中使用 Element。",
            "commentCounts": 0,
            "viewCounts": 3,
            "weight": 0,
            "createDate": "2609-06-27 11:25",
            "author": "12",
            "body": null,
            "tags": [
                {
                    "id": 5,
                    "avatar": null,
                    "tagName": "444"
                },
                {
                    "id": 6,
                    "avatar": null,
                    "tagName": "33"
                },
                {
                    "id": 7,
                    "avatar": null,
                    "tagName": "22"
                },
                {
                    "id": 8,
                    "avatar": null,
                    "tagName": "11"
                }
            ],
            "categorys": null
        }
    ]
}

2.2 编码

2.2.1 表结构

文章表:

CREATE TABLE `blog`.`ms_article`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `comment_counts` int(0) NULL DEFAULT NULL COMMENT '评论数量',
  `create_date` bigint(0) NULL DEFAULT NULL COMMENT '创建时间',
  `summary` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '简介',
  `title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',
  `view_counts` int(0) NULL DEFAULT NULL COMMENT '浏览数量',
  `weight` int(0) NOT NULL COMMENT '是否置顶',
  `author_id` bigint(0) NULL DEFAULT NULL COMMENT '作者id',
  `body_id` bigint(0) NULL DEFAULT NULL COMMENT '内容id',		#和article_body表连接
  `category_id` int(0) NULL DEFAULT NULL COMMENT '类别id',	#和category表连接
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

标签表:

#`id`字段没有其他意义,只是代表一条数据,每条数据中存放着文章id和标签id的对应关系,真正的标签id存放在ms_tag表中。
# 两张表通过tag_id进行连接
CREATE TABLE `blog`.`ms_article_tag`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `article_id` bigint(0) NOT NULL,
  `tag_id` bigint(0) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `article_id`(`article_id`) USING BTREE,
  INDEX `tag_id`(`tag_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

用户表:

CREATE TABLE `blog`.`ms_sys_user`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `account` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号',
  `admin` bit(1) NULL DEFAULT NULL COMMENT '是否管理员',
  `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像',
  `create_date` bigint(0) NULL DEFAULT NULL COMMENT '注册时间',
  `deleted` bit(1) NULL DEFAULT NULL COMMENT '是否删除',
  `email` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `last_login` bigint(0) NULL DEFAULT NULL COMMENT '最后登录时间',
  `mobile_phone_number` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号',
  `nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `salt` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加密盐',
  `status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
  • 创建包com.cherriesovo.blog.dao.pojocom.cherriesovo.blog.dao.mapper

文章实体类:

package com.cherriesovo.blog.dao.pojo;

import lombok.Data;

@Data
public class Article {

    public static final int Article_TOP = 1;

    public static final int Article_Common = 0;

    private Long id;

    private String title;

    private String summary;

    private int commentCounts;

    private int viewCounts;

    /**
     * 作者id
     */
    private Long authorId;
    /**
     * 内容id
     */
    private Long bodyId;
    /**
     *类别id
     */
    private Long categoryId;

    /**
     * 置顶
     */
    private int weight = Article_Common;


    /**
     * 创建时间
     */
    private Long createDate;
}

用户实体类:

package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class SysUser {

    private Long id;

    private String account;

    private Integer admin;

    private String avatar;

    private Long createDate;

    private Integer deleted;

    private String email;

    private Long lastLogin;

    private String mobilePhoneNumber;

    private String nickname;

    private String password;

    private String salt;

    private String status;
}

标签实体类:

package com.mszlu.blog.dao.pojo;

import lombok.Data;

@Data
public class Tag {

    private Long id;

    private String avatar;

    private String tagName;

}

2.2.2 Controller

在 Spring Boot 项目中,vo 目录通常用来存放值对象(Value Object)。值对象是一种用于封装多个属性或字段的简单对象,通常用于数据传输、数据展示或领域模型中。

值对象的特点是它们是不可变的(Immutable),也就是说一旦创建之后,其属性值就不能再被修改。这种不可变性使得值对象更加安全和可靠,可以避免出现意外的状态变化。

在项目中,vo 目录可能包含一些用于表示业务领域中的概念或承载某个特定用途的值对象。这些值对象可以用于封装一些复杂的数据结构,提供更加清晰和可读性强的代码。

例如,一个电子商务系统中的订单对象可以包含多个属性,如订单号、下单时间、商品列表等。为了方便传输和展示,可以定义一个 OrderVO 值对象,其中包含上述属性的对应字段。在业务逻辑中,可以使用 OrderVO 对象进行数据传输和展示,并且保持其不可变性。

总之,vo 目录用来存放值对象,将复杂的数据结构封装起来,提高代码的可读性和可维护性。

  • 创建包com.cherriesovo.blog.vo.params;在该包下创建实体类PageParams用来存放页面参数
package com.cherriesovo.blog.vo.params;

import lombok.Data;

@Data
public class PageParams {
    private int page = 1;
    private int pageSize = 10;

}
  • 在包com.cherriesovo.blog.vo.params下创建实体类Result用来存放返回结果
package com.cherriesovo.blog.vo.params;

import com.cherriesovo.blog.dao.pojo.Article;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {

    private boolean success;

    private Integer code;

    private String msg; //信息

    private Object data;    //数据


    public static Result success(Object data) {
        return new Result(true,200,"success",data);
    }
    public static Result fail(Integer code, String msg) {
        return new Result(false,code,msg,null);
    }
}
  • 创建文章控制类ArticleController

1、@RequestBody这个注解用于将 HTTP 请求的内容(JSON 格式的数据)绑定到 PageParams 对象上。HTTP 请求的内容包含page和pageSize两个参数,PageParams 是一个值对象。

2、articleService.listArticlesPage(pageParams):调用 ArticleService 中的 listArticlesPage 方法,传入 PageParams 对象,以获取文章列表。

3、Result.success(articles)Result 是一个统一结果返回的工具类,Result.success 方法用于返回成功的结果,该结果也是一个Result类,其中包含文章列表数据。这个方法会将文章列表转换为 JSON 格式并返回给客户端

package com.cherriesovo.blog.controller;

import com.cherriesovo.blog.dao.pojo.Article;
import com.cherriesovo.blog.service.ArticleService;
import com.cherriesovo.blog.vo.params.ArticleVo;
import com.cherriesovo.blog.vo.params.PageParams;
import com.cherriesovo.blog.vo.params.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

//json数据进行交互,其中的方法会返回 JSON 格式的数据
@RestController
@RequestMapping("articles")
public class ArticleController {

    //将ArticleService 自动注入到控制器中,这样控制器就可以调用 ArticleService 中的方法。
    @Autowired
    private ArticleService articleService;
    //Result是统一结果返回
    @PostMapping
    public Result articles(@RequestBody PageParams pageParams) {
        //ArticleVo 页面接收的数据
        List<ArticleVo> articles = articleService.listArticlesPage(pageParams);

        return Result.success(articles);
    }


}

  • 创建ArticleVo来接收页面的数据(data)

package com.cherriesovo.blog.vo.params;


import com.cherriesovo.blog.dao.pojo.SysUser;
import com.cherriesovo.blog.dao.pojo.Tag;
import lombok.Data;

import java.util.List;

@Data
public class ArticleVo {

    private Long id;

    private String title;

    private String summary;

    private int commentCounts;

    private int viewCounts;

    private int weight;
    /**
     * 创建时间
     */
    private String createDate;

    private String author;

//    private ArticleBodyVo body;

    private List<TagVo> tags;

//    private List<CategoryVo> categorys;

}

2.2.3 Service

分页查询文章列表

package com.cherriesovo.blog.service;

//import com.cherriesovo.blog.vo.Archive;
import com.cherriesovo.blog.vo.ArticleVo;
import com.cherriesovo.blog.vo.params.PageParams;

import java.util.List;

public interface ArticleService {
    //分页查询文章列表
    List<ArticleVo> listArticlesPage(PageParams pageParams);

}

1、QueryWrapper

queryWrapper = new QueryWrapper<>()详解:

QueryWrapper<Article> 是 MyBatis-Plus 提供的一个查询条件构造器,用于构建数据库查询条件。

在这段代码中,QueryWrapper<Article> queryWrapper = new QueryWrapper<>(); 创建了一个 QueryWrapper 对象,并指定其泛型为 Article,表示要查询的是 Article 数据库表。

QueryWrapper 类提供了一系列方法,可以通过链式调用来设置查询条件,例如 eq()ne()like() 等。这些方法可以根据需要来组合使用,构建出复杂的查询条件。

在这里,queryWrapper 对象是用于分页查询文章数据的条件构造器,但是在代码中没有显式地设置任何查询条件。这种情况下,如果不设置任何查询条件,QueryWrapper 会默认查询整个表的数据,即相当于 SELECT * FROM Article

如果想要添加具体的查询条件,可以在 queryWrapper 上使用相应的方法进行设置。例如,可以使用 eq("column", value) 方法来设置等于某个字段值的查询条件。

示例:

javaCopy CodequeryWrapper.eq("author", "John Doe"); // 查询作者为 "John Doe" 的文章
queryWrapper.like("title", "Java"); // 查询标题中包含 "Java" 的文章

最后,在分页查询时将 queryWrapper 对象传递给 selectPage() 方法,这样可以在查询过程中应用设置的查询条件。

2、Page

page = new Page<>(pageParams.getPage(), pageParams.getPageSize())详解:

该语句创建了一个 Page 对象,用于表示分页查询的相关信息。

Page 是 MyBatis-Plus 提供的一个分页对象,它包含了分页查询所需的各种信息,如当前页码、每页大小、总记录数等。

在这段代码中,pageParams 是传入的分页参数对象,其中包括了当前页码和每页大小。通过调用 getPage()getPageSize() 方法,可以获取分页参数的具体值。

然后,将获取到的当前页码和每页大小作为参数传递给 Page 的构造方法 new Page<>(pageParams.getPage(), pageParams.getPageSize()),从而创建了一个 Page 对象 page

这个 page 对象会在后续的分页查询中被传递给 selectPage() 方法,用于告知数据库查询时的分页信息,以及接收数据库返回的分页查询结果。

  1. copy 方法用于将 Article 对象转换为 ArticleVo 对象,并根据参数 isAuthorisBodyisTags 决定是否需要拷贝作者信息、文章正文和标签信息。
    1. 使用 BeanUtils.copyProperties 方法将 Article 对象 article 的属性复制到 articleVo 中。这里使用了 Spring 的 BeanUtils 工具类来实现属性的拷贝。
    2. Article 对象的创建时间属性 createDate 转换为指定格式的字符串,并设置到 articleVo 对象的 createDate 属性中。这里使用了 Joda-Time 库的 DateTime 类来进行日期时间的格式化。
    3. 如果 isTags 参数为 true,则通过 tagService.findTagsByArticleId(articleId) 方法获取该文章的标签信息,并设置到 articleVo 对象的 tags 属性中。
    4. 如果 isAuthor 参数为 true,则通过 sysUserService.findUserById(authorId) 方法获取作者的用户信息,并从中获取昵称(使用 getNickname() 方法),然后将昵称设置到 articleVo 对象的 author 属性中
  2. listArticlesPage分页查询文章列表,并将查询结果转换为对应的 ArticleVo 对象列表返回给调用方。
    1. 首先创建了一个 QueryWrapper<Article> 对象 queryWrapper,用于构建查询条件。然后创建了一个 Page<Article> 对象 page,表示要查询的分页信息
    2. 调用 articleMapper.selectPage(page, queryWrapper) 方法进行分页查询文章数据。这里使用了 MyBatis-Plus 提供的 selectPage 方法,它会根据传入的 Page 对象和查询条件进行分页查询,并返回一个分页后的文章数据对象 articlePage
    3. 通过调用 getRecords() 方法,可以获取当前页的记录列表,即符合查询条件的文章记录列表。这个方法会返回一个 List<Article> 对象,其中包含了当前页的所有文章记录。
    4. 将分页查询得到的文章记录列表 articlePage.getRecords() 传入 copyList() 方法中,同时传入三个布尔类型的参数
package com.cherriesovo.blog.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cherriesovo.blog.dao.mapper.ArticleMapper;
import com.cherriesovo.blog.dao.pojo.Article;
import com.cherriesovo.blog.service.ArticleService;
//import com.cherriesovo.blog.service.SysUserService;
////import com.cherriesovo.blog.service.TagsService;
////import com.cherriesovo.blog.vo.ArticleBodyVo;
import com.cherriesovo.blog.service.SysUserService;
import com.cherriesovo.blog.service.TagService;
import com.cherriesovo.blog.vo.ArticleVo;
//import com.cherriesovo.blog.vo.TagVo;
import com.cherriesovo.blog.vo.params.PageParams;
import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private ArticleMapper articleMapper;
    @Autowired
    private TagService tagService;
    @Autowired
    private SysUserService sysUserService;

    public ArticleVo copy(Article article,boolean isAuthor,boolean isBody,boolean isTags){
        ArticleVo articleVo = new ArticleVo();
        BeanUtils.copyProperties(article, articleVo);

        articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyyy-MM-dd HH:mm"));
        //并不是所有的接口都需要标签,作者信息
        if(isTags){
            Long articleId = article.getId();
            articleVo.setTags(tagService.findTagsByArticleId(articleId));
        }
        if(isAuthor){
            Long authorId = article.getAuthorId();
            //getNickname()用于获取某个对象或实体的昵称或别名
            articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
        }
        return articleVo;
    }

    private List<ArticleVo> copyList(List<Article> records,boolean isAuthor,boolean isBody,boolean isTags) {
        List<ArticleVo> articleVoList = new ArrayList<>();
        for (Article article : records) {
            ArticleVo articleVo = copy(article,isAuthor,isBody,isTags);
            articleVoList.add(articleVo);	//add() 方法是 ArrayList 类的一个方法,用于向列表末尾添加元素
        }
        return articleVoList;
    }


    @Override
    public List<ArticleVo> listArticlesPage(PageParams pageParams) {
    //  分页查询article数据库表
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
        Page<Article> page = new Page<>(pageParams.getPage(),pageParams.getPageSize());
        Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);	//分页查询文章数据
        List<ArticleVo> articleVoList = copyList(articlePage.getRecords(),true,false,true);
        return articleVoList;
    }
}

package com.cherriesovo.blog.service;

import com.cherriesovo.blog.dao.pojo.SysUser;

public interface SysUserService {
    SysUser findUserById(Long id);
}

package com.cherriesovo.blog.service.impl;

import com.cherriesovo.blog.dao.mapper.SysUserMapper;
import com.cherriesovo.blog.dao.pojo.SysUser;
import com.cherriesovo.blog.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class SysUserServiceImpl implements SysUserService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Override
    public SysUser findUserById(Long userId) {
        //selectById() 方法是 MyBatis-Plus 提供的
        SysUser sysUser = sysUserMapper.selectById(userId);
        if (sysUser == null) {
            sysUser = new SysUser();
            sysUser.setNickname("CherriesOvO");
        }
        return sysUser;
    }
}

package com.cherriesovo.blog.service;

import com.cherriesovo.blog.vo.TagVo;

import java.util.List;

public interface TagService {
    List<TagVo> findTagsByArticleId(Long articleId);
}

package com.cherriesovo.blog.service.impl;

import com.cherriesovo.blog.dao.mapper.TagMapper;
import com.cherriesovo.blog.dao.pojo.Tag;
import com.cherriesovo.blog.service.TagService;
import com.cherriesovo.blog.vo.TagVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service    //将类标记为服务组件,供其他组件使用
public class TagServiceImpl implements TagService {
    @Autowired
    private TagMapper tagMapper;

    public TagVo copy(Tag tag){
        TagVo tagVo = new TagVo();
        BeanUtils.copyProperties(tag,tagVo);
        return tagVo;
    }
    public List<TagVo> copyList(List<Tag> tagList){
        List<TagVo> tagVoList = new ArrayList<>();
        for (Tag tag : tagList) {
            tagVoList.add(copy(tag));
        }
        return tagVoList;
    }

    @Override
    public List<TagVo> findTagsByArticleId(Long articleId) {
        //mybatisplus无法进行多表查询
        List<Tag> tags = tagMapper.findTagsByArticleId(articleId);
        return copyList(tags);
    }
}

2.2.4 Dao

ArticleMapper

package com.cherriesovo.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cherriesovo.blog.dao.pojo.Article;

//BaseMapper<Article>是mybatisplus提供的,可以很方便的查询Article这张表
/*1、通常情况下,BaseMapper 可能包含一些通用的数据库操作方法的定义,
    比如插入数据、更新数据、删除数据、查询数据等。
    而 ArticleMapper 则可以在此基础上添加针对 Article 实体类的特定数据库操作方法,如根据标题查询文章、根据作者查询文章等。
2、通过这样的设计,可以实现代码的复用,避免重复编写相似的数据库操作方法。
    在具体的实现中,可以在 ArticleMapper 接口中编写与 Article 实体类相关的数据库操作方法,并在其中调用 BaseMapper 中定义的通用方法来实现具体的业务逻辑。
 */
public interface ArticleMapper extends BaseMapper<Article> {
}

TagMapper

package com.cherriesovo.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cherriesovo.blog.dao.pojo.Tag;

import java.util.List;

public interface TagMapper extends BaseMapper<Tag> {

    //根据文章id查询标签列表
    List<Tag> findTagsByArticleId(Long articleId);
}

SysUserMapper

package com.cherriesovo.blog.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cherriesovo.blog.dao.pojo.SysUser;

public interface SysUserMapper extends BaseMapper<SysUser> {
}

com.cherriesovo.blog.dao,mapper.TagMapper.xml

(注意:xml文件的目录结构要与其对应的Mapper保持一致。)

<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mszlu.blog.dao.TagMapper">

    <sql id="all">
        id,avatar,tag_name as tagName
    </sql>

    <select id="findTagsByArticleId" parameterType="long" resultType="com.mszlu.blog.dao.pojo.Tag">
        select <include refid="all" />  from ms_tag
        <where>
            id in
            (select tag_id from ms_article_tag where article_id = #{articleId})
        </where>
    </select>
</mapper>

出现的问题:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.cherriesovo.blog.dao.mapper.TagMapper.findTagsByArticleId

解决方法:

手动将resources文件夹设置为资源目录

2.2.5 测试

3. 首页-最热标签

3.1 接口说明

接口url:/tags/hot

请求方式:GET

请求参数:无

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id":1,
            "tagName":"4444"
        }
    ]
}

3.2 编码

3.2.1 Controller

package com.cherriesovo.blog.controller;

import com.cherriesovo.blog.service.TagService;
import com.cherriesovo.blog.vo.Result;
import com.cherriesovo.blog.vo.TagVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("tags")
public class TagsController {

    @Autowired
    private TagService tagsService;

    //路径:/tags/hot
    @GetMapping("/hot")
    public Result listHotTags() {
        int limit = 6;  //查询最热的6个标签
        List<TagVo> tagVoList = tagsService.hot(limit);
        return Result.success(tagVoList);
    }

}
package com.cherriesovo.blog.vo;

import lombok.Data;

@Data
public class TagVo {

    private  Long id;
    private String tagName;
}

3.2.2 Service

package com.cherriesovo.blog.service;

import com.cherriesovo.blog.vo.TagVo;

import java.util.List;

public interface TagService {
    List<TagVo> hot(int limit);
}
package com.cherriesovo.blog.service.impl;

import com.cherriesovo.blog.dao.mapper.TagMapper;
import com.cherriesovo.blog.dao.pojo.Tag;
import com.cherriesovo.blog.service.TagService;
import com.cherriesovo.blog.vo.Result;
import com.cherriesovo.blog.vo.TagVo;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.Collections;

@Service    //将类标记为服务组件,供其他组件使用
public class TagServiceImpl implements TagService {
    @Autowired
    private TagMapper tagMapper;

    public TagVo copy(Tag tag){
        TagVo tagVo = new TagVo();
        BeanUtils.copyProperties(tag,tagVo);
        return tagVo;
    }
    public List<TagVo> copyList(List<Tag> tagList){
        List<TagVo> tagVoList = new ArrayList<>();
        for (Tag tag : tagList) {
            tagVoList.add(copy(tag));
        }
        return tagVoList;
    }

    //最热标签
    @Override
    public List<TagVo> hot(int limit) {
        //什么是最热标签?
        /*
            1、标签拥有的文章数量最多——最热标签
            2、查询:根据tag_id进行group by 分组、计数,从大到小排序,取前limit
        */
        List<Long> hotsTagIds = tagMapper.findHotsTagIds(limit);
        if (CollectionUtils.isEmpty(hotsTagIds)){
            return Collections.emptyList();
        }
        //需求的是tagId和tagName
        List<Tag> tagList = tagMapper.findTagsByTagIds(hotsTagIds);
        return copyList(tagList);
    }
}


3.2.3 Dao

package com.mszlu.blog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mszlu.blog.dao.pojo.Tag;

import java.util.List;

public interface TagMapper extends BaseMapper<Tag> {
    
    List<Long> findHotsTagIds(int limit);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.cherriesovo.blog.dao.mapper.TagMapper">

<!--List<Long> findHotsTagIds(int limit);-->
    <select id="findHotsTagIds" parameterType="int" resultType="long">
        select tag_id from `ms_article_tag` group by tag_id order by count(*) desc limit #{limit}
    </select>
    
</mapper>

3.2.4 测试