首页 > 其他分享 >SpringBoot构建RESTful风格应用

SpringBoot构建RESTful风格应用

时间:2022-12-06 23:00:25浏览次数:41  
标签:返回 SpringBoot RESTful restful ResponseResult API 构建 com public

Spring Boot 构建 RESTful 风格应用

1.Web开发的两种模式:

前后端不分离:

以前没有移动互联网时,我们做的大部分应用都是前后端不分的,比如jsp,或者thymeleaf等后端分离模板,在这种架构的应用中,数据基本上都是在后端渲染好返回给前端展示的,也就是后端需要控制前端的展示,前端与后端的耦合度很高。
这种应用模式比较适合纯网页应用,但是当后端对接App时,App可能并不需要后端返回一个HTML网页,而仅仅是数据本身,所以后端原本返回网页的接口不再适用于前端App应用,为了对接App后端还需再开发一套接口。这样前后端不分离就有局限性了。

前后端分离:

在前后端分离的应用模式中,后端仅返回前端所需的数据,不再渲染HTML页面,不再控制前端的效果。至于前端用户看到什么效果,从后端请求的数据如何加载到前端中,都由前端自己决定,网页有网页的处理方式,App有App的处理方式,但无论哪种前端,所需的数据基本相同,后端仅需开发一套逻辑对外提供数据即可。
在前后端分离的应用模式中 ,前端与后端的耦合度相对较低。
我们通常将后端开发的每个视图都称为一个接口,或者API,前端通过访问接口来对数据进行增删改查。

前面我们讲解了前后端不分离不分离模式,现在来讲解一下前后端分离模式怎么实现.

2.什么是API?

API:全称是 Application Programming Interface,应用程序编程接口。API是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。简单的说API 是一套协议,规定了我们与外界的沟通方式:如何发送请求和接收响应。
比如我们平时在QQ上可以看到天气信息,而这些天气信息不是腾讯公司用卫星监测到的,而是去调用气象局的天气信息的。但腾讯公司不需要也不能访问气象局的数据库和源码,而是通过调用气象局提供的一个公共函数来实现,我们只需要知道这个函数需要传递什么参数,以及返回什么样的数据就行,函数的内部结构我们并不需要知道。这个函数就是API。

3.什么是RESTful

为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们需要找到一种大家都觉得很好的接口实现规范,
而且这种规范能够让后端写的接口,用途一目了然,减少双方之间的合作成本。所以出现了接口服务架构,目前市面上大部分公司开发人员使用的接口服务架构主要有:restful、rpc。

REST:那Rest是什么呢,它是一种架构风格,就像气象局建立API时要遵守的一种规则,可以是Rest也可以是其它规则。这种规则是为web应用服务的,也就是用URL定位资源,用HTTP动词(GET,POST,DELETE,PUT)描述操作,用HTTP Status Code返回结果状态的这种client和server的交互方式。实现看Url就知道要什么,看http 方法就知道干什么,看http status code就知道结果如何。

RESTFul:RESTful是一种定义Web API接口的设计风格,尤其适用于前后端分离的应用模式中。RESTFul就是为了实现REST这种交互方式而制定的一套约束条件和规则,符合这些约束条件和原则的应用程序或设计就是RESTful。也就是REST本身不实用,实用的是如何设计 RESTful API(REST风格的网络接口)。这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,所以在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。

4.RESTful设计规范

那么RESTFul有哪些设计规范呢?

1、协议

API与用户的通信协议,使用HTTPs协议或者HTTP协议,统一确定用一种。

2、域名

应该尽量将API部署在专用域名之下,如https://xxx.xxx.com;

