小强哥博客

小强哥,小强哥博客,技术大咖

RESTful接口返回的报文数据结构应该怎么设计比较合理?

我从过年2月份到现在实实在在的带了一个项目,做了一次实实在在技术TL,从需求规划、技术选型、上线发版、接口规则制定,以至现在开始开发自己的App。说实话,这技术负责人还真不是那么好做,没做之前总是觉得技术领导这不对,不该这么设计,那也不对,应该这么设计,当真正的做了技术负责人以后才发现,这也可以,那也可以。

来吧,不废话,进入正题,我结合负责的真实项目分享「RESTful接口返回的报文数据结构应该怎么设计比较合理?」

上任第一天,我接到的第一个需求是「快速得迭代开发一个小程序,并完发布」,时间只有一周,当我拿到这需求是心里一紧,尼玛只有一周,需求也不复杂,具体是啥就不说了。

我们几个开发加班加点的讨论着需求、设计着接口,很快,第一版接口和快就出来了《下面展示的是接口案例,数据和实际有所偏差》。

「这是一个Get接口,返回Member列表」

GET http://xxx.xxx.xxx.xxx:8080/v1/members HttpStatus:200
#req
#resp
{
	"code":1,
	"message":"ok",
	"data":{
		"rows":[
			{
				"xxx":"xxxx",
				"yyy":"xxxx"
			},
			{
				"xxx":"xxxx",
				"yyy":"xxxx"
			}
		]
	}
}

「这是一个Post接口,新增Member」

POST http://xxx.xxx.xxx.xxx:8080/v1/members HttpStatus:200
#req
{
	"xxx":"xxxx",
	"yyy":"xxxx"
}
#resp
{
	"code":1,
	"message":"ok"
}

「这是一个Put接口,用来修改数据」

PUT http://xxx.xxx.xxx.xxx:8080/v1/members/[1] HttpStatus:200
#req
{
	"xxx":"xxxx",
	"yyy":"xxxx"
}
#resp
{
	"code":1,
	"message":"ok"
}

「这是一个Delete接口,用来删除数据」

DELETE http://xxx.xxx.xxx.xxx:8080/v1/members/[1] HttpStatus:200
#req
#resp
{
	"code":1,
	"message":"ok"
}

我相信大部分的开发团队都是使用上面这种接口报文设计方式,并且也都能满足大部分的需求,这种接口的特点如下:

  • 报文结构统一,Http状态码强制统一返回200,都是基于「code」「message」「data」进行封装,对前端来说这种封装非常友好
  • 「code」具有1和0两种判断方式,当为1的时候表示后台处理成功,当为0的时候表示后台处理失败,处理失败的时候前端开发再把message里面的数据取出来展现给C端

我一开始也是看上了上面的两点优点采用了这种方式,运行了两个月,一起都没有什么问题。

随着项目越来越大,我们就需要对项目运行时的情况有所了解,这时候系统监控告警功能就开始要加上了。所谓请求监控就是对200、404、500这种Http状态码做监控,通过实时分析Nginx日志,按照日志格式解析出每次请求的状态码,然后汇总、归类,这些数据可以用来生成图标,如下;也可以用来做监控报警,例如:当出现500错误的时候发送一个电子邮件给开发。


面对这样一个需求,我和开发又开始了一轮接口改造工作。首先,我们和前端约定了后台接口状态码,如下:

200 成功,后端能够解析前端数据请求数据,并正确处理。
401 接口认证错误,举例:客户端传入的不正确的token。
403 接口限制,举例:黑名单账号,黑名单IP。
202 用户请求数据没有问题,由于后端产不满足条件或产生了可预见的错误,举例:账密不正确、库存不足
400 用户请求数据有问题,后端无法识别客户端意图,举例:参数没有传递,参数不合法
5XX 后端产生了不可预见的错误,举例:空指针、访问数据库错误,应该尽量将不可预见变为可预见202错误

根据这些状态码,我们又调整了接口定义,如下:

「这是一个Get接口,返回Member列表」

GET http://xxx.xxx.xxx.xxx:8080/v1/members HttpStatus:200|202|400|401|403|5XX
#req
#resp
{
	"message":"ok",
	"data":{
		"rows":[
			{
				"xxx":"xxxx",
				"yyy":"xxxx"
			},
			{
				"xxx":"xxxx",
				"yyy":"xxxx"
			}
		]
	}
}

