聊聊接口的返回数据结构

前段时间在推上看到好几篇讨论接口响应格式的推文,下面的评论有好多都在吐槽和抱怨返回值没有必要包含code码以及错误信息,因为http的code码和code信息已经足够用了,加上纯粹是属于脱了裤子放屁。今天我来和大家聊聊我的想法。

以下讨论的接口响应结构皆是基于博主开源的框架go-sail来进行的。

仓库地址: https://github.com/keepchen/go-sail

首先让我们来看一下go-sail的接口响应都有哪些字段:

{
  "code": 200,
  "data": null,
  "message": "SUCCESS",
  "requestId": "5686efa5-c747-4f63-8657-e6052f8181a9",
  "success": true,
  "ts": 1670899688591
}

除了data字段是interface类型(你可以理解为是其他语言的泛型)外,其余字段都是明确的数据类型。它们都有各自的含义和应用场景。

我们现在逐个解释一下字段的含义:

  • code
    业务码,int类型,可以对应Javascript语言的number类型,对应Java语言的int或Integer类型。

  • data
    数据体,业务的具体数据响应实体。该类型为Go语言的interface,可以对应其他语言的泛型。

  • message
    提示消息,string类型。

  • requestId
    请求唯一标识符,string类型。

  • success
    是否成功,bool类型。用于业务逻辑处理是否成功。

  • ts
    系统当前毫秒时间戳,int64类型,可对应Javascript语言的number类型,Java语言的Long类型。

code

有不少人都说,当请求成功时,http状态码已经是200了,这里的code码又写一遍200,纯粹就是脱了裤子放屁。这里需要解释一下:

1.go-sail提供的默认code值为0,在使用过程,开发者可以根据自己的需要自行指定成功的code为多少。
2.我建议成功的code码应该设置为非0值,原因是出于兜底方案的考虑,当你在某处调用遗忘了对code进行赋值,那么code=0的情况就与你设置的非0成功code码不相等,此时兴许能在关键时刻救你一命(KPI和BUG率警告⚠️)。

那么为什么非要设置业务code呢?

在回答这个问题之前,我想问一句,你有没有集成过阿里云、腾讯云、七牛云等等的云服务商的功能接口?有没有发现他们的接口都有业务code。这里提到他们并非人云亦云,他们有我就该照葫芦画瓢也要有。而是业务code有其必然存在的原因,否则大家的设计不会出奇的一致。

其他的设计者思路我不清楚,我谈谈我的设计思路:

  • 业务码可以尽可能详尽的表现业务逻辑处理过程中遇到的各种问题,表达更多的业务处理状态或是中断状态。
  • 业务码可以结合为前端或客户端提供更好的多语言支持。

先说第一点,就拿常见的购物下单逻辑来举例,code=1000可以表示余额不足,code=1001可以表示库存不足,code=1002可以表示在并发竞争的时候抢购失败,code=1003可以表示写入数据库的时候由于网络抖动而写入失败。等等等等。

当前端或客户端拿到对应的code码,可以根据前端的本地化配置,根据code码以及用户当前的系统语言,为用户展示出足够人性化的提示语,例如:“抱歉,当前商品库存不足”,“您当前的余额不足”,“您来晚了,商品已被抢购一空”等等。

这里分享一下星光图床的本地化实践代码:

//接口错误码
TID_API_CODE_200: '请求成功',
TID_API_CODE_504: '请求超时',
TID_API_CODE_999999: '服务器内部错误',
TID_API_CODE_100000: '请求参数有误',
TID_API_CODE_100001: '登录状态失效',
TID_API_CODE_100002: '人机验证不通过',
TID_API_CODE_100003: '验证码不正确',
TID_API_CODE_100004: '账号不存在',
TID_API_CODE_100005: '账号密码不匹配',
TID_API_CODE_100006: '找不到此订阅信息',
TID_API_CODE_100007: '发送频率超出限制',
TID_API_CODE_100008: '发送次数超出限制',
TID_API_CODE_100009: '空间不存在',
TID_API_CODE_100010: '空间已存在',
TID_API_CODE_100011: '空间不为空',
TID_API_CODE_100012: '操作太过频繁',
TID_API_CODE_100013: '空间数量配额耗尽',
TID_API_CODE_100014: '文件数量配额耗尽',
TID_API_CODE_100015: '传输流量配额耗尽',
TID_API_CODE_100016: '订阅计划不匹配',
TID_API_CODE_100017: '空间文件访问受限',
TID_API_CODE_100018: '上传文件超出大小限制',
TID_API_CODE_100019: '上传文件格式不支持',
...

