# 基础信息封装

  • 接口统一返回
  • 接口文档Swagger整合

准备 新建一个项目,springboot-restful-api-demo 引入web模块。

# 统一接口返回

随着前后端分离的发展,各端分工越来越精细化,前后端耦合性大大降低,后端接口需要文档,统一格式返回等等,今天一一分析分析……

前后端分离:前端负责数据的展示,后端负责数据的处理,前后端交互变得非常重要!

前后端分离架构:

模块 描述
前端 iOS Android 小程序 web m 等等
调用接口(Http)
后端 Nginx (负载均衡,流量分发,静态资源处理,反向代理等等)
后端 聚合服务
后端 微服务(分布式部署的各种服务)
处理数据
后端 DB,Redis,MongoDB,MQ等

一套系统适应多端(iOS App, Android App, 小程序,m站,pc站等),所以API最好返回统一的数据格式 如下模板,code-状态码 message-消息提示,错误消息等 data-真正的数据

{
  "code": 200,
  "message": "OK",
  "data": {
    "memo": "",
    "dataState": 1
  }
}
1
2
3
4
5
6
7
8

开始撸起来,响应实体Result

@Getter
public class Result<T> implements Serializable {
    private static final long serialVersionUID = 7491166533026088331L;

    private int code;
    private String message;
    private T data;

    public Result() {
    }

    private Result(int code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "ok", data);
    }

    public static <T> Result<T> success() {
        return success(null);
    }

    public static <T> Result<T> error(ResultCode resultCode) {
        return error(resultCode.getCode(), resultCode.getMessage());
    }

    public static <T> Result<T> error(int code, String message) {
        return new Result<>(code, message, null);
    }

    public static <T> Result<T> error(String message) {
        return error(-1, message);
    }

    public static <T> Result<T> error() {
        return error("error");
    }

}
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

统一错误状态码ResultCode

public enum ResultCode {
    //异常声明
    SUCCESS(0, "ok"),
    ERROR(-1, "error"),

    //参数异常
    ERROR_SYSTEM_EXCEPTION(1001, "系统异常"),

    ERROR_PARAM_IS_BLANK(1002, "参数为空"),
    ERROR_PARAM_TYPE_BIND_ERROR(1003, "参数类形错误"),

    //用户异常
    ERROR_USER_NOT_LOGGED_IN(104001, "用户未登录,需要验证,请登录"),
    ERROR_USER_LOGIN_ERROR(104002, "账号不存在或密码错误"),
    ERROR_USER_NOT_EXIST(104003, "用户不存在"),
    ERROR_USER_HAS_EXISTED(104004, "用户已存在");//末尾分号,勿删

    private final int code;
    private final String message;

    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return this.code;
    }

    public String getMessage() {
        return this.message;
    }
}

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

测试 DemoController

@RestController
@RequestMapping("/api")
public class DemoController {

    @RequestMapping("/demo")
    public Result<Map<String, Object>> demo() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "张三");
        map.put("id", 10001);
        map.put("hobby", new ArrayList<>(Arrays.asList("篮球", "网球")));
        map.put("city", "上海");
        return Result.success(map);
    }

    @RequestMapping("/test")
    public Result<String> demos() {
        return Result.success("测字符串");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

h94vSP.png]

# 全局异常处理

  1. 自定义异常
public class SystemException extends RuntimeException {
    private static final long serialVersionUID = 1488902735359521074L;

    public SystemException(String message) {
        super(message);
    }
}
1
2
3
4
5
6
7
  1. 全局异常捕获
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public Result<Object> exceptionHandler(Exception ex, HttpServletRequest request) {
        log.error("url:{},|errMsg:{}", request.getRequestURI(), ex.getMessage(), ex);
        return Result.error(500, ex.getMessage());
    }

    /**
     * 参数校验异常捕获
     */
    @ExceptionHandler(BindException.class)
    public Result<Object> bindExceptionHandler(BindException ex, HttpServletRequest request) {
        log.error("url:{},|errMsg:{}", request.getRequestURI(), ex.getBindingResult().getFieldError().getDefaultMessage(), ex);
        return Result.error(500, ex.getBindingResult().getFieldError().getDefaultMessage());
    }

    /**
     * 参数校验异常捕获
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex, HttpServletRequest request) {
        log.error("url:{},|errMsg:{}", request.getRequestURI(), ex.getBindingResult().getFieldError().getDefaultMessage(), ex);
        return Result.error(500, ex.getBindingResult().getFieldError().getDefaultMessage());
    }

    /**
     * 自定义异常
     */
    @ExceptionHandler(SystemException.class)
    public Result<Object> methodArgumentNotValidExceptionHandler(SystemException ex, HttpServletRequest request) {
        log.error("url:{},|errMsg:{}", request.getRequestURI(), ex.getMessage(), ex);
        return Result.error(500, ex.getMessage());
    }
}
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
  1. 测试