「这是一个Post接口,新增Member」

POST http://xxx.xxx.xxx.xxx:8080/v1/members HttpStatus:200|202|400|401|403|5XX
#req
{
	"xxx":"xxxx",
	"yyy":"xxxx"
}
#resp
{
	"message":"ok"
}

「这是一个Put接口,用来修改数据」

PUT http://xxx.xxx.xxx.xxx:8080/v1/members/[1] HttpStatus:200|202|400|401|403|5XX
#req
{
	"xxx":"xxxx",
	"yyy":"xxxx"
}
#resp
{
	"message":"ok"
}

「这是一个Delete接口,用来删除数据」

DELETE http://xxx.xxx.xxx.xxx:8080/v1/members/[1] HttpStatus:200|202|400|401|403|5XX
#req
#resp
{
	"message":"ok"
}

这一版接口出来以后,着实让前端开发吐槽了一波,主要是前端觉得不好用,之前根据「code」直接来判断是否成功,现在还需要根据各种状态码来判断;再一个问题就是前段开发用了vue的一个数据请求插件,这插件会直接把像400、401这种状态给过滤掉,导致前端绕了一个大圈才获得到这些状态码。介于这两点,前端开发吐槽是非常多的,但是从技术负责人角度来考虑,这是必须得做的,我需要对项目负责,需要知道项目的运行情况。在我看来,http接口上使用状态码使使接口看起来更加清晰,错误的、无权限的、前端数据格式错误的看起来更加直接。

没过多久,前端跑过来说让我再增加一个状态码,仔细了解下来是有一个业务逻辑,如下图,前端需要根据后端传递过来的状态码进行业务跳转,于是立刻和后台开发同事商量解决方案。


我心想,这肯定不能通过增加状态吗来解决,一是状态码本来就不多,如果每个跳转都需要一个状态码,那还没等项目开发完状态码就没了,二是考虑这跳转本身就是业务逻辑,状态码只是用来反映我当前的请求情况,增加状态码肯定不合适。如是又和开发重新定义了接口,新版的接口如下,


「这是一个Get接口,返回Member列表

GET http://xxx.xxx.xxx.xxx:8080/v1/members HttpStatus:200|202|400|401|403|5XX
#req
#resp
{
    "code":"OK",
	"message":"ok",
	"data":{
		"rows":[
			{
				"xxx":"xxxx",
				"yyy":"xxxx"
			},
			{
				"xxx":"xxxx",
				"yyy":"xxxx"
			}
		]
	}
}

「这是一个Post接口,新增Member」

POST http://xxx.xxx.xxx.xxx:8080/v1/members HttpStatus:200|202|400|401|403|5XX
#req
{
	"xxx":"xxxx",
	"yyy":"xxxx"
}
#resp
{
    "code":"EXISTS_TICKET",
	"message":"ok"
}

「这是一个Put接口,用来修改数据」

PUT http://xxx.xxx.xxx.xxx:8080/v1/members/[1] HttpStatus:200|202|400|401|403|5XX
#req
{
	"xxx":"xxxx",
	"yyy":"xxxx"
}
#resp
{
    "code":"EXISTS_TICKET",
	"message":"ok"
}

「这是一个Delete接口,用来删除数据」

DELETE http://xxx.xxx.xxx.xxx:8080/v1/members/[1] HttpStatus:200|202|400|401|403|5XX
#req
#resp
{
    "code":"OK",
	"message":"ok"
}

在新版的返回参数中,我们重新将「code」加入了进来,这里「code」并不是一开始那个1或0的「code」,而是一个枚举值,用来表示当前业务情况的一个值,随着业务走。每一套业务下都定义有自己的code,如这里的Member定义有,

enum MemberCode {
  OK("ok")
  EXISTS("会员存在")
  EXISTS_TICKET("会员已经购票")
}

通过这种方式及解决了监控问题,有解决了前端需要依赖业务状态码问题。


以上就是我目前所负责的项目使用的接口报文规范以及Http状态码使用,可能不是最好的,但是在我目前阶段应该是最合理的。


完。


相关内容:

http://xiaoqiangge.com/aritcle/1497859085696.html