目录

1. 工程搭建

1.1 新建maven工程

1.2 配置

1.3 启动类

2. 首页-文章列表

2.1 接口说明

2.2 编码

2.2.1 表结构

2.2.2 Controller(控制层)

2.2.3 VO层

2.2.4 Service层

2.2.5 Dao层(与数据库进行交互)

2.2.6 测试

3. 首页-最热标签

3.1 接口说明

3.2 编码

3.2.1 Controller

3.2.2 Service

3.2.3 Dao

3.2.4 测试

4. 统一异常处理

5. 首页-最热文章

5.1 接口说明

5.2 Controller

5.3 Service

5.4 测试

6. 首页-最新文章

6.1 接口说明

6.2 Controller

6.3 Service

6.4 测试

7. 首页-文章归档

7.1接口说明

7.1 Controller

7.2 Service

7.3 Dao

7.4 测试


项目使用技术 :

springboot + mybatisplus+redis+mysql

提供前端工程,只需要实现后端接口即可

1. 工程搭建

前端的工程:

npm install
npm run build
npm run dev

1.1 新建maven工程

父工程

<?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.pjp</groupId>
  <artifactId>pjpBlog-parent</artifactId>
  <version>1.0-SNAPSHOT</version>

  <!--    <properties>-->
  <!--        <maven.compiler.source>8</maven.compiler.source>-->
  <!--        <maven.compiler.target>8</maven.compiler.target>-->
  <!--        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>-->
  <!--    </properties>-->
  <packaging>pom</packaging>
  <modules>
    <module>blog-api</module>
  </modules>
  <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>

子工程

<?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>
  <parent>
    <groupId>com.pjp</groupId>
    <artifactId>pjpBlog-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>

  <artifactId>blog-api</artifactId>

  <!--    <properties>-->
  <!--        <maven.compiler.source>8</maven.compiler.source>-->
  <!--        <maven.compiler.target>8</maven.compiler.target>-->
  <!--        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>-->
  <!--    </properties>-->
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <!-- 排除 默认使用的logback  -->
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

    <!-- log4j2 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>


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

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-configuration-processor</artifactId>
      <optional>true</optional>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>

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

        <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3</version>
        </dependency>
        <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.10.10</version>
        </dependency>
        <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
        </dependency>

        <dependency>
        <groupId>com.qiniu</groupId>
        <artifactId>qiniu-java-sdk</artifactId>
        <version>[7.7.0, 7.7.99]</version>
        </dependency>
        <dependency>
        <groupId>com.github.ulisesbocchio</groupId>
        <artifactId>jasypt-spring-boot-starter</artifactId>
        <version>2.1.1</version>
        </dependency>
        </dependencies>

        </project>

1.2 配置

配置文件 application.propertis

#server
server.port= 8888
spring.application.name=PJPBlog

# datasource
spring.datasource.url=jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=pjp
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#mybatis-plus
#打印日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#标识表数据的前缀为ms_
mybatis-plus.global-config.db-config.table-prefix=ms_

配置类

@Configuration
@MapperScan("com.pjp.blog.dao.mapper")
public class MybatisPlusConfig {

    //分页插件
    @Bean
    //    通过拦截器实现分页
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //跨域配置,不可设置为*,不安全, 前后端分离项目,可能域名不一致
        //本地测试 端口不一致 也算跨域
        registry.addMapping("/**").allowedOrigins("http://localhost:8080");
    }
}

1.3 启动类

