如何让 Web API 统一回传格式以及例外处理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何让 Web API 统一回传格式以及例外处理相关的知识,希望对你有一定的参考价值。

参考技术A 当我们在开发WebAPI时,一般的情况下每个API回传的数据型态或格式都不尽相同,如果你的项目从头到尾都是由你一个人独力完成,那也许还可以说声「阿密陀佛」,但如果是有其他人需要和你共享你的Api,而回传的数据格式又不一样,相信是会增加使用者的困扰,也大大增加了程序的复杂度与维护上的难度。所以本篇也纪录一下自己在实作上的经验,一方面留个纪录也希望帮助人,废物不多说我们开始吧!了解架构并实作原本在找数据时找到这篇使用Asp.NetMVC打造WebApi(16)–统一输入/出格式以及异常处理策略,不过发现里面的所用到的方法似乎是ForASP.NETMVC,而非WebAPI(不知道笔者这样认知有没有错误,如果有还麻烦前辈们指教),而本篇的思考模式跟这篇是一样的,只是把它改成WebAPI能用的方法而已。[]按照上图所示当使用者请求不同的API时,返回页面之前会将数据重新打包后再传回页面给使用者,如此一来用户所看到的数据格式就会是固定的。1.所以首先我们需要先自定义一个Model来当作我们的包装的容器,其类别的定义如下:publicclassApiResultModelpublicHttpStatusCodeStatusget;set;publicobjectDataget;set;publicstringErrorMessageget;set;2.相信写过ASP.NETMVC的朋友一定会知道,一般我们会将一些在Action中固定的逻辑,利用Filter来套用到每一个Action上面,例如:Authorize。如果你对Filter不是很熟悉可以参考一下网络上前辈所写的文章:[VS2010]ASP.NETMVCwithActionFilters。所以这边我们也需要使用同样的技巧来重新打包我们回传的数据格式,我们先新增一个ApiResultAttribute.cs的档案,且继承System.Web.Http.Filters.ActionFilterAttribute,并且复写OnActionExecuted的方法,如下:publicclassApiResultAttribute:System.Web.Http.Filters.ActionFilterAttributepublicoverridevoidOnActionExecuted(HttpActionExecutedContextactionExecutedContext)base.OnActionExecuted(actionExecutedContext);3.而OnActionExecuted会在Action执行之后呼叫,也表示我们将资料送进这个方法里面,接着处理我们主要打包的程序逻辑,程序代码如下:publicoverridevoidOnActionExecuted(HttpActionExecutedContextactionExecutedContext)base.OnActionExecuted(actionExecutedContext);ApiResultModelresult=newApiResultModel();//取得由API返回的状态代码result.Status=actionExecutedContext.ActionContext.Response.StatusCode;//取得由API返回的资料result.Data=actionExecutedContext.ActionContext.Response.Content.ReadAsAsync().Result;//重新封装回传格式actionExecutedContext.Response=actionExecutedContext.Request.CreateResponse(result.Status,result);4.而为了要让所有的WebAPI都能套用我们自定义的Filter,所以我们需要到App_Start→WebApiConfig.cs→Register来注册全局的WebAPIFilter。(注意:若要注册WebAPI的Filter需在WebApiConfig.cs中注册,而非FilterConfig.cs中)config.Filters.Add(newApiResultAttribute());5.重新建置之后我们再重新执行一次原先的WebAPI程序,就会看到回传的格式已经变成我们自定义的格式了:"Status":200,"Data":["Account":"taxi","Mark":"","Name":"王大明","Telephone":"0986540123","AccountStatus":true,"Account":"taxi2","Mark":"","Name":"方大同","Telephone":"0922335111","AccountStatus":true,"Account":"q121234567","Mark":null,"Name":"0000","Telephone":"0972334334","AccountStatus":true],"ErrorMessage":null例外处理前面我们已经将讯息打包成我们要的格式了,不过我们还没确切地去处理有关例外的程序代码,一般当程序发生错误产生例外时,我们当然也希望接收端能知道程序发生错误,进而显示该显示的讯息,而不是活生生地看着程序Crash或是停顿,这样将带给你的客户不好的体验,而在ASP.NETMVC中也有提供专门处理例外的ExceptionFilterAttribute,所以接着来看看该如何打包我们的例外讯息吧。1.新增一个ApiErrorHandleAttribute.cs并且继承System.Web.Http.Filters.ExceptionFilterAttribute,接着复写OnException当例外发生时执行的方法,程序代码如下:publicclassApiErrorHandleAttribute:System.Web.Http.Filters.ExceptionFilterAttributepublicoverridevoidOnException(System.Web.Http.Filters.HttpActionExecutedContextactionExecutedContext)base.OnException(actionExecutedContext);2.透过OnException的方法能让我们捕捉当例外发生时要处理的事情,一般系统我们也会在这边将发生错误的时间、登入的用户以及错误的状况记录下来(例如:系统事件、存入数据库、写入.txt档…等),不过这边不是我们讨论的重点,我们先来看看该如何打包我们的例外讯息,程序代码如下:publicoverridevoidOnException(System.Web.Http.Filters.HttpActionExecutedContextactionExecutedContext)base.OnException(actionExecutedContext);//取得发生例外时的错误讯息varerrorMessage=actionExecutedContext.Exception.Message;varresult=newApiResultEntity()Status=HttpStatusCode.BadRequest,ErrorMessage=errorMessage;//重新打包回传的讯息actionExecutedContext.Response=actionExecutedContext.Request.CreateResponse(result.Status,result);3.而因为程序丢出例外后会先回到OnActionExcuted在进到例外的处理,所以我们稍微修改一下原本的OnActionExcuted这个方法,让发生例外时就直接跳过不再这边打包我们的讯息,程序代码如下:publicoverridevoidOnActionExecuted(HttpActionExecutedContextactionExecutedContext)//若发生例外则不在这边处理if(actionExecutedContext.Exception!=null)return;base.OnActionExecuted(actionExecutedContext);ApiResultModelresult=newApiResultModel();//取得由API返回的状态代码result.Status=actionExecutedContext.ActionContext.Response.StatusCode;//取得由API返回的资料result.Data=actionExecutedContext.ActionContext.Response.Content.ReadAsAsync().Result;//重新封装回传格式actionExecutedContext.Response=actionExecutedContext.Request.CreateResponse(result.Status,result);3.接着我们一样要将此自定义的Filter注册到程序当中,所以一样到App_Start→WebApiConfig.cs→Register来注册我们的Filter:config.Filters.Add(newApiErrorHandleAttribute());4.重新建置后,当我们程序发生例外时也会依照我们的格式回传给使用者:"Status":400,"Data":null,"ErrorMessage":"尝试以零除。"总结就这样我们又成功解决了一个简单的案例,不过这边也需要提醒一下读者,一般在处理例外这边是不会直接将例外讯息回传给用户的,因为如果假设你今天丢出的例外有包含了一些比较敏感的信息,例如:数据库名称或数据表名称…等等,这样一来你的程序就间接的有了漏洞了,所以如果真的要用此程序代码记得后面例外捕捉那边还要在包装一下。

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