如果多个项目创建API,把项目名称带上 如[https://项目名.XXX.com

3、版本

应该将API的版本号放入URL。

http://www.example.com/app/1.0/foo

http://www.example.com/app/1.1/foo

http://www.example.com/app/2.0/foo


另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观。Github就采用了这种做法。

因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URL。版本号可以在HTTP请求头信息的Accept字段中进行区分

Accept: vnd.example-com.foo+json; version=1.0

Accept: vnd.example-com.foo+json; version=1.1

4、路径

路径又称"终点"(endpoint),表示API的具体网址,每个网址代表一种资源(resource)

(1) 资源作为网址,只能有名词,不能有动词,而且所用的名词往往与数据库的表名对应。

举例来说,以下是不好的例子:
/selectGoods
/listOrders
/retreiveClientByOrder?orderId=1

对于一个简洁结构,你应该始终用名词。 此外,利用的HTTP方法可以分离网址中的资源名称的操作。

GET /goods :将返回所有商品清单
POST /goods :将商品新建到集合
GET /goods/4 :将获取商品 4
PATCH(或)PUT /goods/4 :将更新商品 4


(2) API中的名词应该使用复数。无论子资源或者所有资源。

举例来说,获取产品的API可以这样定义

获取单个产品:http://127.0.0.1:8080/AppName/goods/1
获取所有产品: http://127.0.0.1:8080/AppName/goods

5、HTTP动词

对于资源的具体操作类型,由HTTP动词表示。

常用的HTTP动词有下面四个(括号里是对应的SQL命令)。

- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- DELETE(DELETE):从服务器删除资源。

还有三个不常用的HTTP动词。

- PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

下面是一些例子。

GET /goods:列出所有商品清单
POST /goods:新建一个商品(上传文件)
GET /goods/ID:获取某个指定商品的信息
PUT /goods/ID:更新某个指定商品的信息(提供该商品的全部信息)
PATCH /goods/ID:更新某个指定商品的信息(提供该商品的部分信息)
DELETE /goods/ID:删除某个商品
GET /goods/ID/attributes:列出某个指定商品的所有属性信息
DELETE /goods/ID/attributes/ID:删除某个指定商品的指定属性

6、过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户,API会提供参数,过滤返回结果,用于补充规范一些通用字段,常见的参数有:
1. ?limit=20:指定返回记录的数量为20;
2. ?offset=8:指定返回记录的开始位置为8;
3. ?page=1&per_page=50:指定第1页,以及每页的记录数为50;
4. ?sortby=name&order=asc:指定返回结果按照name属性进行升序排序;
5. ?attr_id=2:指定筛选条件。

7、状态码

服务器会向用户返回状态码和提示信息,以下是常用的一些状态码,可以根据实际业务添加对应的状态码,需和http状态码对应:
1. 200 OK - [GET]:服务器成功返回用户请求的数据;
2. 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功;
3. 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务);
4. 204 NO CONTENT - [DELETE]:用户删除数据成功;
5. 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作;
6. 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误);
7. 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的;
8. 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作;
9. 406 Not Acceptable - [GET]:用户请求的格式不可得;
10. 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的;
11. 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误;
12. 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

8、错误处理

如果状态码是4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

比如Google 的出错信息:

{
"error": {
"errors": [
{
"domain": "global",
"reason": "insufficientFilePermissions",
"message": "The user does not have sufficient permissions for file {fileId}."
}
],
"code": 403,
"message": "The user does not have sufficient permissions for file {fileId}."
}
}

9、返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

GET /collection:返回资源对象的列表(数组)

GET /collection/ID:返回单个资源对象(json)

POST /collection:返回新生成的资源对象(json)

PUT /collection/ID:返回完整的资源对象(json)

DELETE /collection/ID:返回一个空文档(空字符串)

比如:
code:200,
msg:查询成功
data:[{},{},{}]

10、超媒体(Hypermedia API)

RESTful API最好做到Hypermedia(即返回结果中提供链接,连向其他API方法),使得用户不查文档,也知道下一步应该做什么。

比如,Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。