@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": "Mybatis-Plus如何使用分页",
            "summary": "导入Mybatis-plus的启动器配置分页拦截器利用mybatis-plus的内置Page对象进行分页,然后在vo包下编写一个从页面传参数类TopicCondition(vo包里的类的每一个字段与前台页面相对应)",
            "commentCounts": 2,
            "viewCounts": 1000,
            "weight": 1,
            "createDate": "2611-02-03 04:43",
            "author": "pjp",
            "tags": [
                {
                    "id": 5,
                    "tagName": "Mybatis-Plus",
                    "avatar": null
                },
                {
                    "id": 7,
                    "tagName": "表达式",
                    "avatar": null
                },
                {
                    "id": 8,
                    "tagName": "Java",
                    "avatar": null
                }
            ]
        },
        {
            "id": 10,
            "title": "queryWrapper",
            "summary": "queryWrapper是mybatis plus中实现查询的对象封装操作类,可以封装sql对象,包括where条件,order by排序,select哪些字段等等",
            "commentCounts": 1,
            "viewCounts": 56,
            "weight": 0,
            "createDate": "2611-02-02 03:45",
            "author": "pjp",
            "tags": [
                {
                    "id": 7,
                    "tagName": "表达式",
                    "avatar": null
                },
                {
                    "id": 8,
                    "tagName": "Java",
                    "avatar": null
                },
                {
                    "id": 5,
                    "tagName": "Mybatis-Plus",
                    "avatar": null
                },
                {
                    "id": 6,
                    "tagName": "Lambda",
                    "avatar": null
                }
            ]
        },
        {
            "id": 9,
            "title": "Java中Lambda表达式使用",
            "summary": "Lambda表达式(闭包):java8的新特性,lambda运行将函数作为一个方法的参数,也就是函数作为参数传递到方法中。使用lambda表达式可以让代码更加简洁。",
            "commentCounts": 0,
            "viewCounts": 18,
            "weight": 0,
            "createDate": "2610-10-02 09:02",
            "author": "pjp",
            "tags": [
                {
                    "id": 7,
                    "tagName": "表达式",
                    "avatar": null
                }
            ]
        }
    ]
}

2.2 编码

2.2.1 表结构
REATE 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',
  `category_id` int(0) NULL DEFAULT NULL COMMENT '类别id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 25 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
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_tag`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  `tag_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 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 R

对应的pojo实体类

Article

package com.pjp.blog.dao.pojo;

import lombok.Data;

@Data
//@Data 注解的主要作用是提高代码的简洁,使用这个注解可以
// 省去代码中大量的get()、 set()、 toString()等方法;
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 Integer commentCounts;

    private Integer viewCounts;

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

    /**
    * 置顶
    */
    private Integer weight;


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

SysUser

package com.pjp.blog.dao.pojo;

import lombok.Data;

@Data
public class SysUser {
    //    @TableId(type = IdType.ASSIGN_ID) // 默认id类型
    // 以后 用户多了之后,要进行分表操作,id就需要用分布式id了
    //    @TableId(type = IdType.AUTO) 数据库自增
    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;
}

Tag

package com.pjp.blog.dao.pojo;

import lombok.Data;

@Data
public class Tag {
    private Long id;

    private String avatar;

    private String tagName;
}
2.2.2 Controller(控制层)
package com.pjp.blog.controller;

import com.pjp.blog.service.ArticleService;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.params.PageParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//json数据进行交互
@RestController
@RequestMapping("/articles")
public class ArticleController {
    @Autowired
    private ArticleService articleService;
    /**
    * 首页 文章列表
    * @param pageParams
    * @return
    */

    @PostMapping
    //    @RequestBody接收返回的请求信息 page 和 pageSize
    public Result listArticle(@RequestBody PageParams pageParams){
        return articleService.listArticle(pageParams);
    }
}
2.2.3 VO层

View Object,视图层,其作用是将指定页面的展示数据封装起来,通常用于业务层之间的数据传递。

params包下的类主要用于封装前端返回的json参数

PageParams

@Data
public class PageParams {

    //    请求参数
    private int page = 1; //当前页数

    private int pageSize = 10; //每页显示的数量
}

Result返回值

package com.pjp.blog.vo;

import com.sun.org.apache.regexp.internal.RE;
import lombok.AllArgsConstructor;
import lombok.Data;

/**
* 使用@Data时,默认存在无参构造器(没手写有参构造器)
* 使用@Data时,手写了有参构造器,则无参构造器消失
*
* @AllArgsConstructor : 注在类上,提供类的全参构造
*/
@Data
@AllArgsConstructor
public class Result {
    private boolean success;
    private int code;
    private String msg;
    private Object data;

    public static Result success(Object data) {
        return new Result(true, 200, "success", data);
    }

    public static Result fail(int code, String msg) {
        return new Result(false, code, msg, null);
    }
}

ArticleVo

/**
* 和前端数据进行交互
*/
@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.4 Service层

ArticleService

public interface ArticleService {
    /**
    * 分页查询 文章列表
    * @param pageParams
    * @return
    */
    Result listArticle(PageParams pageParams);
}

实现类

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

@Autowired
private SysUserService sysUserService;