前端或客户端在请求拦截器里面可以做统一处理:

//重写错误消息
let message = $t(`TID_API_CODE_${respData.code}`)

$t是博主自己实现的一个根据语言代码获取语言文字的函数,逻辑比较简单,这里就不赘述了。

读到这里,稍微有些许的认同感了吗?😛

另外,上面一系列的业务码可能都会影响到另一个字段值success,让success变为false。这一点后面详说。

message

提示消息,当你定义好业务码枚举并使用RegisterCodeTable函数注册好之后,go-sail会在你调用的时候,自动帮你维护提示消息。这个提示消息在我看来更多的是给开发人员或是本地化不完善的情况下做兜底的方案。例如上述的code=1000的时候,本地化没有找到对应的语言代码,可以降级使用接口返回的message字段值,显示效果可能从“您的余额不足”变成了"Insufficient balance",但好歹能用不是?

尽管如此,go-sail依然提供了多语言业务码的注册功能,你可以在服务端做上述的前端或客户端本地化的工作。至于采用哪种方案,那需要看你的具体业务需求。

requestId

唯一请求标识符,这个字段可以说为问题排查奠定了坚实的基础。go-sail提供了调用链日志追踪功能,也就是可观测性的一隅。

当你的接口响应出现错误时,可以依据requestId进行调用链追踪,方便问题排查:

GVMadgLbkAAcLmr.jpeg(via stardots.ink)

GVMalvqbwAAjM1w.jpeg(via stardots.ink)

图中博主使用的是ELK日志收集,当然你也可以从你的本地文件用命令来进行查看。go-sail的日志收集也同样支持本地文件输出。

tail -f -n 100 ~/your-logfile.log |grep 72a99ac0

日志输出到文件做了日志轮转功能,不会把你的磁盘塞满,把心放到肚子里。

这个时候可以说不再像以前一样遇到问题两眼一抹黑了。😎

你的产品经理甚至可以要求前端或客户端同学,将requestId放到提示弹窗里面,这样出现问题时,用户反馈截图即可解决问题!

像这样:
iShot_2024-12-18_22.01.31.png(via stardots.ink)

提示:图中是对requestId截取了一部分显示出来。

对用户来说几乎无感。悄悄的,你就把事情给办了。🐮

success

表示业务逻辑是否成功。go-sail会根据你调用时设置的code码,自动维护success的值,前端或客户端可以依据此值断言业务逻辑处理是否成功。甚至可以根据这个值决定是渲染绿色的提示弹窗还是黄色或红色的提示弹窗。怎么样,有意思吗?

ts

即timestamp的缩写,表示系统当前毫秒时间戳。我在设计之初考虑到的两点用途:

  • 当前端或客户端需要严格比对服务器时间的时候,例如产品预售倒计时等等。特别是在你的产品是面向全球用户的时候特别实用。
  • 用户定位问题、请求、操作的时间。前端或客户端可以将这个值记录到本地或是直接展示出来,用于协助问题排查。

为什么是毫秒?

如果你是一个Web前端开发者,可能你会抿嘴一笑,是的,Date对象的构造函数接收的就是一个毫秒时间戳:

let date = new Date(ts)

拿来就直接用,不必再思考是不是还要乘以1000,降低了使用时的心智负担。除此之外,毫秒的粒度足够覆盖绝大多数的应用场景了。

data

业务数据实体。这个字段就是装载具体业务逻辑的响应数据,或是数组,或是map,或是结构体或是简单的数字、字符串布尔值,都可以。

需要说明的是,字段名的命名并不重要,重要的是结构明确,思路清晰。跟着博主一起聊完了本文的主要内容,现在感觉如何?欢迎在评论区一起分享。

最后,欢迎大家关注go-sail开源项目:

仓库地址: https://github.com/keepchen/go-sail


转载请注明原文地址:https://blog.keepchen.com/a/talk-about-api-response-structure.html