WHCSRL 技术网

前后端分离项目-后端接口入门开发手记

说明:本文档为前后端分离项目-后端开发(入门级)文档,仅供参考使用

项目名称:myBlog

环境:工具/IDEA | JDK/1.8

一、创建项目

1. 新建maven项目

依次点击File-New-Project-Maven新建Maven项目

在这里插入图片描述

在这里插入图片描述

点击Finnish后,初始Maven项目就创建好了

在这里插入图片描述

2. 搭建SpringBoot

在pom.xml中添加SpringBoot依赖,最终如下

<?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>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
    </parent>

    <groupId>com.myBlog</groupId>
    <artifactId>myBlog</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

</project>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

3. 创建接口

在java下新建包com.myblog,然后创建MyBlogApplication类,添加如下代码

package com.myblog;

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

/**
 * @author Jaon
 * @date 2021/10/29 14:45
 */
@SpringBootApplication
public class MyBlogApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyBlogApplication.class);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

然后在com.myblog下创建controller包,在controller包下创建HelloController类,并添加如下代码

package com.myblog.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Jaon
 * @date 2021/10/29 14:48
 */
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello World!";
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

最终如下

在这里插入图片描述

然后启动MyBlogApplication,浏览器访问localhost:8080/hello,效果图如下

在这里插入图片描述

二、开发接口

1. 创建数据库

使用navicat创建数据库my_blog

在这里插入图片描述

然后执行sql语句创建数据表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_blog
-- ----------------------------
DROP TABLE IF EXISTS `t_blog`;
CREATE TABLE `t_blog`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用户id',
  `blog_type_id` bigint(0) NULL DEFAULT NULL COMMENT '博客类型id',
  `title` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',
  `content` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '内容',
  `desc` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  `visit` bigint(0) NULL DEFAULT 0 COMMENT '浏览人数',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '博客信息' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_blog_type
-- ----------------------------
DROP TABLE IF EXISTS `t_blog_type`;
CREATE TABLE `t_blog_type`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `user_id` int(0) NULL DEFAULT NULL COMMENT '所属用户',
  `name` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '博客类型名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '博客类型信息' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT,
  `username` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `nick` varchar(12) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `email` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

2. 创建接口

在项目中引入mysql、mybatis和lombok依赖

<!-- 数据库 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

然后在项目resources下创建application.yml文件,内容如下(注意修改自己的数据库密码)

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/my_blog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
mybatis:
  mapper-locations: classpath:/mapper/*Mapper.xml
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后在com.myblog包下创建entity包,然后创建User类如下

package com.myblog.entity;

import lombok.Data;

import java.util.Date;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:13
 */
@Data
public class User {

    private Long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 昵称
     */
    private String nick;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

在com.myblog包下创建mapper包,然后创建UserMapper接口,如下

package com.myblog.mapper;

import com.myblog.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:09
 */
@Mapper
public interface UserMapper {

    List<User> list();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在resources下创建mapper包,然后创建UserMapper.xml如下(这里写的sql语句是将数据库user表中所有条目查询出来,如果查询全部字段值,不推荐使用*号,推荐手写全部字段值查询)

<?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.myblog.mapper.UserMapper">
    <select id="list" resultType="com.myblog.entity.User">
        SELECT id, username, nick, email, create_time createTime, update_time updateTime
        FROM t_user
    </select>
</mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在com.myblog包下创建service包,然后创建UserService接口如下

package com.myblog.service;

import com.myblog.entity.User;

import java.util.List;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:11
 */
public interface UserService {