@Override
public Result listArticle(PageParams pageParams) {
    Page<Article> page = new Page<>(pageParams.getPage(), pageParams.getPageSize());
    //        LambdaQueryWrapper可以使用lambda表达式构建SQL查询语句
    LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
    //        按时间进行排序
    //        queryWrapper.orderByDesc(“属性”)——根据属性降序排序
    //        queryWrapper.orderByDesc(Article::getCreateDate);
    //        是否置顶排序
    //        queryWrapper.orderByDesc(Article::getWeight);
    queryWrapper.orderByDesc(Article::getWeight,Article::getCreateDate);
    Page<Article> articlePage = articleMapper.selectPage(page, queryWrapper);
    //        getRecords();查询到的数据
    List<Article> records = articlePage.getRecords();
    //      数据不能直接返回,和前端进行数据交互
    List<ArticleVo> articleVoList = copyList(records,true,true);
    return Result.success(articleVoList);
}

private List<ArticleVo> copyList(List<Article> records, boolean isTag, boolean isAuthor) {
    List<ArticleVo> articleVoList = new ArrayList<>();
    for (Article article : records) {
        articleVoList.add(cope(article,isTag,isAuthor));
    }
    return articleVoList;
}

private ArticleVo cope(Article article, boolean isTag, boolean isAuthor) {
    ArticleVo articleVo = new ArticleVo();
    BeanUtils.copyProperties(article, articleVo);
    //      vo中createDate为String Article中为long copy不了
    articleVo.setCreateDate(new DateTime(article.getCreateDate()).toString("yyy-MM-dd HH:ss"));
    //        不是所有的接口 都需要标签 作者信息
    if (isTag) {
        //            根据ArticleId 查询 TagId
        Long articleId = article.getId();
        articleVo.setTags(tagService.findTagByArticleId(articleId));
    }
    if (isAuthor) {
        Long authorId = article.getAuthorId();
        articleVo.setAuthor(sysUserService.findUserById(authorId).getNickname());
    }
    return articleVo;
}
}

SysUserService

public interface SysUserService {
    /**
    * 根据id查询用户信息
    * @param id
    * @return
    */
    SysUser findUserById(Long id);
}

实现类

@Service
public class SysUserServiceIml implements SysUserService {

    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public SysUser findUserById(Long id) {
        SysUser sysUser = sysUserMapper.selectById(id);
        if (sysUser == null){
            sysUser = new SysUser();
            sysUser.setNickname("pjp");
        }
        return sysUser;
    }
}

TagService

public interface TagService {
    List<TagVo> findTagByArticleId(long articleId);
}

实现类

@Service
public class TagServiceImpl implements TagService {
    @Autowired
    private TagMapper tagMapper;
    @Override
    public List<TagVo> findTagByArticleId(long articleId) {
        //        MyBatis无法进行多表查询
        List<Tag> tags = tagMapper.findTagByArticleId(articleId);
        return copyList(tags);
    }

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

SysUserService 和 TagService 在文章列表中主要是实现以下

2.2.5 Dao层(与数据库进行交互)

mapper

ArticleMapper

package com.pjp.blog.dao.mapper;

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

public interface ArticleMapper extends BaseMapper<Article> {
}

TagMapper

public interface TagMapper extends BaseMapper<Tag> {
    /**
    * 根据文章id查询标签列表
    * @param articleId
    * @return
    */
    List<Tag> findTagByArticleId(long articleId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pjp.blog.dao.mapper.TagMapper">

  <!--List<Tag> findTagByArticleId(long articleId);-->
  <select id="findTagByArticleId" parameterType="long" resultType="com.pjp.blog.dao.pojo.Tag">
    select id, avatar, tag_name as tagName
    from ms_tag
    where id in
    (select tag_id from ms_article_tag where article_id = #{articleId})
  </select>
</mapper>

SysUserMapper

public interface SysUserMapper extends BaseMapper<SysUser> {
}
2.2.6 测试

3. 首页-最热标签

3.1 接口说明

接口url:/tags/hot

请求方式:GET

请求参数:无

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id": 5,
            "tagName": "Mybatis-Plus",
            "avatar": null
        }
    ]
}

3.2 编码

3.2.1 Controller
package com.pjp.blog.controller;

import com.pjp.blog.service.TagService;
import com.pjp.blog.vo.Result;
import com.pjp.blog.vo.TagVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
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 tagService;

