# 基础信息封装
- 接口统一返回
- 接口文档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
}
}
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");
}
}
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;
}
}
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("测字符串");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
]
# 全局异常处理
- 自定义异常
public class SystemException extends RuntimeException {
private static final long serialVersionUID = 1488902735359521074L;
public SystemException(String message) {
super(message);
}
}
2
3
4
5
6
7
- 全局异常捕获
@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());
}
}
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
- 测试
@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;
}
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
}
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>
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,如下图
]
]
然后看看控制台,,我尼玛,这啥问题呀
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]
2
3
4
5
6
7
8
接着百度了下,杂七乱八的,有说包不全的,需要导入jsckson的,有说需要加context-type的,然鹅。。我这里都不管用。。。
于是乎,就仔细检查了下代码,,此时已经一万只草泥马在奔腾,原来是Result返回类少写了Getter。。。mdzz!!哎。。。一言难尽就。不说了,赶紧加上就可以了
@Getter//就这个,一定要认真
public class Result<T> implements Serializable {
}
2
3
4
总结分析一下吧,加深印象。。其实是给自己找点面子 😂
这里必须提到 @ResponseBody
注解,是将controller的方法返回的对象 通过适当的转换器 转换为指定的格式之后,写入到response对象的body区(响应体中),通常用来返回JSON数据
该注解用于将Controller的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到Response对象的body数据区
转换的时候会用到Getter方法,就报错了