写 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; }
请教大家平常用什么方法比较优雅。
![]() | 1 chendy 2019-07-23 16:31:53 +08:00 ![]() 多种正常错误码的场景是什么样的? 个人观点是不给 service 层做这个包装,全部 controller 做,或者自己拿 ControllerAdvice 或者自己写 MessageConverter 啥的 顺便吐槽一下国内就这么不喜欢 http 状态码区分成功失败么 |
2 icebow 2019-07-23 16:32:12 +08:00 ResponseEntity? |
![]() | 3 beryl 2019-07-23 16:32:38 +08:00 抛异常,返回错误码问题!,感觉这是个引战帖 |
![]() | 4 misaka19000 2019-07-23 16:33:59 +08:00 切面 |
![]() | 5 EastLord 2019-07-23 16:38:51 +08:00 problem-spring-web 这个行吗 |
![]() | 6 qwerthhusn 2019-07-23 16:39:25 +08:00 ![]() 我很早之前做过,就是写一个 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); } } } ``` |
![]() | 7 lululau 2019-07-23 16:47:54 +08:00 参照 Rails, 这两种方法都是可取的,Rails 是同时支持这两种风格的 API |
8 Ravenddd 2019-07-23 16:49:16 +08:00 接过一次用 http 状态码区分业务的接口, 还是比较恶心的, 如:用户不存在直接 404, 我还以为我 url 错了 |
![]() | 10 binbinyouliiii OP |
![]() | 11 TheBestSivir 2019-07-23 16:54:28 +08:00 @chendy 恩,国内互联网尤其不喜欢 http 状态码区分成功失败,因为几乎没有大厂的 C 端业务完全适合 restful,而假设不玩 restful 的完全范式,其所谓的统一接口( Uniform Interface )也就没有意义了,自然就是放飞自我,各自团队各自规范,协议层也没啥特别统一的必要了。 还有最重要的一点,劫持太严重了,走 http 状态码实在是不行。。。 |
![]() | 12 binbinyouliiii OP @chendy #1 其实还有,国内网络环境很复杂,很多网络设备都有防火墙之类的装置,他们会拦截你的非 200 结果,返回其他结果 |
![]() | 13 vmskipper 2019-07-23 17:00:46 +08:00 自定义状态码 参考各司开放平台文档 |
![]() | 14 Takamine 2019-07-23 17:01:14 +08:00 常规的做法就是统一封装响应对象到比如 ServiceResponse<T>中,不同系统和业务之间的状态码也具体描述在枚举字段中统一整理。 至于是不是就遵从用 http 状态码 restful 的那一套见仁见智吧。 个人觉得可能 http 的状态码 200 表示这次请求是成功的,但是返回的对象里面的 code 是 50010 表示密码错误,也完全可以接受。 |
![]() | 15 binbinyouliiii OP @Takamine #14 就是感觉由 Service 封装不舒服 |
![]() | 16 Aresxue 2019-07-23 17:16:19 +08:00 自定义状态码,比较友好的方式是 HTTP 状态码+四位自定义编码作为整个状态码,嫌长的话就屏蔽掉 Http 状态码,只使用自定义四位或六位编码。对系统异常和业务异常做严格区分,最好设置个切面统一捕获处理。 |
![]() | 17 binbinyouliiii OP |
![]() | 18 nikandaoleshenme 2019-07-23 17:45:47 +08:00 @chendy 最后一句话表示同理,最烦 code=200,200 从哪冒出来的,莫名其妙,http 也为 200,到底按哪个值为准,应该是很久很久很久以前,有个程序员写的,后面的都是从他这复制来的,无论请求成功,失败,异常,错误,都特么的 http 200, |
![]() | 19 zhybb2010 2019-07-23 17:51:58 +08:00 我使用 异常 /拦截器 去做处理,http 状态码一半只会使用 200/403, 其它的都是自己的状态码控制 |
![]() | 20 Takamine 2019-07-23 18:07:50 +08:00 via Android @binbinyouliiii 我们的开发规范是 controller 不包含任何业务逻辑(除开部分简单验签),相当于整个业务全是 service 包场,controller 就只是一个 return,其他啥都不用干。不管怎么样,最后总得有个地方把响应封装,实现方式多种多样,按照开发规范来_(:з」∠)_。 |
21 ke1e 2019-07-23 18:15:30 +08:00 via Android 一个简单的方法,所有方法都返回同一个类,假设是 TopContext,有状态码和 data 属性,再提供静态方法,例如 TopContext.ok(data),这样就可以实现。但是还是有点复杂 |
![]() | 22 liuxey 2019-07-23 19:54:37 +08:00 我见过一个国内知名公司使用四层结构,在 controller 和 service 层增加 facade 层,然后 controller 就真只做接收校验和返回 虽然有人可能不喜欢,但使用异常来结束业务代码真的很精简,写起来及其飘逸 当然还有 http 状态码设计等东西,我觉的没有孰优孰劣,用好了用统一了都是值得肯定的 |
23 shipy 2019-07-23 19:56:31 +08:00 用元组呀 |
![]() | 24 palmers 2019-07-23 20:12:21 +08:00 异常和状态码的作用目的是不一样的,你这么用是不对的, 再者 java 是面向对象语言,应该站在对象的角度来考虑 如果按照对象角度返回状态对象就是合理的, 再者 底层抛出异常代表这个是无法容忍的错误, 不应该继续后续逻辑,而不是应该用来设计不同的状态 |
![]() | 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 住我们自定义的异常就可以了,其他无法容忍的错误该怎么还是怎么样 |
26 lihongjie0209 2019-07-23 20:34:58 +08:00 业务层直接抛异常, controller 只处理正常情况, 异常情况同意用拦截器翻译异常类型以及异常信息发送给前端 |
27 lwd369 2019-07-23 20:41:31 +08:00 正好今天白天一直在纠结这个问题,在 service 层处理非正常响应时,直接抛出一个 exception,然后用 ExceptionHandler 里统一返回,不知道这样使用异常是否合理。如果使用普通的 ifelse 流程控制的话,要把错误状态返回给 controller 层似乎又有点麻烦。 |
![]() | 28 Takamine 2019-07-24 00:15:07 +08:00 via Android @springmarker 是的,只要是暴露出去的都做封装,不管是业务之间还是应用之间。 我们是不同系统用不同状态码,一般一个系统会分别领取分别代表业务异常、系统异常等的不同数字编号开头的 500-2000 个状态码。后面新增的系统再接着申请领取状态码,以此类推。所有系统的状态码有做统一维护。 |