{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。

{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}
上面代码表示,服务器给出了提示信息,以及文档的网址。

11、其它

服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

注意:上面的规范是一些约定的,并不是强制,可以不遵守,也可以只遵守几条

5.RESTFUl风格的 GET、POST、PUT、DELETE前端请求和服务器端接收示例

5.1 GET请求

后端:

@ResponseBody
@GetMapping("/users/{id}")
public User getUserById(@PathVariable("id") Integer id){
return userService.getUserById(id);
}

前端:

localhost:8080/users/1

5.2 POST请求

后端:

@ResponseBody
@PostMapping("/users")
public int addUser(User user){

return userService.addUser(user);
}

前端:

<form action="http://localhost:8080/users" method="post">

<input type="text" name="uname" />

<input type="text" name="age" />

<input type="submit" value="提交"/>

</form>

5.3 PUT请求

后端:

@ResponseBody
@PutMapping("/users")
public Users updateUser(User user){

return userService.updateUser(user);
}

前端:

<form action="http://localhost:8080/users" method="post">

<input type="hidden" name="id" />

<input type="text" name="uname" />

<input type="text" name="age" />

<input type="hidden" name="_method" value="PUT"/> <!-- 在表单中添加 _method 提交put 请求-->

<input type="submit" value="提交"/>

</form>

5.4 DELETE请求

后端:

@DeleteMapping("/users/{id}")
@ResponseBody
public String deleteUser(@PathVariable("id") Integer id){
return userService.deleteUserById(id);
}

前端:

<form action="http://localhost:8083/user/1" method="post">

<input type="hidden" name="_method" value="DELETE"/>

<input type="submit" value="提交"/>

</form>

异步请求,对于PUT和DELETE请求,使用post方法提交,在发送的数据中加上_method=PUT/DELETE

6.RESTFUL风格中设计统一响应体

6.1为什么要用统一响应体

什么是统一响应体呢?在前后端分离架构下,后端主要是一个​​RESTful API​​的数据接口。接口中有时返回数据,有时又没有,还有的会出错,也就是结果不一致。只用http状态码表达不够。

那么可以通过修改响应返回的​​JSON​​数据,让其带上一些固有的字段,例如以下这样的:

{
"code": 600,
"msg": "success",
"data": {
"id": 1,
"uname": "daimenglaoshi"
}
}

6.2 怎么实现

1.创建一个响应结果类

package com.test.restful.util;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseResult<T> {

public int code; //返回状态码

private String msg; //返回描述信息

private T data; //返回内容体


}

2.创建一个生成响应结果类的类

package com.test.restful.util;


public class Response {

private final static String SUCCESS = "success";

private final static String FAIL = "fail";

//不同的响应情景
public static <T> ResponseResult<T> makeOKRsp() {
return new ResponseResult<T>(200,SUCCESS,null);
}

public static <T> ResponseResult<T> makeOKRsp(String message) {
return new ResponseResult<T>(200,message,null);
}

public static <T> ResponseResult<T> makeOKRsp(T data) {
return new ResponseResult<T>(200,SUCCESS,data);
}

public static <T> ResponseResult<T> makeErrRsp(String message) {
return new ResponseResult<T>(500,message,null);
}

public static <T> ResponseResult<T> makeErrRsp() {
return new ResponseResult<T>(500,FAIL,null);
}

public static <T> ResponseResult<T> makeRsp(int code, String msg, T data) {
return new ResponseResult<T>(code,msg,data);
}
}

3.控制层调用

@GetMapping("/users")
public ResponseResult<List<User>> findUsers()
{
List<User> userList= userService.findUsers();

return Response.makeOKRsp(userList);
}

4.Postman测试

SpringBoot构建RESTful风格应用_服务器

5.封装状态码

我们也可以将状态码封装到枚举类中

比如:

package com.test.restful.util;


public enum CodeStatus {

// 成功
SUCCESS(200),

// 错误的请求
FAIL(400),

// 访问被拒绝,比如未认证(签名错误)
UNAUTHORIZED(401),

// 接口不存在
NOT_FOUND(404),

// 服务器内部错误
INTERNAL_SERVER_ERROR(500);

//自定义 状态码
NOT_ALLOWRD_REG(1001);

public int code;

CodeStatus(int code) {
this.code = code;
}

}

那么我们在Response类中使用状态码时可以替换成CodeStatus来访问,视频中会有详细讲解。

7.全局异常处理

​ 在使用统一响应结果的时候,还会遇到一种情况,就是程序的报错是由于运行时出异常导致的,有些异常是我们可以提前预知在业务中抛出,有些则是无法提前预知的。不管能否预知,我们都需要对异常处理,如果我们可以定义一个统一的全局异常处理,在​​Controller​​捕获所有异常,并且做适当处理,也做成统一响应体返回就好了。

7.1设计思路

  1. 自定义一个异常类(如:​​NotAllowedRegException​​),捕获针对项目或业务的某个异常;
  2. 使用​​@ExceptionHandler​​注解处理自定义异常和通用异常,并指明异常的处理类型;
  3. 使用​​@ControllerAdvice​​接收所有的控制层方法抛出的异常

7.2 代码实现

创建NotAllowedRegException类

package com.test.restful.exception;

import lombok.Data;


@Data
public class NotAllowedRegException extends Exception {

private int code; //这里的状态码 在 StatusCode枚举类设置好

private String message="异常:该用户不允许注册";

public NotAllowedRegException(String msg) {
super(msg);
}
public NotAllowedRegException() {

}
}

创建统一异常处理类GlobalExceptionHandler:

package com.test.restful.controller;

import com.test.restful.exception.NotAllowedRegException;
import com.test.restful.util.Response;
import com.test.restful.util.ResponseResult;
import com.test.restful.util.StatusCode;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


/*
@RestCnotallow=@RestController+ @ControllerAdvice @ControllerAdvice字面上意思是“控制器通知”,作用是接收所有的控制层方法抛出的异常
如果你只想对一部分控制器添加通知,比如某个包下的控制器,可以写@RestControllerAdvice("包名")
*/
@RestControllerAdvice
public class GlobalExceptionHandler {

//@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。
@ExceptionHandler(Exception.class)
public ResponseResult handlerException(Exception e) {

// 自定义异常
if (e instanceof NotAllowedRegException) {
return Response.createFailResp(StatusCode.NOT_ALLOWRD_REG.code,((NotAllowedRegException) e).getMessage());
}else {
// 其他异常,当我们定义了多个异常时,这里可以增加判断和记录
return Response.createFailResp(StatusCode.SERVER_ERROR.code,e.getMessage());
}
}


}

创建控制层方法测试:

package com.test.restful.controller;

import com.test.restful.exception.NotAllowedRegException;
import com.test.restful.pojo.User;
import com.test.restful.util.Response;
import com.test.restful.util.ResponseResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/test")
public class User3Controller {

@PostMapping("/users")
public ResponseResult addUser(User user) throws NotAllowedRegException {
if(user.getUname().equals("daimenglaoshi"))
throw new NotAllowedRegException();

return Response.createOkResp();
}

}

测试结果:

SpringBoot构建RESTful风格应用_API_02

标签:返回,SpringBoot,RESTful,restful,ResponseResult,API,构建,com,public
From: https://blog.51cto.com/u_15707781/5916692

相关文章

  • 解决SpringBoot框架因post数据量过大没反应问题(踩坑)
    最后在尝试下,springboot的application中加入如下两句话:OK~~~~spring.http.multipart.max-file-size=1000Mbspring.http.multipart.max-request-size=1000Mb补充知识:解......
  • 【译】Desmond2022-4_3构建模型系统
    对水性生物系统进行模拟需要准备生物分子,如蛋白质和配体,加入反离子以中和系统,选择模拟箱的大小,使用明确的溶剂分子溶解溶质,以及将蛋白质与膜双层对齐(如果使用)。如果必须手......
  • 很简单的源码剖析-SpringBoot内嵌Tomcat原理
    SpringBoot默认支持Tomcat,Jetty,和Undertow作为底层容器。而SpringBoot默认使用Tomcat,一旦引入spring-boot-starter-web模块,就默认使用Tomcat容器。<dependency><gr......
  • 多数据源配置 springboot+druid+mybatisplus使用注解整合
     1.pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"......
  • SpringBoot文件分片上传,断点续传
    ​ 1 背景用户本地有一份txt或者csv文件,无论是从业务数据库导出、还是其他途径获取,当需要使用蚂蚁的大数据分析工具进行数据加工、挖掘和共创应用的时候,首先要将本地文......
  • SpringBoot中@Async异步的使用及异步与同步的区别
    简介在开发过程中,异步是提升系统并发能力的一个重要利器。而spring中的@Async异步注解,使我们能够非常方便地实现方法地异步调用。接下来主要结合以下几个问题来讲述j......
  • Springboot优雅进行字段检验
    Springboot优雅进行字段检验1、ControllerVSService推荐与业务无关的放在controller层中进行校验,而与业务相关的放在service层中校验。2、常用校验工具类使用Hiberna......
  • springboot2 搭建日志收集系统存入mongodb + redis+mq+线程池+xxljobs
    我们看到了高效批量插入mongodb速度还不错,那么我们的系统日志收集怎么做呢。当然当前文件日志收集效果也不错,比如前面博文写的elkf搭建日志收集系统。但我们系统中总是有......
  • springboot2 mongodb 高效批量入库--环境搭建
    当今使用微服务越来越多,每个服务都需要记录日志,那么记录到mysql中已完全不合适了。那么就记录到mongo中吧。想要速度快,那么一定要使用批量保存,做过尝试入库10万数据,逐条插......
  • SpringBoot整合Netty+WebSocket
    SpringBoot整合Netty+WebSocket构建环境pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w......