    List<User> list();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在service下创建impl包,创建UserServiceImpl类并实现UserService接口如下

package com.myblog.service.impl;

import com.myblog.entity.User;
import com.myblog.mapper.UserMapper;
import com.myblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:11
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public List<User> list() {
        return userMapper.list();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

在controller下创建UserController如下

package com.myblog.controller;

import com.myblog.entity.User;
import com.myblog.service.UserService;
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;

/**
 * @author Jaon
 * @datetime 2021/10/29 16:22
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("/list")
    public List<User> list() {
        return userService.list();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

最后在数据库造一条数据,重启项目,访问localhost:8080/user/list

如数据库里

在这里插入图片描述

访问结果(我这里使用了CSDN的插件,所以自动格式化了返回数据)

在这里插入图片描述

a. 时间格式化

如图,后端返回的时间格式与我们实际想要的格式不符合,所以我们需要转换以下格式,可以直接在实体类属性上添加@JsonFormat注解

/**
 * 创建时间
 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;

/**
 * 更新时间
 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

b. 隐藏数据为空的属性

如图,我们所看到的返回数据中有值为null的参数,在某些情况下,我们可能会隐藏值为null的参数,我们可以使用@JsonInclude注解来实现

@Data
@JsonInclude(value = JsonInclude.Include.NON_NULL)
public class User {
    /* 省略代码 */
}
  • 1
  • 2
  • 3
  • 4
  • 5

3. 实现跨域

如果是前后端分离的项目,此时如果我们创建一个前端项目来访问当前接口是访问不通的,因为地址或端口不同造成了跨域请求,此时我们需要后端开放跨域请求,如图

在这里插入图片描述

在com.myblog包下创建config包,并创建WebMvcConfig类,如下

package com.myblog.config;

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

/**
 * @author Jaon
 * @datetime 2021/11/1 9:54
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 允许所有的路径可以跨域
        registry.addMapping("/**")
                // 允许所有来源都可以跨域
                .allowedOriginPatterns("*")
                // 允许跨域的请求
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                // 允许证书凭证(如果这里设置为true,设置来源为所有只能使用allowedOriginPatterns)
                .allowCredentials(true)
                // 跨域时间3600秒
                .maxAge(3600)
                // 允许所以头标题
                .allowedHeaders("*");
    }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

然后我们重启项目,重新访问可以发现跨域报错消失了,并且能够返回数据,如图

在这里插入图片描述

4. 全局异常处理

作为后端,就算是报错也需要返回异常信息来提示前端,但是在我们的代码中,controller层与service层之间传递数据不可能局限于一个非常统一的数据类型或者service根本就没有返回值,这就造成了有某些异常信息不能直接从service层返回到前端,如果使用try-catch来捕获就太过于麻烦了,所以我们定义全局异常处理来统一捕获异常并返回给前端

如图,我们写了一个非常简单并且肯定能够报错的代码

在这里插入图片描述

然后我们去访问该地址,会发现报了一个500的错,并在控制台打印了错误信息

在这里插入图片描述

在这里插入图片描述

然后我们在com.myblog包下创建handler包,并创建GlobalExceptionHandler类,如下

package com.myblog.handler;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author Jaon
 * @datetime 2021/11/1 11:27
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        return e.getMessage();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

然后重启项目再次访问地址,发现返回信息变了,这说明我们捕获到了异常,并返回给了前端,但是控制台是没有报错信息的

在这里插入图片描述

a. 报错日志

一个后端项目,报错日志是非常重要的,对于后期维护和问题解决起到了决定性作用,所以全局异常处理没有打印报错信息是一个不太好的现象。我们可以使用@Slf4j来实现错误信息日志,如下

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        log.error("报错信息:{}", e.getMessage());
        return e.getMessage();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

b. 自定义异常

在我们的业务逻辑中,可能需要自定义异常来抛出,由全局异常处理类来处理,这个时候考虑到返回的状态码和异常信息的不同,我们需要自定义异常类来配合全局异常处理

在com.myblog包下创建common.lang包,并创建MyBlogException类,如下

package com.myblog.common.exception;

import lombok.Getter;

/**
 * @author Jaon
 * @datetime 2021/11/1 12:32
 */
@Getter
public class MyBlogException extends RuntimeException {

    private Integer code;

    private String msg;

    public MyBlogException(Integer code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public MyBlogException(Integer code, String msg, Throwable throwable) {
        super(msg, throwable);
        this.code = code;
        this.msg = msg;
    }

    public MyBlogException(String msg) {
        super(msg);
        this.code = 403;
        this.msg = msg;
    }

    public MyBlogException(String msg, Throwable throwable) {
        super(msg, throwable);
        this.code = 403;
        this.msg = msg;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

然后我们就可以使用自定义的异常类来抛出异常了,如

修改list接口

@GetMapping("/list")
public List<User> list() {
    List<User> list = userService.list();
    if (list.size() <= 0) {
        // 如果数据库中没有用户信息则抛出异常
        throw new MyBlogException("当前没有用户信息");
    }
    return list;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在全局异常处理类中添加方法

@ExceptionHandler(MyBlogException.class)
public Map<String, Object> handleException(MyBlogException e) {
    log.error("报错信息:{}", e.getMessage());
    Map<String, Object> map = new HashMap<>();
    map.put("code", e.getCode());
    map.put("msg", e.getMsg());
    return map;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

然后删除user表中全部信息,然后再次请求,可以看到前端收到了我们想要的结果

在这里插入图片描述

5. 返回数据统一封装

对于前后端分离的项目,后端返回数据往往需要统一规范,来配合前端数据统一处理

在common包下创建lang包,并创建Result类,如下

package com.myblog.common.lang;

import lombok.Data;

/**
 * @author Jaon
 * @datetime 2021/11/1 13:14
 */
@Data
public class Result {

    private Integer code;

    private String msg;

    private Object data;

    public static Result success() {
        return success(null);
    }

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

    public static Result success(Integer code, Object data) {
        return common(code, "操作成功", data);
    }

    public static Result fail() {
        return fail(null);
    }

    public static Result fail(Integer code, String msg) {
        return common(code, msg, null);
    }

    public static Result fail(String msg) {
        return common(400, msg, null);
    }

    public static Result fail(Integer code, Object data) {
        return common(code, "操作失败", data);
    }

    public static Result common(Integer code, String msg, Object data) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

然后将所有的返回替换掉

// 修改接口返回值
@GetMapping("/list")
public Result list() {
    List<User> list = userService.list();
    if (list.size() <= 0) {
        throw new MyBlogException("当前没有用户信息");
    }
    return Result.success(list);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// 修改异常处理类返回值
@ExceptionHandler(MyBlogException.class)
public Result handleException(MyBlogException e) {
    log.error("报错信息:{}", e.getMessage());
    return Result.fail(e.getCode(), e.getMsg());
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
    log.error("报错信息:{}", e.getMessage());
    return Result.fail(e.getMessage());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

然后我们访问一下接口,可以看到返回格式变了

在这里插入图片描述

至此,我们的入门接口开发就算是完成了

推荐阅读