写 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 1
多种正常错误码的场景是什么样的?
个人观点是不给 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 1
我很早之前做过,就是写一个 ControllerAdvice。将所有的 Controller 返回的数据再包裹一层{"code": "success", "data": Controller 返回的}。如果业务想要返回非 success 的响应,通过抛出一个指定的异常,然后再在 ExceptionHandler 里面捕获。
但是后来发现对于 REST 接口,为什么要将所有的业务响应再包裹一层呢? 而且我感觉不少公司都是这么搞的。客户端是根据 HTTP 错误码还是根据 body 中自定义的错误码判断业务正常呢???? 我之前发的一个帖子也顺便提到过这个东西。https://www.v2ex.com/t/558315#reply18 我反正是比较讨厌 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 个状态码。后面新增的系统再接着申请领取状态码,以此类推。所有系统的状态码有做统一维护。 |