随着前后端分离的发展,各端分工越来越精细化,前后端耦合性大大降低,后端接口需要文档,统一格式返回等等,今天一一分析分析……
准备
新建一个项目,springboot-restful-api-demo 引入web模块。
统一接口返回
前后端分离:前端负责数据的展示,后端负责数据的处理,前后端交互变得非常重要!
前后端分离架构:
1 | 前端:iOS Android 小程序 web m 等等 |
一套系统适应多端(iOS App, Android App, 小程序,m站,pc站等),所以API最好返回统一的数据格式 如下模板,code-状态码 message-消息提示,错误消息等 data-真正的数据
1 | { |
开始撸起来,响应实体Result
1 | package com.biubiu.api.vo; |
统一错误状态码ResultCode
1 | package com.biubiu.api.vo; |
测试 DemoController
1 | package com.biubiu.api.controller; |
]
全局异常处理
自定义异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.biubiu.api.handler;
/**
* <p>
* SystemException
* </p>
*
* @author wbbaijq
* @since 2021/8/23
*/
public class SystemException extends RuntimeException {
private static final long serialVersionUID = 1488902735359521074L;
public SystemException(String message) {
super(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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57package com.biubiu.api.handler;
import com.biubiu.api.vo.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* <p>
* GlobalExceptionHandler
* </p>
*
* @author wbbaijq
* @since 2021/8/23
*/
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
public Result<Object> exceptionHandler(Exception ex, HttpServletRequest request) {
log.error("url:{},|errMsg:{}", request.getRequestURI(), ex.getMessage(), ex);
return Result.error(500, ex.getMessage());
}
/**
* 参数校验异常捕获
*/
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());
}
/**
* 参数校验异常捕获
*/
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());
}
/**
* 自定义异常
*/
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/**
* <p>
* DemoController
* </p>
*
* @author wbbaijq
* @since 2021/8/23
*/
public class DemoController {
public Result<String> e1() {
throw new SystemException("错误测试");
}
public Result<String> e2( User user){
return Result.success("Hello " + user.getName());
}
public Result<String> e3() {
int i = 3 / 0;
return Result.success("ok");
}
}
class User {
private int id;
private String name;
}返回结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15{
"code": 500,
"message": "错误测试",
"data": null
}
{
"code": 500,
"message": "名字不为空",
"data": null
}
{
"code": 500,
"message": "/ by zero",
"data": null
}
备注:上面有个
@Valid
注解,这是参数校验用的,使用的是Hibernate Validator处理的,下面来说 参数校验
参数校验
Hibernate Validator是SpringBoot内置的校验框架,只要集成了SpringBoot就自动集成了它,就可以在对象上面使用它提供的注解来完成参数校验。
由于SpringBoot 2.3版本默认移除了校验功能,如果想要开启的话需要添加如下依赖。
1 | <dependency> |
常用注解
- @NotNull:被注释的属性不能为null;
- @NotBlank:被注释的字符串不能为空字符串;
- @NotEmpty:被注释的属性不能为空;
- @Pattern:被注释的属性必须符合其regexp所定义的正则表达式;
- @Email:被注释的属性必须符合邮箱格式。
- @Min:被注释的属性必须大于等于其value值;
- @Max:被注释的属性必须小于等于其value值;
- @Size:被注释的属性必须在其min和max值之间;
RestFul-API
随着互联网和移动设备得发展,人们对Web应用的使用需求也增加,传统的动态页面由于低效率而渐渐被HTML+JavaScript(Ajax)的前后端分离所取代!
所以一套结构清晰、符合标准、易于理解、扩展方便让大部分人都能够理解接受的接口风格就显得越来越重要,而RESTful风格的接口(RESTful API)刚好有以上特点,就逐渐被实践应用而变得流行起来
用URL定位资源,用Http请求描述操作
HTTP请求
- GET 从服务器取出资源(一项或多项)
- POST 在服务器新建一个资源
- PUT 在服务器更新资源(客户端提供完整资源数据)
- PATCH 在服务器更新资源(客户端提供需要修改的资源数据)
- DELETE 从服务器删除资源
URL: 统一资源定位符
HTTP/1.1:
- 引入了持久连接,即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive
- 加入了管道机制,在同一个TCP连接里,允许多个请求同时发送,增加了并发性,进一步改善了HTTP协议的效率
- 新增了请求方式PUT、PATCH、OPTIONS、DELETE等
状态码
- 200 OK - 客户端请求成功 [GET]
- 201 CREATED 用户新建或修改数据成功 [POST/PUT/PATCH]
- 202 Accepted 表示一个请求已经进入后台排队(异步任务)
- 204 Accepted 用户删除数据成功 [DELETE]
- 301 - 资源(网页等)被永久转移到其它URL
- 302 - 临时跳转
- 400 Bad Request - 客户端请求有语法错误,不能被服务器所理解 [POST/PUT/PATCH]
- 401 Unauthorized - 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 表示用户没有权限(令牌、用户名、密码错误)
- 403 表示用户得到授权(与401错误相对),但是访问是被禁止
- 404 NOT FOUND - 请求资源不存在,可能是输入了错误的URL
- 500 - 服务器内部发生了不可预期的错误
- 503 Server Unavailable - 服务器当前不能处理客户端的请求,一段时间后可能恢复正常。
RestFul和非RestFul比较
API name | 非 RestFul | RestFul |
---|---|---|
获取dog | /dogs/query/{dogid} | GET /dogs/{dogid} |
插入dog | /dogs/add | POST /dogs |
更新dog | /dogs/update/{dogid} | PUT /dogs/{dogid} |
删除dog | /dogs/delete/{dogid} | DELETE /dogs/{dogid} |
示例Controller
1 |
|
]
Swagger
引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!-- swagger 2.9.2
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
-->
<!--swagger3-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>配置
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
54package com.biubiu.common.config;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
/**
* Swagger API文档配置类
*
* @author biubiu
* @date 2020/11/27 23:59
*/
public class Swagger3Config {
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
.paths(PathSelectors.any())
.build();
//.globalOperationParameters(headerParameter());//全局配置
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Swagger3 API 文档标题")
.description("API 文档描述")
.contact(new Contact("biubiu", "https://www.yuque.com/biubiu-note", "baijqmail@163.com"))
.version("1.0.0")
.build();
}
private List<Parameter> headerParameter() {
ParameterBuilder cityen = new ParameterBuilder();
cityen.name("cityen").description("城市简写:sz,tj").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
ParameterBuilder platform = new ParameterBuilder();
platform.name("platform").description("平台:ios,android,wap,wechat").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
pars.add(cityen.build());
pars.add(version.build());
return pars;
}
}访问Swagger3
http://ip:port/swagger-ui/常用注解
注解 | 描述 |
---|---|
@Api | 标记一个Controller类做为swagger文档资源 |
@ApiOperation | 用在Controller里的方法上 |
@ApiModel | 标记实体对象 |
@ApiModelProperty | 标记实体对象属性 |
@ApiParam | 用于Controller中方法的参数说明,单个参数 |
@ApiIgnore | 用来屏蔽某些接口或参数,使其不在页面上显示 |
遇到的问题
错误描述
写完Result之后就基于测试,没注意检查,当我访问测试接口 http://localhost:8080/api/demo 的时候报错,状态码406,如下图
]
]
然后看看控制台,,我尼玛,这啥问题呀
1 | 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 '' |
接着百度了下,杂七乱八的,有说包不全的,需要导入jsckson的,有说需要加context-type的,然鹅。。我这里都不管用。。。
于是乎,就仔细检查了下代码,,此时已经一万只草泥马在奔腾,原来是Result返回类少写了Getter。。。mdzz!!哎。。。一言难尽就。不说了,赶紧加上就可以了
1 | //就这个,一定要认值 |
总结分析一下吧,加深印象。。其实是给自己找点面子 😂
这里必须提到 @ResponseBody
注解,是将controller的方法返回的对象 通过适当的转换器 转换为指定的格式之后,写入到response对象的body区(响应体中),通常用来返回JSON数据
该注解用于将Controller的方法返回的对象,通过适当的 HttpMessageConverter 转换为指定格式后,写入到Response对象的body数据区
转换的时候会用到Getter方法,就报错了