我们做WEB开发的时候无可避免的就是接口请求的规范,因为现在是MVVC开发的模式不会去像之前的MVC开发模式去直接返回Model层就全部交给前端去渲染,MVC架构的兴起是由J2EE中的SSH框架(Struts,Sring,Hibernate)框架去兴起的,PHP,C++,QT开发的框架也会去做MVC开发模式。
不会像传统的MVC架构去直接在存储在Map对象中返回数据对象。MVC层有三种常用的传输Model渲染方式页面都是被渲染出来的不像现在的‘尤大大’改善后的MVVC模式,现在的前端算是一个独立工种,传统的SSH框架开发中用到的渲染框架去渲染Html(Freemarker,JSP,Thymeleaf)去渲染的HTML直到现在依旧使用到现在
Spingboot框架处理接口返回值Springboot框架算是现在Java中使用比较广泛的框架因为SSM框架去做整合太繁琐了,新手直接劝退,Spingboot的出现简便多了,Springboot去做的事情就是开箱及使用内嵌Tomact启动服务,约定大于配置,本文介绍的Springboot开发常见的三种返回方式:
第一种Sting类型返回的数据
@RestController
@GetMapping("/hello")
public String getStr(){
return "hello,world";
}
此时请求/hello返回结果:
hello,world
第二种返回自定义对象结果
@RestController
@GetMapping(/Theworld)
public TheWorld world(){
TheWorld theworld=new TheWorld();
theworld.add(0,'Us');
theworld.add(1,'China');
return theworld;
}
此时请求/Theworld请求返回结果:
{
"id":0,
"name":"China"
}
第三种就是请求异常:
@RestController
@GetMapping("/error")
public int error(){
int i = 9/0;
return i;
}
此时请求/error返回值就会去返回Sprinboot默认的异常处理结果:
{
"timestamp": "2021-07-08T08:05:15.423+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/all"
}
以上基本上就为基本返回结果之前MVC未分离的时候大多都是这样写但是现在的MVVC分离前端更专注于用户层,后端只关心性能分工合作,你要给他返回这样的结果前端会很蒙蔽,axios请求完之后都不知道怎么去处理结果返回给用户了,还有就是,比如张三喜欢对结果进行封装,他使用了Result对象,李四也喜欢对结果进行包装,但是他却使用的是Response对象,当出现这种情况时我相信前端人员一定会抓狂的。
所以我们迫切的需要一套统一规范的接口返回值去做到让前端请求后看到了美如画的接口返回结果
开启正文:定义统一的返回结果
1. status 状态值:由后端统一定义各种返回结果的状态码
2. message 描述:本次接口调用的结果描述
3.data 数据:本次返回的数据。
这是后端接口不成文的规则!code,data,msg,很多年前定下的规范沿用至今
定义完之后请求结果就为:
{
“status”:“0”,
“message”:“操作成功”,
“data”:“hello”
}
当然我们也可以去添加请求结果:比如请求结果附带请求时间‘requesttime’,请求IP地址’Iplocation’
/**
* @author 仗剑走天涯!
* @date 2022/4/12
* 指尖改变世界
* 描述: 定义返回结果Object,此处省略Get,Set
*/
public class ResultData <T> {
//获取当前Time时间
private LocalDateTime localDateTime2 = LocalDateTime.now();
//返回状态码
private Integer status;
//返回消息
private String message;
//返回数据
private T data;
//Success结果
public static <T> ResultData<T> success(T data){
ResultData<T> resultData=new ResultData<>();
resultData.setStatus(RequestCode.SUCCESS.getCode());
resultData.setMessage(RequestCode.SUCCESS.getMessage());
resultData.setData(data);
return resultData;
}
//Fail结果
public static <T> ResultData<T> fail(int code, String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(code);
resultData.setMessage(message);
return resultData;
}
}
/**
* @author 仗剑走天涯!
* @date 2022/4/12
* 指尖改变世界
* 描述: 定义返回结果状态码枚举类,此处省略Get,Set
*/
public enum RequestCode {
//常见返回结果,剩下的一一去完善
SUCCESS(0, "操作成功"),
ERROR(1, "操作失败"),
SERVER_ERROR(500, "服务器异常")
;
//自定义状态码
private final int code;
//自定义描述
private final String message;
调用统一返回结果
@RestController
@RequestMapping("/guest")
public class GuestController {
@GetMapping("/all")
public ResultData<String> all(){
ResultConfig resultConfig=new ResultConfig();
System.err.println("恭喜您访问游客接口");
return ResultData.success("aaaaa");
}
}
此时请求/guest返回结果为:
{
"status": 0,
"message": "操作成功",
"data": "aaaaa",
"requesttime": "2022-04-12T16:22:11.479",
"iplocation": "127.0.0.1"
}
这样我们调用
ResultData.success()的时候直接返回Data数据就可以了,这样也能满足大部分需求但是我们要做就封装到极致,因为每次都去调用ResultData.success()也会去觉得繁琐。所以我们去进行封装优化
优化请求:
我们只需要借助SpringBoot提供的 @ResponseBodyAdvice即可。@ControllerAdvice@ControllerAdvice是@Controller的puls版本主要的作用有三大特性:(全局异常处理、全局数据绑定、全局数据预处理)简单的讲就是为了去处理Controller中报错的信息去配合
@ResponseBody跟@Controller注解配合使用就是为了让请求之后返回Json数据类型
@RestControllerAdvice就是两者的结合版本去做到让前端用axios请求之后知道信息不然的话报错了前端哪里返回也是success结果前端也懵逼。
/**
* @author 仗剑走天涯!
* @date 2022/4/12
* 指尖改变世界
* 描述: 使用@RestControllerAdvice注解编写检测到Sting类型直接Json数据方便前端辨析
的作用:拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。
@RestControllerAdvice:可以理解为@Controller的增强版本
比起@Controller注解增强方面:
1:全局异常处理
2:全局数据绑定
3:全局数据预处理
*/
@RestControllerAdvice
public class ResultPlus implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (o instanceof String) {
return objectMapper.writeValueAsString(ResultData.success(o));
}
return ResultData.success(o);
}
}
此时我们再去访问之前的接口不需要调用Result.success直接使返回就行可以减少每次繁琐的调用,我们可以更专注于面试造火箭的基础了
@RestController
@RequestMapping("/guest")
public class GuestController {
@GetMapping("/all")
public String all(){
ResultConfig resultConfig=new ResultConfig();
System.err.println("恭喜您访问游客接口");
return "aaa";
}
}
请求结果为:
{
"status": 0,
"message": "操作成功",
"data": "aaa",
"requesttime": "2022-04-12T17:12:54.517",
"iplocation": "127.0.0.1"
}
接下来就要面对无情的异常了!
ForExample:我此次请求报错:因为@RestController注解没加上找不到此接口请求但是请求之后还是返回状态success,这前端看到了要打人的,明明请求失败了但是返回结果还是成功的我们就要解决异常返回的结果了不能请求失败但是返回的结果还是这样不然前端去请求完之后蒙蔽了,可能以为自己的问题
1:@Controller上文提到了因为这个注解报错了,这里Get一个知识:@RestController是@RestControlle是用于绑定@RequestMapping配合去绑定路径用的
2:@ResponseBody@ResponseBody是为是用来把返回对象自动序列化成HttpResponse的。简短总结就是@ResponseBody是为了告诉HttpResponse让其返回为Josn数据的
3:@RestController就是为两者的结合体plus版:
为何要用到全局处理异常:
@GetMapping("/error")
public Integer error() {
int age=1;
try {
//万恶的空指针异常
age= Integer.parseInt(null);
return age;
}catch (Exception e) {
age=1;
}
return age;
}
- 避免大量的try…catch模块去自己手动抛出异常
{
"timestamp": "2022-04-12T22:47:07.71",
"status": 500,
"error": "Internal Server Error",
"path": "/error"
}
**Springboot默认抛出的异常前端大多数情况下看不懂!这里就需要我们去配置一个统一的异常处理配置类**这是Spingboot默认的错误提示方式:不管异常是什么都会去提示500状态码错误提示没法自定义,对于前端来说不太友好,没法去更广泛的编写"JS"
怎么去解决那,我们希望自定义返回数据就需要去在异常发生之前提前拦截:“预判了异常的发生并作出相应的处理”
自定义异常返回类型
/**
* @author 仗剑走天涯!
* @date 2022/4/11
* 指尖改变世界此处并不省略Get,Set觉得臃肿的话可以去用hutool
* 描述: 返回结果Object
*/
public class ResultData <T> {
//获取当前Time时间
private LocalDateTime localDateTime2 = LocalDateTime.now();
//返回状态码
private Integer status;
//返回消息
private String message;
//返回数据
private T data;
public String getIplocation() {
return Iplocation;
}
public void setIplocation(String iplocation) {
Iplocation = iplocation;
}
private String Iplocation;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public LocalDateTime getRequesttime() {
return requesttime;
}
public void setRequesttime(LocalDateTime requesttime) {
this.requesttime = requesttime;
}
private LocalDateTime requesttime;
//返回json请求time
public ResultData(){
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
this.requesttime=localDateTime2;
this.Iplocation=ip;
}
//Success结果
public static <T> ResultData<T> success(T data){
ResultData<T> resultData=new ResultData<>();
resultData.setStatus(RequestCode.SUCCESS.getCode());
resultData.setMessage(RequestCode.SUCCESS.getMessage());
resultData.setData(data);
return resultData;
}
//Fail结果
public static <T> ResultData<T> fail(int code, String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setStatus(code);
resultData.setMessage(message);
return resultData;
}
自定义状态码描述Object
/**
* @author 仗剑走天涯!
* @date 2022/4/12
* 指尖改变世界
* 描述: 定义状态码,此处并不省略Get,Set觉得臃肿的话可以去用hutool
*/
public enum RequestCode {
SUCCESS(0, "操作成功"),
ERROR(1, "操作失败"),
SERVER_ERROR(500, "服务器异常"),
INVALID_TOKEN(2001,"访问令牌不合法"),
ACCESS_DENIED(2003,"没有权限访问该资源"),
CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"),
USERNAME_OR_PASSWORD_ERROR(1002,"用户名或密码错误"),
UNSUPPORTED_GRANT_TYPE(1003, "不支持的认证模式")
;
//自定义状态码
private final int code;
//自定义描述
private final String message;
RequestCode(int code, String message){
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
预判异常的出现并进行处理
/**
* @author 仗剑走天涯
* @date 2022/4/11
* 指尖改变世界
* 描述: 全局异常处理
*/
@RestControllerAdvice
public class ExceptionControlle {
private static final Logger log= LoggerFactory.getLogger(ExceptionControlle.class);
/**
* 默认全局异常处理。
*
* @param e the e
* @return ResultData
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultData<String> exception(Exception e) {
log.error("全局异常信息 ex={}", e.getMessage(), e);
return ResultData.fail(RequestCode.SERVER_ERROR.getCode(), e.getMessage());
}
}
@RestController
@RequestMapping("/guest")
public class GuestController extends ExceptionControlle{
@GetMapping("/all")
public String all(){
ResultConfig resultConfig=new ResultConfig();
System.err.println("恭喜您访问游客接口");
return "aaa";
}
@GetMapping("/error")
public void error() {
throw new RuntimeException("aaaa");
}
}
此处用到了
当然这些只是我本文中用到的功能,具体有什么功能可以去“Spring”官网查看
本文中可能遇到的问题就是预判好了就等他放技能了,可是这次没预判到,注解没生效:
有两种解决方法1:“extends”继承那个统一的全局处理器:“extends” 大致讲就是哈士奇继承了狼全部好处,他自己也可以有自己的个性
学好Java必须要去了解他的语言特性:三大特征:封装,继承,多态Java基础可以去看一位大佬的博客
Java天花板
但是当我们同时启用统一标准格式封装功能 ResponseAdvice和 RestExceptionHandler全局异常处理器时又出现了新的问题就是我们去预判了一层异常,接着Springboot也预判到了异常两个一山不容二虎啊除非一公一母,这看着有点头疼,前端看着也很纳闷自定义返回结果说是成功了,但是Springobot提示报错:我们现在去解决问题:
{
"status": 0,
"message": "操作成功",
"data": {
"status": 500,
"message": "aaaa",
"data": null,
"requesttime": "2022-04-12T23:37:33.033",
"iplocation": "127.0.0.1"
},
"requesttime": "2022-04-12T23:37:33.094",
"iplocation": "127.0.0.1"
}
/**
* @author 仗剑走天涯!
* @date 2022/4/12
* 指尖改变世界
* 描述: 使用@RestControllerAdvice注解编写检测到Sting类型直接Json数据方便前端辨析
的作用:拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。
@RestControllerAdvice:可以理解为@Controller的增强版本
比起@Controller注解增强方面:
1:全局异常处理
2:全局数据绑定
3:全局数据预处理
*/
@RestControllerAdvice
public class ResultPlus implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
//返回类型是否为Sring类型,返回Json类型
if(o instanceof String){
return objectMapper.writeValueAsString(ResultData.success(o));
}
//返回类型是否已经封装
if(o instanceof ResultData){
return o;
}
return ResultData.success(o);
}
}
这样我们就完成了异常处理!这里直接用if判断请求结果是否封装过,if如果封装过就不显示Springboot默认返回的错误信息