一、背景

在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。

所以统一接口的返回值,保证接口返回值的幂等性很重要,本文主要介绍博主当前使用的结果集。

二、统一格式设计

2.1 统一结果的一般形式

  • 示例:
{
# 是否响应成功
success: true,
# 响应状态码
code: 200,
# 响应数据
data: Object
# 返回错误信息
message: "",
}

2.2 结果类枚举

public enum ResultCodeEnum {
/*** 通用部分 100 - 599***/
// 成功请求
SUCCESS(200, "successful"),
// 重定向
REDIRECT(301, "redirect"),
// 资源未找到
NOT_FOUND(404, "not found"),
// 服务器错误
SERVER_ERROR(500,"server error"), /*** 这里可以根据不同模块用不同的区级分开错误码,例如: ***/ // 1000~1999 区间表示用户模块错误
// 2000~2999 区间表示订单模块错误
// 3000~3999 区间表示商品模块错误
// 。。。 ;
/**
* 响应状态码
*/
private Integer code;
/**
* 响应信息
*/
private String message; ResultCodeEnum(Integer code, String msg) {
this.code = code;
this.message = msg;
} public Integer getCode() {
return code;
} public String getMessage() {
return message;
}
}
  • code:响应状态码

一般小伙伴们是在开发的时候需要什么,就添加什么。但是,为了规范,我们应当参考HTTP请求返回的状态码。

code区间 类型 含义
1** 100-199 信息 服务器接收到请求,需要请求者继续执行操作
2** 200-299 成功 请求被成功接收并处理
3** 300-399 重定向 需要进一步的操作以完成请求
4** 400-499 客户端错误 请求包含语法错误或无法完成请求
5** 500-599 服务器错误 服务器在处理的时候发生错误

常见的HTTP状态码:

  1. 200 - 请求成功;
  2. 301 - 资源(网页等)被永久转移到其它URL
  3. 404 - 请求的资源(网页等)不存在;
  4. 500 - 内部服务器错误。
  • message:错误信息

在发生错误时,如何友好的进行提示?

  1. 根据code 给予对应的错误码定位;
  2. 把错误描述记录到message中,便于接口调用者更详细的了解错误。

2.3 统一结果类

public class HttpResult <T> implements Serializable {

    /**
* 是否响应成功
*/
private Boolean success;
/**
* 响应状态码
*/
private Integer code;
/**
* 响应数据
*/
private T data;
/**
* 错误信息
*/
private String message; // 构造器开始
/**
* 无参构造器(构造器私有,外部不可以直接创建)
*/
private HttpResult() {
this.code = 200;
this.success = true;
}
/**
* 有参构造器
* @param obj
*/
private HttpResult(T obj) {
this.code = 200;
this.data = obj;
this.success = true;
} /**
* 有参构造器
* @param resultCode
*/
private HttpResult(ResultCodeEnum resultCode) {
this.success = false;
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
}
// 构造器结束 /**
* 通用返回成功(没有返回结果)
* @param <T>
* @return
*/
public static<T> HttpResult<T> success(){
return new HttpResult();
} /**
* 返回成功(有返回结果)
* @param data
* @param <T>
* @return
*/
public static<T> HttpResult<T> success(T data){
return new HttpResult<T>(data);
} /**
* 通用返回失败
* @param resultCode
* @param <T>
* @return
*/
public static<T> HttpResult<T> failure(ResultCodeEnum resultCode){
return new HttpResult<T>(resultCode);
} public Boolean getSuccess() {
return success;
} public void setSuccess(Boolean success) {
this.success = success;
} public Integer getCode() {
return code;
} public void setCode(Integer code) {
this.code = code;
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
} public String getMessage() {
return message;
} public void setMessage(String message) {
this.message = message;
} @Override
public String toString() {
return "HttpResult{" +
"success=" + success +
", code=" + code +
", data=" + data +
", message='" + message + '\'' +
'}';
}
}

说明:

  1. 构造器私有,外部不可以直接创建;
  2. 只可以调用统一返回类的静态方法返回对象;
  3. success 是一个Boolean 值,通过这个值,可以直接观察到该次请求是否成功;
  4. data 表示响应数据,用于请求成功后,返回客户端需要的数据。

三、测试及总结

3.1 简单的接口测试

@RestController
@RequestMapping("/httpRest")
@Api(tags = "统一结果测试")
public class HttpRestController { @ApiOperation(value = "通用返回成功(没有返回结果)", httpMethod = "GET")
@GetMapping("/success")
public HttpResult success(){
return HttpResult.success();
} @ApiOperation(value = "返回成功(有返回结果)", httpMethod = "GET")
@GetMapping("/successWithData")
public HttpResult successWithData(){
return HttpResult.success("风尘博客");
} @ApiOperation(value = "通用返回失败", httpMethod = "GET")
@GetMapping("/failure")
public HttpResult failure(){
return HttpResult.failure(ResultCodeEnum.NOT_FOUND);
} }