扫描下方海报 试读 

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


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

来源:

老顾聊技术


  • 前言

  • 接口交互

  • 返回格式

  • 控制层Controller

  • 美观美化

  • 优雅优化

  • 实现方案


前言

在移动互联网,分布式、微服务盛行的今天,现在项目绝大部分都采用的微服务框架,前后端分离方式,

(题外话:前后端的工作职责越来越明确,现在的前端都称之为大前端,技术栈以及生态圈都已经非常成熟;以前后端人员瞧不起前端人员,那现在后端人员要重新认识一下前端,前端已经很成体系了)

一般系统的大致整体架构图如下:

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

需要说明的是,有些小伙伴会回复说,这个架构太简单了吧,太low了,什么网关啊,缓存啊,消息中间件啊,都没有。


因为老顾这篇主要介绍的是API接口,所以我们聚焦点,其他的模块小伙伴们自行去补充。

接口交互

前端和后端进行交互,前端按照约定请求URL路径,并传入相关参数,后端服务器接收请求,进行业务处理,返回数据给前端。

针对URL路径的restful风格,以及传入参数的公共请求头的要求(如:app_version,api_version,device等),老顾这里就不介绍了,小伙伴们可以自行去了解,也比较简单。

后端服务器如何实现把数据返回给前端?

返回格式

后端返回给前端我们一般用JSON体方式,定义如下:

{
  #返回状态码
  code:integer,
  #返回信息描述
  message:string,
  #返回值
  data:object
}

CODE状态码

code返回状态码,一般小伙伴们是在开发的时候需要什么,就添加什么。