    @GetMapping("hot")//此时接口url相当于拼接 /tags/hot
    public Result listHotTags() {
        int limit = 6;
        List<TagVo> tagVoList = tagService.hot(limit);
        return Result.success(tagVoList);
    }
}

返回数据的封装类

@Data
public class TagVo {
    private long id;
    private String tagName;
    private String avatar;
}
@Data
@AllArgsConstructor
public class Result {
    private boolean success;
    private int code;
    private String msg;
    private Object data;

    public static Result success(Object data) {
        return new Result(true, 200, "success", data);
    }

    public static Result fail(int code, String msg) {
        return new Result(false, code, msg, null);
    }
}
3.2.2 Service
public interface TagService {
    /**
    *最热标签
    * @param limit
    * @return
    */
    List<TagVo> hot(int limit);
}
@Service
public class TagServiceImpl implements TagService {
    @Autowired
    private TagMapper tagMapper;

    @Override
    public List<TagVo> hot(int limit) {
        /**
        * 1.标签所拥有的文章数量最多 最热标签
        * 2.查询 根据tag_id 分组 计数 ,从大到小 取前limit个
        */
        List<Long> hotTagIds = tagMapper.findHotTagsId(limit);
        //没有查询到返回空List
        if (CollectionUtils.isEmpty(hotTagIds)) {
            return Collections.emptyList();
        }
        //需求的是tagId 和 tagName  Tag对象
        List<Tag> tagList = tagMapper.findHotTagsById(hotTagIds);
        return copyList(tagList);
    }

    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;
    }
}
3.2.3 Dao

Mapper

public interface TagMapper extends BaseMapper<Tag> {

    /**
    * 查找最热标签id
    * @param limit
    * @return
    */
    List<Long> findHotTagsId(int limit);

    /**
    * 查找最热标签
    * @param hotTagIds
    * @return
    */
    List<Tag> findHotTagsById(List<Long> hotTagIds);
}
3.2.4 测试

4. 统一异常处理

不管是controller层还是service,dao层,都有可能报异常,如果是预料中的异常,可以直接捕获处理,如果是意料之外的异常,需要统一进行处理,进行记录,并给用户提示相对比较友好的信息。

package com.pjp.blog.handler;

import com.pjp.blog.vo.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
* @ControllerAdvice主要用来处理全局数据
* 对加了@Controller注解的方法进行拦截处理  原理就是AOP的实现
*/
@ControllerAdvice
public class AllExceptionHandler {
    //用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的
    @ExceptionHandler(Exception.class)
    @ResponseBody//返回json数据
    public Result doException(Exception ex){
        //ex.printStackTrace()方法:在命令行打印异常信息在程序中出错的位置及原因
        ex.printStackTrace();
        return Result.fail(-999,"系统异常");
    }
}

实际开发不建议使用:
e.printStackTrace() 会导致锁死

  • 因为e.printStackTrace() 语句要产生的字符串记录的是堆栈信息,太长太多
  • 建议使用logger输出到日志文件里面。

后面改用logger输出到日志文件里面

测试

现在随便造一个异常 int i = 1/0;

//json数据进行交互
@RestController
@RequestMapping("/articles")
public class ArticleController {
    @Autowired
    private ArticleService articleService;
    @PostMapping
    //    @RequestBody接收返回的请求信息 page 和 pageSize
    public Result listArticle(@RequestBody PageParams pageParams){
        int i = 1/0;
        return articleService.listArticle(pageParams);
    }
}

前端接收到数据

idea后台

5. 首页-最热文章

5.1 接口说明

接口url:/articles/hot

请求方式:POST

请求参数:

参数名称

参数类型

说明

id

Long

文章id

title

String

文章标题

返回数据:

{
    "success": true,
    "code": 200,
    "msg": "success",
    "data": [
        {
            "id": 1,
            "title": "Mybatis-Plus如何使用分页",
        },
        {
            "id": 9,
            "title": "Java中Lambda表达式使用",
        },
        {
            "id": 10,
            "title": "queryWrapper",
            
        }
    ]
}

5.2 Controller

//json数据进行交互
@RestController
@RequestMapping("/articles")
public class ArticleController {
    @Autowired
    private ArticleService articleService;
    
   /**
     * 最热文章
     * @return
     */
    @PostMapping("hot")
    public Result listHotArticles(){
        int limit = 5;
        return articleService.listHotArticles(limit);
    }

}

5.3 Service

