V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
h82258652
V2EX  ›  程序员

采取 RESTful 风格的 api 是否应该对结果包一层?

  •  
  •   h82258652 ·
    h82258652 · 2019-10-21 23:18:13 +08:00 · 25552 次点击
    这是一个创建于 1858 天前的主题,其中的信息可能已经有所发展或是发生改变。

    RT,今天公司的新项目开始对接,app 端的一看我这接口就吐槽我。让我改成如下这种: { "code": 200, "message": "", "data": xxx }

    但我觉得首先这 code 肯定是多余的,可以直接从 http 状态码里面读取。之前也看过 twitter 的 api,也没有说包一层,200 的话那就直接返回 data 了。 公司项目我就忍忍算了,毕竟人家老员工。但后面有自己项目的话,还是想弄标准一点。不知道一般来说,大家是怎样实现?

    305 条回复    2019-10-24 10:47:14 +08:00
    1  2  3  4  
    baiyi
        201
    baiyi  
       2019-10-22 14:46:53 +08:00
    @xingheng #194

    举个可能有些冒犯的例子,但我本身没有恶意:
    脱裤子放屁不会对放屁的结果造成影响,不代表这就是对的。同理,就算框架可以过滤,也不太代表每次错误都返回无用的参数是正确的。

    关于 ios 的是几年前的经验了,只是个提醒,现在的主流框架都支持了也是好事
    ipiao
        202
    ipiao  
       2019-10-22 14:48:07 +08:00   ❤️ 1
    算了,直接开喷吧,一群 xx
    simo
        203
    simo  
       2019-10-22 14:49:43 +08:00
    没有业内强制、标准的东西,就这样了?
    规范,可以有限定条件的,一个公司,一个业务,甚至一次都可以做个规范,没有坏处,遵守就好了,有什么纠结的呢?
    看来自由这点事儿,还是要有点门槛才好
    ipiao
        204
    ipiao  
       2019-10-22 14:50:24 +08:00
    直接开喷吧,不要和 xx 争论,没用
    markgor
        205
    markgor  
       2019-10-22 14:53:50 +08:00
    @binux 忘記 @你了,不好意思
    googlepay 很明显就是我说的成功直接返回 object
    上面哪個人說的不是返回 object,上面都是在討論返回的 object 是否需要包含成功代碼,還是說直接通過 http 的 200 表示成功。建議你話 1-2 分鐘看看上面針輪的話題和 LZ 的提問。

    “业务请求成功 body 会有内容啊,为什么要区分网络请求成功,然后业务请求失败?你拿不到预期的内容那就是失败。

    失敗時候的處理方式不一樣,
    最簡單的例子,網絡失敗隔 10 分鐘重試,業務失敗檢查自身提交業務數據。如果業務失敗也是按網絡失敗處理,那你不就等於叫別人 ddos 你嗎?

    “失败就去解析失败 object 就好了啊,你自己的例子已经给你示范了,你无法理解吗?”
    建議你話 1-2 分鐘看看上面針輪的話題和 LZ 的提問。

    “bat 自己连个全站 HTTPS 都做不到,他们的规范你还要去学?”
    總有人覺得自己比 bat 都厲害,但卻幹不掉 bat。

    “再者我供职的公司压根就没有面向中国市场的业务啊。”
    那我沒什麼好說的了。
    lijialong1313
        206
    lijialong1313  
       2019-10-22 14:55:16 +08:00
    这个东西的话,说实话一般取决于项目。

    我个人是倾向于带上 code 的,但是这个 code 是带上一些详细错误信息,比如某个数据出现什么问题等等。

    其实 http 状态码还有小数,503.1、503.2 都有,但是有可能一来不适合用于当前项目,二来这个可能不够全。

    就比如说,如果我们希望表达一个“页面到底”的标识符,但是查询结果有可能为空,那在 http code 里面挑一个多少比较合适呢?
    ArJun
        207
    ArJun  
       2019-10-22 15:00:34 +08:00
    真的很多公司都不返回状态码的,这是什么设计?
    真的就是所谓的屁 restful?
    binux
        208
    binux  
       2019-10-22 15:01:03 +08:00
    @markgor #199
    #198 “那么多开源接口{code:0,data:[],msg:""}这种格式不是没有道理的,不要想当然” 就不是“直接”返回 object
    问题是你收到 200 但是内容不是正确的业务 respond,这也不是网络失败啊。换句话说,判断网络异常根本就不需要 code 啊。

    是的呢,bat 很厉害,也没见他们干掉 flag 啊。
    galikeoy
        209
    galikeoy  
       2019-10-22 15:08:14 +08:00
    @h82258652 #63 包多一层挺好啊,结构统一,我特么还要判断 http 码来分别用两个结构?
    zhybb2010
        210
    zhybb2010  
       2019-10-22 15:10:14 +08:00
    http code 和 业务 code 不是应该融入 APIer 的血液么 ???
    markgor
        211
    markgor  
       2019-10-22 15:14:00 +08:00   ❤️ 1
    @binux
    你意思是說,無論成功還是失敗,http code 都是返回 200 ?
    看看上面討論的內容,說的是錯誤碼複用 http code。既然是複用了,談何都返回 200 看 response ?

    然後你參考 #209 的回復,你們都是女裝大佬,應該容易接受吧。

    最後,我承認 Google 和 IBM 的偉大和技術,但這不意味著我看不起 AT。
    除了百度&360 一切巨人都值得尊重。
    cowcomic
        212
    cowcomic  
       2019-10-22 15:19:52 +08:00
    我觉得这个得分场景
    如果一个服务接口内部的处理全都是事务性的,那就可以不包一层,因为返回码指代的状态是唯一的
    但如果一个服务接口内部无法做到完全事务性,那就需要包一层,因为这时候返回码指代的状态不是唯一的,需要有另一个码来区分这时系统的状态
    passerbytiny
        213
    passerbytiny  
       2019-10-22 15:20:52 +08:00
    @xuanbg #172 2XX 全部是正常返回,3XX 虽然不是返回内容但也是正常返回,网络问题一般是 4XX。服务器本身问题,包括业务逻辑问题,HTTP 状态码是 500 或者 5XX。你那种要么 200 要么无返回的接口,并不是 RESTful 风格的接口。
    index90
        214
    index90  
       2019-10-22 15:21:27 +08:00
    @l00t 所谓的错误处理,也就是 error recover,你无非做两件事。
    1. 这个是你能处理的错误,记录日志,继续你的逻辑。
    2. 这个你不能处理的错误,要么透传,要么收敛。

    只有第一种你是需要断言错误的,这种情况十分的少,单靠返回码就能处理的更少,那么额外增加返回码的价值就更少了。

    第二种,是你做得最多的,透传就不用说了。收敛的话,以登录接口举例,你可能先查询用户信息,然后验证密码,然后查询权限,等等。你接收到下游返回的错误情况有可能是用户不存在 404,密码错误 406,无权限 403 等等。你会将这些错误收敛为一个 403,并在 msg 中说明是哪一步出错了。

    需要自定义返回码,并大量使用的公司,个人觉得有以下可能:
    1. 接口定义得“太大”,即参数特别多,例如把登录和退出都做在一个接口里。
    2. 不能“有效”地处理错误,大部分人截获到错误后,并没有做 recover 的操作,也没有做收敛的操作,而是转换成另一个他所理解的错误,再返回。这种“扩散”错误的做法,会给系统带来更大的复杂度,降低开发者处理错误的意愿,但带来的收益却很低。
    简单来说,就是你给系统增加了 90%的麻烦,去处理你 10%的情况。
    shangfabao
        215
    shangfabao  
       2019-10-22 15:27:22 +08:00
    你先看看一些开放的 API 人家事怎么设计的?出错了就没有数据就完了?
    ABenmao
        216
    ABenmao  
       2019-10-22 15:29:09 +08:00   ❤️ 1
    哎,你还是太年轻啊,我之前运维的那套核心交易系统,返回的 code 是 8 位数的,因为客户量很大,在进行排查错误的时候,总不能用 message 去排查吧,往往是客户只需要说一下什么动作,返回状态码是啥,我就能很快的定位到问题,最起码能定位一个范围,如果只靠 HTTP CODE 或者 message,运维人员和后面接受的开发人员会骂娘的
    index90
        217
    index90  
       2019-10-22 15:37:12 +08:00
    @ABenmao 你这个场景里的 code 实际上是 error detail,你的程序不会去断言这个 code 做出相应的动作,而是透传到前端。最后由人捕获去排查问题。这种 code 的设计目的和 http 状态码的目的是不同的。这种 code 一般头几位代表系统或服务编号,中间几位代表模块,最后几位可能代表代码行或者顺序码。
    shadeofgod
        218
    shadeofgod  
       2019-10-22 15:37:31 +08:00
    这个问题貌似每一段时间就要冒出来一次。。
    binux
        219
    binux  
       2019-10-22 15:41:26 +08:00
    @markgor #211 不,失败复用最接近的 http code,不返回 200,然后在 body 中返回 error object,在 error object 中你可以再带一个业务的 code,也可以是 string 的 error type。
    200 就是成功,其他的就是失败,具体怎么处理失败就去解 body 中的 error type。 #1 那个文章写得多好,为什么就不看呢。
    l00t
        220
    l00t  
       2019-10-22 15:47:04 +08:00
    @index90 #214 你这个分法我就不认同,非常混乱。收敛和透传难道就不是错误处理么?

    错误分两种:可预期的和不可预期的。可预期的错误可以跟随相应的业务操作,不可预期的错误要么记个日志忽略要么透传出去。这些都取决于具体业务逻辑怎么设计。但是要做到这些操作的第一步是你得能识别错误。怎么识别?错误号啊。不通过错误号反而通过错误信息去做字符串匹配,这是什么思路…… 而且大多数报错信息的设计里都是既有错误号又有错误信息字符串的,你要是只想返回个字符串给人看也一点障碍都没有。
    Ianchen
        221
    Ianchen  
       2019-10-22 15:47:54 +08:00   ❤️ 1
    @ABenmao #216 我给上家公司设计的错误码就是这种思路, 程序员知道错误码后能快速的定位到问题, 每个系统, 模块结合起来, 再加上特定的模块里的业务逻辑再定 3-4 位错误码, 最终设计出来的错误码直接飙到 7-8 位错误码. 结果后期要返回错误码的时候,需要找哪个错误码已经被用过了, 维护错误码又是一个重复的工作量. 我觉得这种设计不美观, 复杂, 繁琐, 不优雅. 后来我就根据逻辑处理来设计错误代码, 省去大量增长的错误代码查找工作. 与其让业务去做这些复杂重复的工作, 我为何不写个能跟踪问题所在的日志记录系统呢? 框架有存在意义就是帮程序员节省重复的劳动力, 专心做业务, 想往技术发展的程序员自然会去研究框架的实现技术.
    ABenmao
        222
    ABenmao  
       2019-10-22 15:49:16 +08:00
    @index90 意思差不多,其实我就是想说,真实的项目,尤其是卖钱的,都要考虑如何快速排查问题,而不直接返回 404,返回的是尽可能有用的信息,是这个问题最便捷的处理方法,所以很多 API 都会说明,httpcode 是 200 不代表完整正确,还得看里面一层的 code
    konakona
        223
    konakona  
       2019-10-22 15:50:56 +08:00   ❤️ 1
    这有什么好争论的?
    你们的唯一论是不是有点夸张了?

    在 http 状态值没有办法全面干掉 code 自定义状态值之前,code 自定义状态值就有其存在的场景和优势!

    在一个复杂的表单验证模块里,模块已经拆分的很细,但仍然有十几个 field 要提交,每一个错误原因都用一个 http 状态值代表?够用?

    还是你要说前后分离,条件判断是前端的事儿,后端只用一个状态值代表成功,一个状态值代表失败,一个状态值代表未授权。

    完了?= = 场景啊!场景!!!当接口一次处理的业务足够复杂,排错也是很重要的,难道前端要关心你后端的单元测试用例通过了没有?他也关心自己提交的数据为什么不通过!

    场景啊!!!
    ABenmao
        224
    ABenmao  
       2019-10-22 15:54:16 +08:00   ❤️ 2
    @Ianchen 日志?我说的这套系统光可见的配置参数就有上万个,出现问题,我们要求三分钟内要定位出原因,这时候看日志?等找到日志,客户估计已经拿着菜刀站在我背后了。
    维护错误码这个任务不是人工完成的,肯定要有一套对应的系统啊,任何一个开发用一个错误码之前,都要通过这套系统去查下有没有相同意思的 code,如果没有,申请新加(有专门的配置管理部去审查),而且错误 code 是有构成规则的,不是递增的,就像你描述的那样
    markgor
        225
    markgor  
       2019-10-22 15:54:19 +08:00
    @binux #1 的剛去看了
    其中有一段指出“
    There are 2 situations where an envelope is really needed - if the API needs to support cross domain requests over JSONP or if the client is incapable of working with HTTP headers.

    JSONP requests come with an additional query parameter (usually named callback or jsonp) representing the name of the callback function. If this parameter is present, the API should switch to a full envelope mode where it always responds with a 200 HTTP status code and passes the real status code in the JSON payload. Any additional HTTP headers that would have been passed alongside the response should be mapped to JSON fields, like so:

    兩種特殊情況會使用到 code。JSONP 和無法獲取 header 的情況下,
    如果你原生就不支持攜帶業務 code,那遇到前端跨域 jsonp 請求時,你再作處理?
    GopherTT
        226
    GopherTT  
       2019-10-22 16:00:49 +08:00
    楼主以为返回的 code 是 http 的 status 才会觉得没必要吧 实际上 body 的 code 是针对业务返回的错误码
    Ianchen
        227
    Ianchen  
       2019-10-22 16:07:29 +08:00
    @ABenmao 也许吧, 哈哈. 如果我站在你的角度上, 我可能还会用这种思路去设计.
    我比较倾向尽量少或者无人力去审查这些, 因为之前设计的错误码也是让专人去审查的. 我觉得这些时间与人力可以用在其他地方上. 还是让场景来决定设计吧.
    littlespider89
        228
    littlespider89  
       2019-10-22 16:09:07 +08:00
    去看看 github,gitlab,docker,k8s,cloudfoundry,amazon s3,哪家的 API 在 200 的时候会包一层 code ?
    再看看 oauth2 标准,401,403 这些定义的明明白白,为什么人家不全部返回 200,再里面包一层?你们用 spring security 的时候是不是还要自己再把它的结果包一层?
    包一层,还不是 if result.code == 200: xxxx else: alert(result.msg),给了那么多的 code,确定每个 code 你们都会去处理一下?用你 API 的人有功夫去一个个研究你们自定义的 code ?
    xaoduer
        229
    xaoduer  
       2019-10-22 16:26:19 +08:00
    业务 code 是个好东西!服务端业务一复杂就体现出来了,比如说 A 请求 B,B 请求 C,D,E 服务。。B 和 CDE 之间发生了 500,404 你是该返回给 A 什么呢
    hantsy
        230
    hantsy  
       2019-10-22 16:29:23 +08:00
    @littlespider89 不光是包装一层 Code 的问题。

    一路看下来,大部分人的公司定 API 标准基本是上看前端和后端人员相互扯蛋的结果(只要能用),而不是基于国际标准和约定。 有些人,不管是错误( 4xx,5xx )还是正确都是返回 200 HTTP CODE (全部是正确的),这种简直超出我的想像,然后包装结果加一层,根本没有正确的使用 HTTP 协议。
    neverfall
        231
    neverfall  
       2019-10-22 16:29:43 +08:00
    @h82258652 我的意思成功的时候不是这样。
    例如 GET /person/1
    失败
    {
    "code": 400,
    "msg": "xxx",
    }
    成功
    {
    "id": 123,
    "name": "xxx"
    }

    你回复的,成功还是相当于包了一层。

    如果以上是你原始定义格式的话,
    那前端跟你合作真是会头疼死。打个比方如果成功里有个值是 code 你们前端要怎么办?
    {
    "code":200,
    "msg":"msg",
    "data":{},
    }
    任何情况下都按上面这个格式返回基本上已经是最佳实践了。

    我前后端都写,你们的前端定义格式是比较流行的,可以完成绝大多数情况的状态。
    hantsy
        232
    hantsy  
       2019-10-22 16:37:27 +08:00
    @neverfall 首先应该判断 HTTP Code 啊,这个在现代框架处理中太容易了,不管 HTTP 操作结果是 Promise,还是基于 Rxjs,正确和错误结果是不同的路径。

    我完全不明白前端开发要加 Code 可以省事。
    719465553
        233
    719465553  
       2019-10-22 16:41:02 +08:00
    code 主要时业务用的,我知道有些人说可以用直接用 errMessage 判断,从客户端这边来讲,我们只判断错误类型,errMessage 是用来提示用户的,比 code!=200 直接就是失败,吐司给用看,还有都是登录失败的情况,可能不同的页面有不同的文案( token 失效,密码错误等等),但是我们这边只需要知道比如登陆失败 code=-1,具体原因你后台可以随意改,如果用 errMessage 就比较被动,服务器改了,客户端不支持啊
    neverfall
        234
    neverfall  
       2019-10-22 16:44:53 +08:00
    @hantsy httpStatusCode 是网络底层框架判断使用的,跟业务无关。业务不关心 httpStatusCode, 业务只关心请求是否符合预期,不符合预期的话,是哪里错了,如何进一步处理。
    neverfall
        235
    neverfall  
       2019-10-22 16:46:56 +08:00
    @hantsy 一般对外提供接口,不管是不是给前端,code,msg,data 这样的格式基本上已经是现阶段比较好的一种实践。
    rpc 之类的接口除外。
    binux
        236
    binux  
       2019-10-22 16:52:24 +08:00
    @markgor #224 我一直都说,错误有错误的 object,成功有成功的 object,你错误带 code 没问题,成功带什么 code 啊。
    hantsy
        237
    hantsy  
       2019-10-22 16:57:01 +08:00
    @neverfall 那是你自己的实践,不要绑架所有人。

    设计 API 我只会遵从标准和约定,力求做到简约(可读性,复用性),而不是为了满足某些人下意识的偷懒(说白了一样的,都是知识实践缺乏)。

    在我开始刚接触 REST 设计到现在有 7,8 年的时间,从来没这么用过。见到这种东西我真的感到恶心。
    nyjy997
        238
    nyjy997  
       2019-10-22 17:01:58 +08:00
    就一个问题,你考虑过业务失败的情况么,比如请求成功,参数校验失败.这到底算什么情况.
    neverfall
        239
    neverfall  
       2019-10-22 17:04:37 +08:00
    @hantsy 看了你之前的回复,真是教条主义,什么 restful 之父,什么国际大厂,http 协议啥的,没看出来你是做了七八年了,不知道的还以为你刚毕业呢。 就你说的那几个标准,简约,可读性,复用性,你如何实现,你平时如何设计的,列出来让大家瞅瞅是啥样的。
    neverfall
        240
    neverfall  
       2019-10-22 17:06:24 +08:00
    @hantsy 已 block 眼不见为净,这样就不恶心了。
    hantsy
        241
    hantsy  
       2019-10-22 17:06:57 +08:00
    @neverfall 有那个能力的话,应该可以从 V 站看到我 Github,上面有一些 API,不多。道不同,Block。
    sm0king
        242
    sm0king  
       2019-10-22 17:10:15 +08:00
    @cowcomic 这个,其实很容易被用成,随心所欲·~~ 对某一个东西的理解可能会有分歧,然后就会增加沟通成本,另外如果没同意规范的话,会造成负担,无法统一处理。
    yamedie
        243
    yamedie  
       2019-10-22 17:20:08 +08:00
    一个简单的问题, 这么鲜明的对立, 这个帖子算不算后端娱乐圈?
    zgray
        244
    zgray  
       2019-10-22 17:28:05 +08:00   ❤️ 1
    我认为 http 的状态码表示的往往是 http 请求的状态情况,比如 301、302、403、404、500 等等,指的是服务器状态,非业务状态。
    我目前对 http 状态码是这样思考的:
    1. 对于所有的业务请求,首先如果能响应,则返回 200 码应该是认为请求成功(可能业务出错)
    2. 然后在保证了正确响应(200)的情况下,对于业务错误和正确响应是否要统一格式输出的话这个我认为这个是不同团队的处理模式的习惯问题。

    我先说下统一格式为{code: xx, msg: xx, data: xx} 这种的:
    一般这种的格式,在 API 调用方很清晰,拿到 HTTP 请求的响应后二话不说直接转为一个统一个 ApiResult 结构,然后判断 code,如果非 0,拿 msg 进行提示。这个思路对于大多数前端很直白。

    对于另一种情况,成功业务返回 data,异常业务返回 errorCode 的:
    那么规则就成了先把结果转 JSON,然后判断是否存在 errorCode 属性,存在认为有错那 msg 提示,否则正常业务。

    上述两种模式的第二种存在很明显的问题,就是:要求 data 区域返回值不能有 errorCode。因此对于这种模式一般要区分状态码,比如非 200 表示业务报错,然后辅助判断。我认为楼主说的是这种模式。

    这里我觉得两个都行,只是第一个模式思路对大多数人来说很直白而已。第二种模式的话如果 API 调用方的开发环境可控问题也不大。但是!!!


    对,我这里要说一个但是!!!微信下开发,那坑爹的安卓版微信浏览器,会默认采用代理发请求,当服务端返回非 200 的响应码时,微信代理抛弃请求,一样的请求从客户端直连二次请求发起!!!
    一个案例就是:用户注册,输入用户名服务器判断是否重复注册,此时 ajax 返回非 200 错误比如(500),然后 response 是错误信息,服务器会二次收到重复的用户注册请求!!!这个坑我相信做个微信开发的人大多数都遇到过。

    也是这个坑踩完我重新思考了下状态码的问题,于是就有了第一种方案的结论。
    Uyuhz
        245
    Uyuhz  
       2019-10-22 17:43:04 +08:00
    说到这里我就想吐槽最近客串小程序开发遇到的。比如某个操作需要根据错误内容区分后续的处理方式,但是后端只返回了 errorMsg,然后我就只能每次用一长串 errorMsg 判断...

    前面有人说的前端只需要提示 errorMsg 不需要后续处理啥的也太绝对了吧...比如未支付订单跳转支付啥的?未登录跳转登录?
    Qinmei
        246
    Qinmei  
       2019-10-22 17:52:37 +08:00
    传业务 code 的一个好处就是前端做语言切换, 说不包的, 那多语言的页面你们怎么报错? 所有的页面前端直接弹窗中文错误?
    lpreterite
        247
    lpreterite  
       2019-10-22 17:54:24 +08:00
    当然包一遍,我甚至都封装成工具了,最近在改版 2.0,欢迎来看看吐槽文档: https://github.com/lpreterite/datagent
    MisakaMikoto
        248
    MisakaMikoto  
       2019-10-22 18:00:20 +08:00
    http 的状态用于请求和响应过程的状态,而不是业务的状态。
    adamzhuang
        249
    adamzhuang  
       2019-10-22 18:23:12 +08:00
    @Qinmei 我们错误 message 弄成 key,请求我们的多语言系统
    ratazzi
        250
    ratazzi  
       2019-10-22 18:37:52 +08:00
    包一层没问题,关键 HTTP status 不要乱用,该 500 就 500,不要包了一层全是 HTTP 200,里面又是报错
    charseer
        251
    charseer  
       2019-10-22 19:36:35 +08:00
    建议面试考考这些,很容易就把只刷题和只经过培训包装的过滤掉了。
    sevenzhou1218
        252
    sevenzhou1218  
       2019-10-22 19:37:59 +08:00
    个人觉得包一层 code 相对较好,主要是针对一些业务状态。
    a15819620038
        253
    a15819620038  
       2019-10-22 19:44:50 +08:00
    成功:HTTP Status + {"orderid": "xxxx"}
    失败:HTTP Status + {"error": "60XX", "error_description": "", "details": [{"xxx": "xxx"}]}
    hantsy
        254
    hantsy  
       2019-10-22 20:12:14 +08:00
    @h82258652

    突然想到以前有一个很早的讨论,希望对你有用。
    https://www.v2ex.com/t/221304#reply54
    CRonaldo9399
        255
    CRonaldo9399  
       2019-10-22 20:37:35 +08:00
    包一层自己的业务 code 好
    ayonel
        256
    ayonel  
       2019-10-22 20:42:24 +08:00
    大公司内部,对 http status 在 nginx 层就会做一层处理,如果 404,500 直接抛统一的出错页面。如果你用 http status 来表示业务逻辑,你的 404,500 咋办。所有好的做法是不管成功出错,统一返回 http status=200, 再根据返回 body 中的 code 作为业务出错的具体代号。
    cdwyd
        257
    cdwyd  
       2019-10-22 20:48:16 +08:00
    强行上 RESTful 自己难受,调用的人也别扭。
    比起来 get put delete 的请求方法还没有用 get_xxx update_xxx delete_xxx 来的直观
    JerryCha
        258
    JerryCha  
       2019-10-22 20:52:03 +08:00
    咱就是懒得查 header 而已
    yinzhili
        259
    yinzhili  
       2019-10-22 21:00:19 +08:00 via Android
    这个 code 应该是用来放业务返回码的,如果是业务较多较复杂的系统,那么就非常必要。我经历的很多个系统也是这么设计的。实际上对于大部分这类业务系统来说,那些个标准的 http 状态码远远不够用。
    举个例子:客户端拿到了 500 错误码,你如何知道是 nginx 反向代理出了问题还是你的后端接口报错?
    duan602728596
        260
    duan602728596  
       2019-10-22 21:05:13 +08:00 via iPhone
    你不给我 code,我就敢 try catch,只提示有错误,至于错误是啥,谁知道呢,自己去慢慢查去吧
    run27017
        261
    run27017  
       2019-10-22 22:55:39 +08:00
    最开始我是直接返回数据对象的,如返回单个 user:

    ```json
    {
    "name": "Jim",
    "age": 18,
    ...
    }
    ```

    返回 user 集合:

    ```json
    [
    { // user 1 },
    { // user 2 },
    ....
    ]
    ```

    后来需要返回集合的时候附上一个`total`字段,用于指导客户端的翻页。没办法我只能将集合资源的返回改为:

    ```json
    {
    "users": [
    ....
    ],
    "total": 163
    }
    ```

    前端做了很大的改动来适应这里的变化。

    这时集合资源成为包装的了,而单个资源没有包装。这样我认为不一致,所以我让单个资源也成为包装的了:

    ```json
    {
    "user": {
    ...
    }
    }
    ```

    最后,为了统一性,我将请求值也变为包装的了:

    ```bash
    curl -XPUT /user -d '
    {
    "user": {
    ...
    }
    }'
    ```

    所以,如果你认为 API 是否应该对结果包装一层,我的答案始终是:**要**. 而且,不仅仅是返回值,我对请求都会主动地包装一层。

    包装对于之后的扩展带来很大的便利。试想一下,如果我一开始就选择包装的方案,那么当我要加入`total`这个字段的时候,影响就很小。

    我这里的包装,可能跟楼主说的不是一个意思。对于`{ "code": 200, "message": "", "data": xxx }`这样的格式,我不是太理解。如果接口返回错误,我的格式一般都统一成:

    ```json
    {
    "code": "一个字符串代表的错误码"
    "message": "错误信息",
    "details": {
    // 可能用一个对象给出详细的错误描述
    }
    }
    ```

    这里注意的是,`code`值最好用简短的字符串,而不是数字,更有语义性。

    顺便夹带点私货,是我之前写的有关 API 设计的:[聊聊 Web 接口设计和接口行为 ]( https://ruby-china.org/topics/39115)。
    watzds
        262
    watzds  
       2019-10-22 23:54:18 +08:00 via Android
    早晚要包装一层,比如 spring 的 ResponseEntity,何况 code 不只是 http 状态码范围
    no1xsyzy
        263
    no1xsyzy  
       2019-10-23 01:50:25 +08:00
    @eason1874 根据你给的链接可以轻易看出:成功访问不需要 Error Code ( 200 OK 或 304 Not Modified )
    这就可以引另一个名言了:幸福的家庭有相似的幸福,不幸的家庭有不同的不幸
    成功就是成功,没必要在成功的时候表示自定义的 Code。

    另一方面,Twitter API 的形态明显不是 “包一层” 而是 “按需嵌一条”。
    no1xsyzy
        264
    no1xsyzy  
       2019-10-23 01:54:50 +08:00
    @markgor
    > 總有人覺得自己比 bat 都厲害,但卻幹不掉 bat。
    没什么比用政治手段赢过一场赢不了的争论更让人舒服的事了,但那样并不会得到真理。—— 我忘了是 PG 还是 ESR 还是 AJS 说的了
    另外,我没钱,你给我足量的钱,我能分分钟干掉 BAT
    no1xsyzy
        265
    no1xsyzy  
       2019-10-23 02:11:46 +08:00
    @run27017
    > 这里注意的是,`code`值最好用简短的字符串,而不是数字,更有语义性。
    这点有待商榷,因为 “简短的字符串” 是模糊的。太短的话和数字同样没有语义性;太长的话不如直接叫做 Message。
    这一点上,我认为传统 C 语言的 “数字+常量定义” 会更好,同时代码规范上不允许 code 和数字字面量比较,而只能跟常量比较。
    就像是 SIGINT 或者 SIGKILL ?
    nvkou
        266
    nvkou  
       2019-10-23 02:32:01 +08:00 via Android
    @Qinmei 传的是报错的 key 啊。对应多语言具体字符串不是前端多语言的事情?
    no1xsyzy
        267
    no1xsyzy  
       2019-10-23 02:44:06 +08:00
    成功无自定义码,失败带码有这么难以理解吗?
    以 TypeScript 写定义:

    Interface Result {...}
    Interface Error {errno:number, msg: string}
    type APIResponseBodyParsedObject = Error | Result

    这有什么难以理解的吗?

    函数式 Left | Right,请
    eason1874
        268
    eason1874  
       2019-10-23 02:44:32 +08:00
    @no1xsyzy #263 是我误解了,以为楼主跟第一页有的评论一样认为 HTTP 状态码够用了无需自定义 code,我在#47 解释了。成功返回要不要 code 我觉得都可以,这个看内部约定。
    no1xsyzy
        269
    no1xsyzy  
       2019-10-23 02:52:31 +08:00
    @ABenmao #216 我觉得照你这么来,错误码直接返回 __FILE__:__LINE__ 不是更快定位?根本不需要查表。
    有需要附个 commit hash,直接 git checkout 过去 ^T __FILE__ <cr> ^G __LINE__ <cr> 就是了,定位原因甚至不需要三秒。
    那 Springboot 或者 Flask 等都是不需要任何处理,直接让这个请求 Fail,返回的立马就是带 Traceback 的,岂不是最强?
    cassyfar
        270
    cassyfar  
       2019-10-23 03:17:12 +08:00
    @markgor 没法接受成功里不包 code 是因为你没有见过这种设计吧?大厂里还是很主流的。
    nvkou
        271
    nvkou  
       2019-10-23 03:22:34 +08:00
    看的我火气都出来了.
    第一个问题: API 的粒度. 粗了状态太多,细了浪费网络.这个暂且不表. 但一个好的 API 应该尽量减少人工干预.尽量做到原子性能帮到很大忙.

    第二个问题:自己的看法
    http 状态码很多地方是框架的事情不是你业务的事情.但框架都是给你留好处理接口的. 包一层统一判断处理简单来说就是懒或者技术不行.

    答某些老哥的疑问:
    状态码不够用? 前面的老哥都说了,没人阻止你返回 480, 599 甚至 999. 看你怎么去处理而已.要真想判断是网络 404 还是业务 404.请遵循规范发 OPTIONS 或者看 msg
    什么请求成功,业务失败.不还是失败? 失败该干什么事情是你们设计文档的问题了. 有些公司用 JWT 鉴权的.登陆超时返回 403 时框架自动重登陆再重试多舒服?还要你业务上判断没有预期结果再重试?

    优雅的结构能跑业务,屎山也能跑业务
    hantsy
        272
    hantsy  
       2019-10-23 07:28:21 +08:00
    @run27017

    > 这里注意的是,`code`值最好用简短的字符串,而不是数字,更有语义性。
    类似方法我也使用,https://github.com/hantsy/angular-spring-reactive-sample/blob/master/server/src/main/java/com/example/demo/RestExceptionHandler.java

    错误信息( Http Status Code 为 4XX,5XX ) Response Body 用统一的格式没有什么问题, Spring hateoas 中的错误处理遵循一个 Error API 的 Draft。为了更好的描述问题,可能要考虑很多,比如国际化,开发人员详细信息 Exception Stack 信息等)。

    但是,我是没法认同很多人所说的所有返回结果都是返回 Http Status 200,然后用{code,message}包装,还说什么业务 CODE 云云。

    至于你说的 Collection 分布包装,这种几乎是有标准了,Spring HATEOAS 1.0 之前( 1.0 刚发布,变化大,加入其他几个标准)是遵循 HAL,其中也有定义 embedded,来包装数据 。

    当 API 能够到达 Richardson Mature Model Level 3 ( Self documation ), 比如遵循 HAL 等去描述的话,几乎不需要额外文档说胆 API 了。
    markgor
        273
    markgor  
       2019-10-23 09:00:38 +08:00
    @binux #236 請看清楚在回復。
    @cassyfar 建議你看看上面的回復再來說主流的這個問題吧。
    martyyyyy
        274
    martyyyyy  
       2019-10-23 09:03:16 +08:00
    @loading 赞同,业务错误千千万,http 错误码根本不够区分,链接只要是通的,都是 200,业务是业务的事情,还是分开好。
    binux
        275
    binux  
       2019-10-23 09:09:45 +08:00
    @markgor #267 你是不是没看懂英文,人家说的 jsonp 的使用场景信封里包的 code 是指 http status code,根本不需要你业务支持啊。
    assad
        276
    assad  
       2019-10-23 09:13:05 +08:00
    还再杠。翻个页
    ganbuliao
        277
    ganbuliao  
       2019-10-23 09:13:05 +08:00
    自己的项目还是按照喜好来吧 国内的行情是 每个公司都会有一套自己的标准
    jayklx
        278
    jayklx  
       2019-10-23 09:16:07 +08:00 via iPhone
    @Narcissu5 对于存在复杂链路调用的分布式系统来说,业务错误码是排查问题的重要手段,你说的只是应用层面的监控,大厂都有业务监控
    markgor
        279
    markgor  
       2019-10-23 09:18:19 +08:00
    @binux
    強扳貼臉就沒意思了,
    你自己去看清楚再來噴吧,
    人家說這模式下 http code 永遠返回 200。所以在 response 裡包個 code。
    你非要強行說包這個 code 是 http code 那我也沒辦法。
    就好像上面所說的,其實你也可以繼續貼臉啊,
    你可以說人家成功的包裹的都是 http code,而不是業務 code 啊。
    markgor
        280
    markgor  
       2019-10-23 09:21:21 +08:00
    @binux 剛又看了一次,是我看錯了,裡面的確是寫通過 response 來傳遞 http code。
    sayhello1991
        281
    sayhello1991  
       2019-10-23 09:21:44 +08:00
    不是个技术问题
    大家在马路上行车要么都靠左, 要么都靠右,
    (虽然是新项目, 但 APP 端用的可能是公司统一封装的请求解析库,规范就是那个格式的)
    i18ns
        282
    i18ns  
       2019-10-23 09:34:50 +08:00
    封装 code 的这种结构:{ "code": xx, "message": "xx", "data": xxx }
    好处是可以自定义错误码,在业务复杂时,能够清晰具体的区分不同的错误类型。
    坏处是自定义的这些东西不通用,增加接口复杂性。一般老外比较习惯于用 http status code 来表示错误。曾经设计了一个接口是以{ "code": xx, "message": "xx", "data": xxx } 的方式来返回信息的,但在 reddit 被怼到不行,索性改成了 http status code 的方式来返回错误信息。其实一个开发者应该无所固执,毕竟你的目的是服务好你的用户。
    smallCoder
        283
    smallCoder  
       2019-10-23 09:37:52 +08:00
    我个人觉得 http code 能满足大部分场景的状态码了,正常就返回 200 和 data 数据即可。如有特殊跳转的业务 code,在 data 里多返回一个业务 code 就行
    back0893
        284
    back0893  
       2019-10-23 09:41:02 +08:00
    喷就完事了
    朱军我喜欢战争
    Z1076
        285
    Z1076  
       2019-10-23 10:36:39 +08:00 via Android
    应该是没经过项目需求频繁变更毒打的小伙(´;︵;`)
    noahwei
        286
    noahwei  
       2019-10-23 13:58:15 +08:00
    孤陋寡闻了,居然有这么多不包一层的开发者,我经历过的项目光靠 http code 是没法满足需求,但这种事情不能讲究一刀切,优雅有优雅的场景,复杂有复杂的必要,看戏。
    icerhe
        287
    icerhe  
       2019-10-23 14:32:13 +08:00
    1.http code 根本不足以描述千奇百怪的业务状态
    2.http code 本身就不是被设计用来描述业务的. REST 里用 http code 来代替业务状态是错误的
    icris
        288
    icris  
       2019-10-23 14:56:59 +08:00
    @no1xsyzy #267
    难以理解。
    简单举例,简单分页请求,类型 Error | [{}],无数据或页码超出的时候返回应该是什么样? status code 404 (或者 499 ) ?那就有失败的网络请求走到正常的显示流程这种难以理解的情况了。
    类型 Error | { code?:number, data:[{}] } 的话,直接合并成 { code?:number, msg?: string, data: T } 显然更方便,code msg 又不是不能不返回,「成功无自定义码,失败带码」。
    ql562482472
        289
    ql562482472  
       2019-10-23 15:13:53 +08:00
    200 时不包装,非 2xx 时才返回
    {
    code,data,msg
    }
    ql562482472
        290
    ql562482472  
       2019-10-23 15:15:26 +08:00
    @galikeoy 大多数前端框架里,遇到 5xx,是做抛异常处理的,所以当后端返回 2xx 时,一定是正常流程,非 2xx 时,再做包裹
    galikeoy
        291
    galikeoy  
       2019-10-23 15:21:47 +08:00
    @ql562482472 #290 我们是用自己的逻辑,自己一套状态码,所以统一处理,没什么好说的
    zeyexe
        292
    zeyexe  
       2019-10-23 15:32:24 +08:00
    我算是看明白了,这里很多人根本就不打算在前端处理 HTTP Status Code。

    他们认为全部都可以放到 200 下处理,正常按 Restful 风格的应该放到 401、403 的错误也放 200 下处理。但是 5xx 错误你们怎么办呢,5xx 错误很可能就没有 HTTP Body,又去哪里看业务 code 呢。话说回来,如果你为处理 5xx 错误开了一个 if 分支了,为什么不能为 4xx 错误开一个 if 分支呢。


    我的做法是这样的:

    API 方面,业务状态正常的放到 HTTP 200 状态码下返回,直接返回数据,不包一层;业务状态异常的放到 HTTP 4xx 状态码下返回,使用固定的数据结构返回,固定的结构包含 code、message 等字段;能处理的 HTTP 5xx 状态一般使用和 4xx 一样的固定结构返回。


    前端方面,收到数据之后线判断 HTTP 状态码,如果是 200,就一切正常处理;如果是 4xx 错误,再根据返回的业务 code 处理,或者直接显示 message,或者根据业务 code 执行预定义逻辑;如果是 5xx 错误,可以尝试读取 HTTP Body 了,如果读取到数据了,就和 4xx 错误一样处理,如果没有读取到数据,就显示默认错误。
    icris
        293
    icris  
       2019-10-23 15:41:21 +08:00
    @zeyexe #292
    前端用的比较多的 axios 的默认处理逻辑,当你写 await axios.get(); 的时候,你的 4xx 5xx 错误是在 catch 里的。想要代码能看,只能不用 await。
    可能有不是这样的,也可能有是这样的,即使不是这样,服务器错误和 503 在一块儿也不像正常逻辑。
    5xx 不需要 if 分支,catch 里直接报网络异常完全没有问题。
    zeyexe
        294
    zeyexe  
       2019-10-23 15:57:05 +08:00 via iPhone
    @icris 4xx 和 200 并没有区别,也是可以像 200 在 catch 里面写逻辑的,5xx 也是多种类型的,有些是 api 系统可以处理的问题,有些不是来自 api 而是来自网关。

    说到底,我的观点是用 http status code 给业务 code 分组,而且这个分组还挺有用的。
    zeyexe
        295
    zeyexe  
       2019-10-23 16:04:48 +08:00 via iPhone
    说什么业务码多 http code 不够,其实 http code 是一种归纳分类,你的业务码最好放到对应的分类下面,我认为这个做法对于 rest 风格很重要。只用 200 状态码的业务最好不要宣称自己是 restful,这可能达不到及格线。
    lepig
        296
    lepig  
       2019-10-23 16:11:41 +08:00
    我觉得都没毛病,code 是很传统的,业界大部分也都这样用过来的。 内部项目就用 code 挺好。你看很多国内的第三方酒店 api 以及微信都有这种业务 code。所以没毛病。

    所以,就是看你能不能干过使用你接口的人....
    icris
        297
    icris  
       2019-10-23 16:41:50 +08:00
    @zeyexe #294
    具体例子,从 axios 拿来一段
    ```
    async function getUser() {
    try {
    const response = await axios.get('/user?ID=12345');
    console.log(response);
    } catch (error) {
    console.error(error);
    }
    }
    ```
    一般的 code msg data 逻辑,业务代码在 console.log(response); 一行,catch 里只有网络异常; status code 逻辑,业务代码在 console.log(response); 和 console.error(error); 两个部分,视情况两个部分可能还有完全相同的处理代码,因为失败不一定只是弹窗提示就完了。
    我是觉得业务代码在 try 内部比较正常,这想法总没错吧。
    zeyexe
        298
    zeyexe  
       2019-10-23 17:00:06 +08:00
    @icris #297 业务代码在 try 内部很正常。区别是我们对于业务代码中失败的请求( code 不是 0 )这部分的理解,我是认为成功的请求应该是 HTTP 200 在前端放到 try 处理,失败的请求是 HTTP 4xx 或者 5xx 在前端放到 catch 或者全局拦截器处理,网关 /网络错误是 HTTP 5xx 放到全局拦截器处理。catch 也不是只能弹窗,try 能做的事情 catch 也能做。
    no1xsyzy
        299
    no1xsyzy  
       2019-10-23 18:22:26 +08:00
    @icris 列表的结束应该有结束指示器,类似 StopIteration,而不是用另外的工具(如状态码、业务代码)表示
    当然这已经是现代语言思维的收束和 JSON 的限制了,实质上对于 partial list,没有触及边界的情况应该返回开界的,并且带有 index 范围,并且可以对同一个 list 的多个 part 进行简单、无错、无序的合并的。同时一个无限延伸的数组(比如推文)同样可以用右侧永远不闭合的 list 来表达。结果 Twitter API 就变成现在这个模样了,Dict[TweetId, TweetContent]。
    这些语言不是为了分布式系统设计的,更不用提连为此设计的 Erlang 都不够适合分布式系统(因为用的人少没有经过充分的实践)。结果 MapReduce 啥的反而符合,但本该采用现有编程语言的(而不是设计一个 DSL ),本该写个 Python 或者 Java 或者 Lua 都能直接编译成 MapReduce 操作的。
    no1xsyzy
        300
    no1xsyzy  
       2019-10-23 19:01:28 +08:00
    @icris 举些例子:(下面不那么符合 .d.ts )
    明示结束:
    Interface T {id:string, name:string, telno?:string, addr?:string}
    type TList = ['start'?, ...T[], 'end'?]
    类似 Twitter 的 partial list:
    type idx = string
    type PartialList = {items: Map<idx, T>, uses: idx[]}
    注意上述 items 甚至可以屏蔽部分对象不再传输一遍,而以 uses 自历史传输中取出。
    另外,其实这个做法 HTTP/2 能够非常高效地支持,只要把这些以 RESTful 的形式拆开传输,先传 uses 然后客户端可以 reset 掉已经传输过的流。

    ——

    然后,下面两个是包含关系,
    Error | { data:T } < { code?:number, msg?: string, data: T }
    我特地分离出来是可以写个 function(obj:APIResponseBodyParsedObject):obj is Error 和 ...:obj is Result 的判断,这样就可以无缝切入函数式的 Left|Right 的错误处理思想(一切对 Left 的运算皆返回 Left 本身,而 Right 则返回对 Right 里面的数据处理后再封装成 monad 的 Right ),虽然其实 JavaScript 内没有这个机制。
    ( BTW 你写的似乎不是 .d.ts ,或者你想表达的正是恰包含一个空对象 {} 的数组)
    1  2  3  4  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1005 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 20:15 · PVG 04:15 · LAX 12:15 · JFK 15:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.