如何优雅的在 Java 方法同时返回 状态码 和 结果 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
binbinyouliiii
V2EX    Java

如何优雅的在 Java 方法同时返回 状态码 和 结果

  •  
  •   binbinyouliiii 2019-07-23 16:24:35 +08:00 7848 次点击
    这是一个创建于 2276 天前的主题,其中的信息可能已经有所发展或是发生改变。

    写 Spring Web 的各位估计都写过返回 JSON 的项目,一般的结构都是这样的

    { "status":200, "msg":"正常" "data":{ ...... } } { "status":201, "msg":"非正常" } 

    按理说 Controller 层不参与业务的处理,顶多校验一下合法性之类的,所以大多数状态码都是交给 Service 层来返回的,可是 Java 的方法只能返回一个结果,想要返回多个结果只能包装成一个对象。但是包装成这样总感觉不舒服,照理说不应该由 service 层包装。

    public Package<User> getUser(){ User user = new User(); if(user.getAge!=10){ //返回非正常 return new Package(201,null); } //返回正常 return new Package(200,user); } 

    我想到的还有一种方式就是用 Exception 来做,直接把状态码放到 Exception 中,非 200 状态的就让 Controller 层 catch 后处理,好处就语法上就会有强制性 catch,但是这样做就会出现遇到 如果有多个正常状态码,Controller 层无法处理的问题:

    public User getUser() throws CustomException { User user = new User(); if(user.getAge!=10){ //返回异常 throw new CustomException(201); } //返回正常 return user; } 

    请教大家平常用什么方法比较优雅。

    chendy
        1
    chendy  
       2019-07-23 16:31:53 +08:00   1
    多种正常错误码的场景是什么样的?
    个人观点是不给 service 层做这个包装,全部 controller 做,或者自己拿 ControllerAdvice 或者自己写 MessageConverter 啥的
    顺便吐槽一下国内就这么不喜欢 http 状态码区分成功失败么
    icebow
        2
    icebow  
       2019-07-23 16:32:12 +08:00
    ResponseEntity?
    beryl
        3
    beryl  
       2019-07-23 16:32:38 +08:00
    抛异常,返回错误码问题!,感觉这是个引战帖
    misaka19000
        4
    misaka19000  
       2019-07-23 16:33:59 +08:00
    切面
    EastLord
        5
    EastLord  
       2019-07-23 16:38:51 +08:00
    problem-spring-web 这个行吗
    qwerthhusn
        6
    qwerthhusn  
       2019-07-23 16:39:25 +08:00   1
    我很早之前做过,就是写一个 ControllerAdvice。将所有的 Controller 返回的数据再包裹一层{"code": "success", "data": Controller 返回的}。如果业务想要返回非 success 的响应,通过抛出一个指定的异常,然后再在 ExceptionHandler 里面捕获。

    但是后来发现对于 REST 接口,为什么要将所有的业务响应再包裹一层呢?
    而且我感觉不少公司都是这么搞的。客户端是根据 HTTP 错误码还是根据 body 中自定义的错误码判断业务正常呢????

    我之前发的一个帖子也顺便提到过这个东西。
    我反正是比较讨厌 REST。


    这个是相关逻辑的代码片段
    ```
    @
    RestControllerAdvice
    @Slf4j
    public class ControllerResponseWrapper implements ResponseBodyAdvice<Object> {
    private static final List<Class<? extends HttpMessageConverter>> PASSED_CONVERTER_TYPES =
    ImmutableList.of(ResourceHttpMessageConverter.class);

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    return !PASSED_CONVERTER_TYPES.contains(converterType);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
    MediaType selectedContentType,
    Class<? extends HttpMessageConverter<?>> selectedConverterType,
    ServerHttpRequest request, ServerHttpResponse response) {
    // 在 Controller 的某个接口方法返回 String 时,
    // 会由 StringHttpMessageConverter 进行 response 写入,而不再是 MappingJackson2HttpMessageConverter
    // 所以预先转好 JSON 返回
    if (body instanceof String) {
    return JsonUtils.writeValueAsString(new WrappedResponse<>(CommonResultCode.SUCCESS.getCode(), null, body));
    }
    // 如果已经包装成了 ResponseWrapper,例如 ExceptionHandler 处理的,则不再处理
    else if (body instanceof ResultCode) {
    return body;
    } else {
    return new WrappedResponse<>(CommonResultCode.SUCCESS.getCode(), null, body);
    }
    }
    }
    ```
    lululau
        7
    lululau  
       2019-07-23 16:47:54 +08:00
    参照 Rails, 这两种方法都是可取的,Rails 是同时支持这两种风格的 API
    Ravenddd
        8
    Ravenddd  
       2019-07-23 16:49:16 +08:00
    接过一次用 http 状态码区分业务的接口, 还是比较恶心的, 如:用户不存在直接 404, 我还以为我 url 错了
    lululau
        9
    lululau  
       2019-07-23 16:50:50 +08:00
    @Ravenddd 这样有问题吗,资源找不到不是 404 吗
    binbinyouliiii
        10
    binbinyouliiii  
    OP
       2019-07-23 16:51:53 +08:00
    @chendy #1 不排除只把状态码只当状态来用,比如 我返回的数据格式可能有两种,就必须用两个状态码来区分是哪个。
    话说我觉得,http 只是个协议,可能用来做 web 服务,也可能做微服务,如果做微服务,我哪天改通讯协议了,岂不是跟 HTTP 耦合了,改起来不也烦。

    @beryl #3 这种帖子能引起战就有鬼了。

    @EastLord #5 处理方法尽量和框架无关比较好。
    TheBestSivir
        11
    TheBestSivir  
       2019-07-23 16:54:28 +08:00
    @chendy 恩,国内互联网尤其不喜欢 http 状态码区分成功失败,因为几乎没有大厂的 C 端业务完全适合 restful,而假设不玩 restful 的完全范式,其所谓的统一接口( Uniform Interface )也就没有意义了,自然就是放飞自我,各自团队各自规范,协议层也没啥特别统一的必要了。
    还有最重要的一点,劫持太严重了,走 http 状态码实在是不行。。。
    binbinyouliiii
        12
    binbinyouliiii  
    OP
       2019-07-23 16:55:32 +08:00
    @chendy #1 其实还有,国内网络环境很复杂,很多网络设备都有防火墙之类的装置,他们会拦截你的非 200 结果,返回其他结果
    vmskipper
        13
    vmskipper  
       2019-07-23 17:00:46 +08:00
    自定义状态码 参考各司开放平台文档
    Takamine
        14
    Takamine  
       2019-07-23 17:01:14 +08:00
    常规的做法就是统一封装响应对象到比如 ServiceResponse<T>中,不同系统和业务之间的状态码也具体描述在枚举字段中统一整理。
    至于是不是就遵从用 http 状态码 restful 的那一套见仁见智吧。
    个人觉得可能 http 的状态码 200 表示这次请求是成功的,但是返回的对象里面的 code 是 50010 表示密码错误,也完全可以接受。
    binbinyouliiii
        15
    binbinyouliiii  
    OP
       2019-07-23 17:09:23 +08:00
    @Takamine #14 就是感觉由 Service 封装不舒服
    Aresxue
        16
    Aresxue  
       2019-07-23 17:16:19 +08:00
    自定义状态码,比较友好的方式是 HTTP 状态码+四位自定义编码作为整个状态码,嫌长的话就屏蔽掉 Http 状态码,只使用自定义四位或六位编码。对系统异常和业务异常做严格区分,最好设置个切面统一捕获处理。
    binbinyouliiii
        17
    binbinyouliiii  
    OP
       2019-07-23 17:19:10 +08:00
    @Aresxue #16
    @vmskipper #13
    我做的就是自定义状态码,文中用 200 表示是为了方便理解这是一个正常的状态码。
    nikandaoleshenme
        18
    nikandaoleshenme  
       2019-07-23 17:45:47 +08:00
    @chendy 最后一句话表示同理,最烦 code=200,200 从哪冒出来的,莫名其妙,http 也为 200,到底按哪个值为准,应该是很久很久很久以前,有个程序员写的,后面的都是从他这复制来的,无论请求成功,失败,异常,错误,都特么的 http 200,
    zhybb2010
        19
    zhybb2010  
       2019-07-23 17:51:58 +08:00
    我使用 异常 /拦截器 去做处理,http 状态码一半只会使用 200/403, 其它的都是自己的状态码控制
    Takamine
        20
    Takamine  
       2019-07-23 18:07:50 +08:00 via Android
    @binbinyouliiii 我们的开发规范是 controller 不包含任何业务逻辑(除开部分简单验签),相当于整个业务全是 service 包场,controller 就只是一个 return,其他啥都不用干。不管怎么样,最后总得有个地方把响应封装,实现方式多种多样,按照开发规范来_(:з」∠)_。
    ke1e
        21
    ke1e  
       2019-07-23 18:15:30 +08:00 via Android
    一个简单的方法,所有方法都返回同一个类,假设是 TopContext,有状态码和 data 属性,再提供静态方法,例如 TopContext.ok(data),这样就可以实现。但是还是有点复杂
    liuxey
        22
    liuxey  
       2019-07-23 19:54:37 +08:00
    我见过一个国内知名公司使用四层结构,在 controller 和 service 层增加 facade 层,然后 controller 就真只做接收校验和返回

    虽然有人可能不喜欢,但使用异常来结束业务代码真的很精简,写起来及其飘逸

    当然还有 http 状态码设计等东西,我觉的没有孰优孰劣,用好了用统一了都是值得肯定的
    shipy
        23
    shipy  
       2019-07-23 19:56:31 +08:00
    用元组呀
    palmers
        24
    palmers  
       2019-07-23 20:12:21 +08:00
    异常和状态码的作用目的是不一样的,你这么用是不对的, 再者 java 是面向对象语言,应该站在对象的角度来考虑 如果按照对象角度返回状态对象就是合理的, 再者 底层抛出异常代表这个是无法容忍的错误, 不应该继续后续逻辑,而不是应该用来设计不同的状态
    springmarker
        25
    springmarker  
       2019-07-23 20:28:49 +08:00
    @Takamine #20 状态码的包装也由 service 层来处理的话,那碰到 service 之间的调用难道还得 get 出来 data 才行?
    顺便问一下,你们的状态码 code 的枚举类是怎么维护的,是每个项目一个?还是所有项目共用?共用的话是怎么维护的,谢谢。

    @ke1e #21 我之前就是这么做的,但是还是属于我说的第一种类型,就是包装,感觉不够清真。

    @liuxey #22 写 4 层是真的要死了。

    @shipy #23 元组在 Java 中并不原生,某种程度上来说跟包装差不多。

    @palmers #24 绝大多数业务就是 一个正常状态+多个异常状态 ,我觉得 业务的抛出非正常状态代码也可以理解为“异常”,catch 的话,只需 catch 住我们自定义的异常就可以了,其他无法容忍的错误该怎么还是怎么样
    lihongjie0209
        26
    lihongjie0209  
       2019-07-23 20:34:58 +08:00
    业务层直接抛异常, controller 只处理正常情况, 异常情况同意用拦截器翻译异常类型以及异常信息发送给前端
    lwd369
        27
    lwd369  
       2019-07-23 20:41:31 +08:00
    正好今天白天一直在纠结这个问题,在 service 层处理非正常响应时,直接抛出一个 exception,然后用 ExceptionHandler 里统一返回,不知道这样使用异常是否合理。如果使用普通的 ifelse 流程控制的话,要把错误状态返回给 controller 层似乎又有点麻烦。
    Takamine
        28
    Takamine  
       2019-07-24 00:15:07 +08:00 via Android
    @springmarker 是的,只要是暴露出去的都做封装,不管是业务之间还是应用之间。
    我们是不同系统用不同状态码,一般一个系统会分别领取分别代表业务异常、系统异常等的不同数字编号开头的 500-2000 个状态码。后面新增的系统再接着申请领取状态码,以此类推。所有系统的状态码有做统一维护。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     870 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 21:44 PVG 05:44 LAX 14:44 JFK 17:44
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86