这里 Swagger以及SpringMVC的配置就没贴出来了,详见Github 示例代码。

3.2 返回结果

http://localhost:8080/swagger-ui.html#/

{
"code": 200,
"success": true
}
{
"code": 200,
"data": "风尘博客",
"success": true
}
{
"code": 404,
"message": "not found",
"success": false
}

四、全局异常处理

使用统一返回结果时,还有一种情况,就是程序的报错是由于运行时异常导致的结果,有些异常是我们在业务中抛出的,有些是无法提前预知。

因此,我们需要定义一个统一的全局异常,在Controller捕获所有异常,并且做适当处理,并作为一种结果返回。

4.1 设计思路:

  1. 自定一个异常类(如:TokenVerificationException),捕获针对项目或业务的异常;
  2. 使用@ExceptionHandler注解捕获自定义异常和通用异常;
  3. 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
  4. 异常的对象信息补充到统一结果枚举中;

4.2 自定义异常

public class TokenVerificationException extends RuntimeException {

    /**
* 错误码
*/
protected Integer code; protected String msg; public Integer getCode() {
return code;
} public String getMsg() {
return msg;
} public void setMsg(String msg) {
this.msg = msg;
} /**
* 有参构造器,返回码在枚举类中,这里可以指定错误信息
* @param msg
*/
public TokenVerificationException(String msg) {
super(msg);
}
}

4.3 统一异常处理器

@ControllerAdvice注解是一种作用于控制层的切面通知(Advice),能够将通用的@ExceptionHandler@InitBinder@ModelAttributes方法收集到一个类型,并应用到所有控制器上。

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler { /**
* 异常捕获
* @param e 捕获的异常
* @return 封装的返回对象
**/
@ExceptionHandler(Exception.class)
public HttpResult handlerException(Exception e) {
ResultCodeEnum resultCodeEnum;
// 自定义异常
if (e instanceof TokenVerificationException) {
resultCodeEnum = ResultCodeEnum.TOKEN_VERIFICATION_ERROR;
resultCodeEnum.setMessage(getConstraintViolationErrMsg(e));
log.error("tokenVerificationException:{}", resultCodeEnum.getMessage());
}else {
// 其他异常,当我们定义了多个异常时,这里可以增加判断和记录
resultCodeEnum = ResultCodeEnum.SERVER_ERROR;
resultCodeEnum.setMessage(e.getMessage());
log.error("common exception:{}", JSON.toJSONString(e));
}
return HttpResult.failure(resultCodeEnum);
} /**
* 获取错误信息
* @param ex
* @return
*/
private String getConstraintViolationErrMsg(Exception ex) {
// validTest1.id: id必须为正数
// validTest1.id: id必须为正数, validTest1.name: 长度必须在有效范围内
String message = ex.getMessage();
try {
int startIdx = message.indexOf(": ");
if (startIdx < 0) {
startIdx = 0;
}
int endIdx = message.indexOf(", ");
if (endIdx < 0) {
endIdx = message.length();
}
message = message.substring(startIdx, endIdx);
return message;
} catch (Throwable throwable) {
log.info("ex caught", throwable);
return message;
}
}
}
  • 说明
  1. 我使用的是@RestControllerAdvice ,等同于@ControllerAdvice + @ResponseBody
  2. 错误枚举类这里省略了,详见Github代码

五、测试及总结

5.1 测试接口

@RestController
@RequestMapping("/exception")
@Api(tags = "异常测试接口")
public class ExceptionRestController { @ApiOperation(value = "业务异常(token 异常)", httpMethod = "GET")
@GetMapping("/token")
public HttpResult token() {
// 模拟业务层抛出 token 异常
throw new TokenVerificationException("token 已经过期");
} @ApiOperation(value = "其他异常", httpMethod = "GET")
@GetMapping("/errorException")
public HttpResult errorException() {
//这里故意造成一个其他异常,并且不进行处理
Integer.parseInt("abc123");
return HttpResult.success();
}
}

5.2 返回结果

http://localhost:8080/swagger-ui.html#/

{
"code": 500,
"message": "For input string: \"abc123\"",
"success": false
}
{
"code": 4000,
"message": "token 已经过期",
"success": false
}

5.3 小结

@RestControllerAdvice@ExceptionHandler会捕获所有Rest接口的异常并封装成我们定义的HttpResult的结果集返回,但是:处理不了拦截器里的异常

六、总结

没有哪一种方案是适用于各种情况的,如:分页情况,还可以增加返回分页结果的静态方案,具体实现,这里就不展示了。所以,适合自己的,具有一定可读性都是很好的,欢迎持不同意见的大佬给出意见建议。

6.1 示例代码

Github 示例代码

6.2 技术交流

  1. 风尘博客
  2. 风尘博客-掘金
  3. 风尘博客-博客园
  4. Github

