RestFul API 统一格式返回 + 全局异常处理
程序员文章站
2022-05-23 18:49:12
一、背景 在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求 路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。 所以统一接口的返回值,保证接口返回值的幂等性很重要,本文主要介绍博主当前使用的结果集。 二、统一格式设 ......
一、背景
在分布式、微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式。前端和后端进行交互,前端按照约定请求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
状态码:
-
200
- 请求成功; -
301
- 资源(网页等)被永久转移到其它url
; -
404
- 请求的资源(网页等)不存在; -
500
- 内部服务器错误。
-
message
:错误信息
在发生错误时,如何友好的进行提示?
- 根据
code
给予对应的错误码定位; - 把错误描述记录到
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 + '\'' + '}'; } }
说明:
- 构造器私有,外部不可以直接创建;
- 只可以调用统一返回类的静态方法返回对象;
-
success
是一个boolean
值,通过这个值,可以直接观察到该次请求是否成功; -
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 返回结果
{ "code": 200, "success": true }
{ "code": 200, "data": "风尘博客", "success": true }
{ "code": 404, "message": "not found", "success": false }
四、全局异常处理
使用统一返回结果时,还有一种情况,就是程序的报错是由于运行时异常导致的结果,有些异常是我们在业务中抛出的,有些是无法提前预知。
因此,我们需要定义一个统一的全局异常,在controller
捕获所有异常,并且做适当处理,并作为一种结果返回。
4.1 设计思路:
- 自定一个异常类(如:
tokenverificationexception
),捕获针对项目或业务的异常; - 使用
@exceptionhandler
注解捕获自定义异常和通用异常; - 使用
@controlleradvice
集成@exceptionhandler
的方法到一个类中; - 异常的对象信息补充到统一结果枚举中;
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; } } }
- 说明
- 我使用的是
@restcontrolleradvice
,等同于@controlleradvice
+@responsebody
- 错误枚举类这里省略了,详见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 返回结果
{ "code": 500, "message": "for input string: \"abc123\"", "success": false }
{ "code": 4000, "message": "token 已经过期", "success": false }
5.3 小结
@restcontrolleradvice
和@exceptionhandler
会捕获所有rest
接口的异常并封装成我们定义的httpresult
的结果集返回,但是:处理不了拦截器里的异常
六、总结
没有哪一种方案是适用于各种情况的,如:分页情况,还可以增加返回分页结果的静态方案,具体实现,这里就不展示了。所以,适合自己的,具有一定可读性都是很好的,欢迎持不同意见的大佬给出意见建议。
6.1 示例代码
6.2 技术交流
推荐阅读
-
RestFul API 统一格式返回 + 全局异常处理
-
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
-
只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常
-
spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式
-
spring boot 2 全局统一返回RESTful风格数据、统一异常处理
-
RestFul API 统一格式返回 + 全局异常处理
-
利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
-
spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式