public interface ArticleService {
    /**
    * 最热文章
    * @param limit
    * @return
    */
    Result listHotArticles(int limit);
}
@Service
public class ArticleServiceImpl implements ArticleService {
    @Autowired
    private ArticleMapper articleMapper;


    @Override
    public Result listHotArticles(int limit) {
        LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
        //按浏览量排序
        queryWrapper.orderByDesc(Article::getViewCounts);
        queryWrapper.select(Article::getId, Article::getTitle);
        queryWrapper.last("limit " + limit);
        //select id,title from article order by view_counts desc limit 5
        List<Article> articles = articleMapper.selectList(queryWrapper);
        return Result.success(articles);
    }
}

5.4 测试

6. 首页-最新文章

和最热文章类似

6.1 接口说明

接口url:/articles/new

请求方式:POST

请求参数:

参数名称

参数类型

说明

id

Long

文章id

title

String

文章标题

返回数据:

{
  "success": true,
  "code": 200,
  "msg": "success",
  "data": [
    {
      "id": 1,
      "title": "Mybatis-Plus如何使用分页",
      "summary": null,
      "commentCounts": null,
      "viewCounts": null,
      "authorId": null,
      "bodyId": null,
      "categoryId": null,
      "weight": null,
      "createDate": null
    },
    .....
  ]
}

6.2 Controller

//json数据进行交互
@RestController
@RequestMapping("/articles")
public class ArticleController {
    @Autowired
    private ArticleService articleService;
    
    /**
     * 最新文章
     *
     * @return
     */
    @PostMapping("new")
    public Result newArticles() {
        int limit = 5;
        return articleService.newArticles(limit);
    }

}

6.3 Service

/**
 * 最新文章
 * @param limit
 * @return
 */
Result newArticles(int limit);
@Override
public Result newArticles(int limit) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>();
//按发布时间排序
queryWrapper.orderByDesc(Article::getCreateDate);
queryWrapper.select(Article::getId, Article::getTitle);
queryWrapper.last("limit " + limit);
List<Article> articles = articleMapper.selectList(queryWrapper);
return Result.success(articles);
}

6.4 测试

7. 首页-文章归档

7.1接口说明

接口url:/articles/listArchives

请求方式:POST

请求参数:

参数名称

参数类型

说明

year

Integer

month

Integer

count

Long

相应年月的文章总数

返回数据:

{
  "success": true,
  "code": 200,
  "msg": "success",
  "data": [
    {
      "year": 2017,
      "month": 8,
      "count": 1
    },
    {
      "year": 2021,
      "month": 1,
      "count": 1
    },
    {
      "year": 2023,
      "month": 8,
      "count": 1
    }
  ]
}

7.1 Controller

/**
 * 文章归档
 * @return
 */
@PostMapping("listArchives")
public Result listArchives(){
    return articleService. listArchives();
}

在dao目录下建包dos

这个包下的类封装的是 不是从数据库直接查询出来的数据

例如从ms_article表create_date获取的year,month,count(*) 是调用了YEAR()函数生成的

SELECT YEAR(create_date) as year,MONTH(create_date) as month,count(*) as count from ms_article GROUP BY year,month

@Data
public class Archives {
    private Integer year;
    private Integer month;
    private Long count;
}

7.2 Service

/**
 * 文章归档
 * @return
 */
Result listArchives();
@Override
public Result listArchives() {
    List<Archives> archivesList = articleMapper.listArchives();
    return Result.success(archivesList);
}

7.3 Dao

public interface ArticleMapper extends BaseMapper<Article> {
    /**
     * 文章归档
     * @return
     */
    List<Archives> listArchives();
}

因为数据库存放文章的创建时间为时间戳 要做一个转换

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pjp.blog.dao.mapper.ArticleMapper">

  <!--    List<Archives> listArchives();-->
  <select id="listArchives" resultType="com.pjp.blog.dao.dos.Archives">
    SELECT YEAR(FROM_UNIXTIME(create_date)) as year,MONTH(FROM_UNIXTIME(create_date)) as month,count(*) as count
    from ms_article GROUP BY year,month
  </select>
</mapper>

from_unixtime(时间戳 ,日期格式

参数说明

timestamp :时间戳,可为一串数字,也可为字段。

date_format:时间格式,不填默认为%Y-%m-%d %H:%i:%s的格式。

7.4 测试

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