如接口要返回用户权限异常,我们加一个状态码为101吧,下一次又要加一个数据参数异常,就加一个102的状态码。这样虽然能够照常满足业务,但状态码太凌乱了

我们应该可以参考HTTP请求返回的状态码

:下面是常见的HTTP状态码:
200 - 请求成功
301 - 资源(网页等)被永久转移到其它URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误

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

我们可以参考这样的设计,这样的好处就把错误类型归类到某个区间内,如果区间不够,可以设计成4位数。

#1000~1999 区间表示参数错误
#2000~2999 区间表示用户错误
#3000~3999 区间表示接口异常

这样前端开发人员在得到返回值后,根据状态码就可以知道,大概什么错误,再根据message相关的信息描述,可以快速定位。

Message

这个字段相对理解比较简单,就是发生错误时,如何友好的进行提示。一般的设计是和code状态码一起设计,如:

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

再在枚举中定义,状态码

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

状态码和信息就会一一对应,比较好维护。

Data

返回数据体,JSON格式,根据不同的业务又不同的JSON体。

我们要设计一个返回体类Result

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

控制层Controller

我们会在controller层处理业务请求,并返回给前端,以order订单为例

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

我们看到在获得order对象之后,我们是用的Result构造方法进行包装赋值,然后进行返回。小伙伴们有没有发现,构造方法这样的包装是不是很麻烦,我们可以优化一下。

美观美化

我们可以在Result类中,加入静态方法,一看就懂

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

那我们来改造一下Controller

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

代码是不是比较简洁了,也美观了。

优雅优化

上面我们看到在Result类中增加了静态方法,使得业务处理代码简洁了。但小伙伴们有没有发现这样有几个问题:

  1. 每个方法的返回都是Result封装对象,没有业务含义

  2. 在业务代码中,成功的时候我们调用Result.success,异常错误调用Result.failure。是不是很多余

  3. 上面的代码,判断id是否为null,其实我们可以使用hibernate validate做校验,没有必要在方法体中做判断。

我们最好的方式直接返回真实业务对象,最好不要改变之前的业务方式,如下图:

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

这个和我们平时的代码是一样的,非常直观,直接返回order对象,这样是不是很完美。

那实现方案是什么呢?

实现方案

小伙伴们怎么去实现是不是有点思路,在这个过程中,我们需要做几个事情

  1. 定义一个注解@ResponseResult,表示这个接口返回的值需要包装一下

  2. 拦截请求,判断此请求是否需要被@ResponseResult注解

  3. 核心步骤就是实现接口ResponseBodyAdvice和@ControllerAdvice,判断是否需要包装返回值,如果需要,就把Controller接口的返回值进行重写。

注解类

用来标记方法的返回值,是否需要包装

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

拦截器

拦截请求,是否此请求返回的值需要包装,其实就是运行的时候,解析@ResponseResult注解

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

此代码核心思想,就是获取此请求,是否需要返回值包装,设置一个属性标记。

重写返回体

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

上面代码就是判断是否需要返回值包装,如果需要就直接包装。

这里我们只处理了正常成功的包装,如果方法体报异常怎么办?处理异常也比较简单,只要判断body是否为异常类。

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

怎么做全局的异常处理,篇幅原因,老顾这里就不做介绍了,只要思路理清楚了,自行改造就行。

重写Controller

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

在控制器类上或者方法体上加上@ResponseResult注解,这样就ok了,简单吧。到此返回的设计思路完成,是不是又简洁,又优雅。

这个方案还有没有别的优化空间,当然是有的。

如:每次请求都要反射一下,获取请求的方法是否需要包装,其实可以做个缓存,不需要每次都需要解析。当然整体思路了解,小伙伴们就可以在此基础上面自行扩展。

END

如有收获,请划至底部,点击“在看”,谢

BAT架构经验倾囊相授

以上是关于如何让 Web API 统一回传格式以及例外处理的主要内容,如果未能解决你的问题,请参考以下文章

WEB API 系列 Filter的使用以及执行顺序

一文了解.Net Core 3.1 Web API基础知识

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

SPRINGBOOT9--AOP的使用(本例展示统一处理Web请求日志)

WEB API 系列 Filter的使用以及执行顺序

基于.Net Framework 4.0 Web API开发:ASP.NET Web APIs 异常的统一处理Attribute 和统一写Log 的Attribute的实现