RestFul API 统一格式返回 + 全局异常处理的更多相关文章

  1. springboot实现定时任务,异步操作,统一结果返回,全局异常处理,拦截器及事务处理

    本文是作者原创,版权归作者所有.若要转载,请注明出处. 本文都是springboot的常用和实用功能,话不多说开始吧 定时任务 1.启动类开启注解 @EnableScheduling //开启基于注解 ...

  2. Java生鲜电商平台-统一格式返回的API架构设计与实战

    Java生鲜电商平台-统一格式返回的API架构设计与实战 说明:随着互联网各岗位精细化分工的普及,出现了很多的系统架构设计,比如常见的前后端分离架构,后端提供接口给前端,前端根据接口的数据进行渲染,大 ...

  3. 如何设计 API 接口,实现统一格式返回?

    文章目录: 目录 前后端接口交互 接口返回值约定 返回值规范 正确返回 错误返回 统一定义错误码 错误码规范 Controller 层如何用? 正确返回 错误返回 详细代码实现 错误码 Control ...

  4. Java设计RestfulApi接口,实现统一格式返回

    创建返回状态码枚举 package com.sunny.tool.api.enums; /** * @Author sunt * @Description 响应枚举状态码 * @Date 2019/1 ...

  5. Hbase RESTFul API创建namespace返回500

    1.使用官方提供的/namespaces/namespace创建namespace失败,返回500,官方提供示例:/namespaces/namespace POST 创建一个新的namespace. ...

  6. 每天用SpringBoot,还不懂RESTful API返回统一数据格式是怎么实现的?

    上一篇文章RESTful API 返回统一JSON数据格式 说明了 RESTful API 统一返回数据格式问题,这是请求一切正常的情形,这篇文章将说明如何统一处理异常,以及其背后的实现原理,老套路, ...

  7. 使用 .NET Core 3.x 构建 RESTFUL Api

    准备工作:在此之前你需要了解关于.NET .Core的基础,前面几篇文章已经介绍:https://www.cnblogs.com/hcyesdo/p/12834345.html 首先需要明确一点的就是 ...

  8. 【第二十三章】 springboot + 全局异常处理

    一.单个controller范围的异常处理 package com.xxx.secondboot.web; import org.springframework.web.bind.annotation ...

  9. 第二十三章 springboot + 全局异常处理

    一.单个controller范围的异常处理 package com.xxx.secondboot.web; import org.springframework.web.bind.annotation ...

随机推荐

  1. 最长递增子序列-dp问题

    Longest Increasing Subsequence The longest increasing subsequence problem is to find a subsequence o ...

  2. cesium入门示例-3dTiles加载

    数据转换工具采用cesiumlab1.5.17版本,转换后的3dTiles加载显示比较简单,通过Cesium.Cesium3DTileset接口指定url即可,3dTiles文件可与js前端代码放置一 ...

  3. HTMLTestRunner测试报告

    把测试报告写入文件中,设置报告生成的路径 测试报告名称上添加时间 HTMLTestRunner文件如下,复制即可用,把该文件放在Lib下即可 """ A TestRunn ...

  4. 吴裕雄--天生自然python编程:turtle模块绘图(3)

    turtle(海龟)是Python重要的标准库之一,它能够进行基本的图形绘制.turtle图形绘制的概念诞生于1969年,成功应用于LOGO编程语言. turtle库绘制图形有一个基本框架:一个小海龟 ...

  5. php 克隆 clone

    php 克隆 clone 在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值, ...

  6. JAVA中String类的比较

    首先给大家看一段代码 package javaapptest; public class JavaAppTest { public static void main(String[] args) { ...

  7. IT培训的背后,是“韭菜”的躺赚梦!

    作者:九歌 本文转自公众号:Hack 本文只谈"骗局",不谈其他,绝不引战,如有错误,希望指出我会及时改正 导语 为什么要写这篇文章呢,近些年培训这个话题也比较火,很多在看这篇文章 ...

  8. Java的锁机制--synchronsized关键字

    引言 高并发环境下,多线程可能需要同时访问一个资源,并交替执行非原子性的操作,很容易出现最终结果与期望值相违背的情况,或者直接引发程序错误. 举个简单示例,存在一个初始静态变量count=0,两个线程 ...

  9. 听起来很美,用起来很累!停车类APP软肋在哪

    据数据显示,全国现有汽车已达1亿7千万辆,停车位缺于6800万个.而在北京,汽车保有量和车位的配比大约是1:0.5,而国际上一般是1:1.2,结构严重失衡.正所谓哪里有需求,哪里就有市场.停车位的走俏 ...

  10. 【基础篇】hexo博客搭建教程

    [基础篇]搭建hexo博客(一) 作者:Huanhao bilibili:Mrhuanhao 前言 你是否想拥有属于自己的博客?你是否无奈与自己不会写网站而烦恼? 不要担心,本系列教程将会实现你白嫖的 ...