@RestController
@RequestMapping("/api")
public class DemoController {

    @RequestMapping("/e1")
    public Result<String> e1() {
        throw new SystemException("错误测试");
    }

    @RequestMapping("/e2")
    public Result<String> e2(@Valid User user) {
        return Result.success("Hello " + user.getName());
    }

    @RequestMapping("/e3")
    public Result<String> e3() {
        int i = 3 / 0;
        return Result.success("ok");
    }
}

@Data
class User {
    private int id;
    @NotBlank(message = "名字不为空")
    private String name;
}
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

返回结果

{
  "code": 500,
  "message": "错误测试",
  "data": null
}
{
  "code": 500,
  "message": "名字不为空",
  "data": null
}
{
  "code": 500,
  "message": "/ by zero",
  "data": null
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

备注:上面有个 @Valid 注解,这是参数校验用的,使用的是Hibernate Validator处理的,下面来说 参数校验

# 参数校验

Hibernate Validator是SpringBoot内置的校验框架,只要集成了SpringBoot就自动集成了它,就可以在对象上面使用它提供的注解来完成参数校验。

由于SpringBoot 2.3版本默认移除了校验功能,如果想要开启的话需要添加如下依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
1
2
3
4

常用注解

  • @NotNull:被注释的属性不能为null;
  • @NotBlank:被注释的字符串不能为空字符串;
  • @NotEmpty:被注释的属性不能为空;
  • @Pattern:被注释的属性必须符合其regexp所定义的正则表达式;
  • @Email:被注释的属性必须符合邮箱格式。
  • @Min:被注释的属性必须大于等于其value值;
  • @Max:被注释的属性必须小于等于其value值;
  • @Size:被注释的属性必须在其min和max值之间;

# RestFul-API

# 封装 Swagger

# 遇到的问题

错误描述

写完Result之后就基于测试,没注意检查,当我访问测试接口 http://localhost:8080/api/demo 的时候报错,状态码406,如下图

h95MTJ.png]

h95Kw4.png]

然后看看控制台,,我尼玛,这啥问题呀

2021-08-23 11:53:37.097  INFO 14532 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-08-23 11:53:37.528  INFO 14532 --- [           main] com.biubiu.api.RestFulApplication        : Started RestFulApplication in 3.801 seconds (JVM running for 5.205)
2021-08-23 11:53:46.718  INFO 14532 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-08-23 11:53:46.718  INFO 14532 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-08-23 11:53:46.719  INFO 14532 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2021-08-23 11:53:46.776  WARN 14532 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
2021-08-23 11:53:50.096  WARN 14532 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
2021-08-23 11:53:53.173  WARN 14532 --- [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation]
1
2
3
4
5
6
7
8

接着百度了下,杂七乱八的,有说包不全的,需要导入jsckson的,有说需要加context-type的,然鹅。。我这里都不管用。。。

于是乎,就仔细检查了下代码,,此时已经一万只草泥马在奔腾,原来是Result返回类少写了Getter。。。mdzz!!哎。。。一言难尽就。不说了,赶紧加上就可以了

@Getter//就这个,一定要认真
public class Result<T> implements Serializable {

}
1
2
3
4

总结分析一下吧,加深印象。。其实是给自己找点面子 😂

这里必须提到 @ResponseBody 注解,是将controller的方法返回的对象 通过适当的转换器 转换为指定的格式之后,写入到response对象的body区(响应体中),通常用来返回JSON数据

该注解用于将Controller的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到Response对象的body数据区

转换的时候会用到Getter方法,就报错了

# 源代码

本文对应代码 (opens new window)