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

2019-07-23 16:24:35 +08:00
 binbinyouliiii

写 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;
}

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

7363 次点击
所在节点    Java
28 条回复
chendy
2019-07-23 16:31:53 +08:00
多种正常错误码的场景是什么样的?
个人观点是不给 service 层做这个包装,全部 controller 做,或者自己拿 ControllerAdvice 或者自己写 MessageConverter 啥的
顺便吐槽一下国内就这么不喜欢 http 状态码区分成功失败么
icebow
2019-07-23 16:32:12 +08:00
ResponseEntity?
beryl
2019-07-23 16:32:38 +08:00
抛异常,返回错误码问题!,感觉这是个引战帖
misaka19000
2019-07-23 16:33:59 +08:00
切面
EastLord
2019-07-23 16:38:51 +08:00
problem-spring-web 这个行吗
qwerthhusn
2019-07-23 16:39:25 +08:00
我很早之前做过,就是写一个 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);
}
}
}
```
lululau
2019-07-23 16:47:54 +08:00
参照 Rails, 这两种方法都是可取的,Rails 是同时支持这两种风格的 API
Ravenddd
2019-07-23 16:49:16 +08:00
接过一次用 http 状态码区分业务的接口, 还是比较恶心的, 如:用户不存在直接 404, 我还以为我 url 错了
lululau
2019-07-23 16:50:50 +08:00
@Ravenddd 这样有问题吗,资源找不到不是 404 吗
binbinyouliiii
2019-07-23 16:51:53 +08:00
@chendy #1 不排除只把状态码只当状态来用,比如 我返回的数据格式可能有两种,就必须用两个状态码来区分是哪个。
话说我觉得,http 只是个协议,可能用来做 web 服务,也可能做微服务,如果做微服务,我哪天改通讯协议了,岂不是跟 HTTP 耦合了,改起来不也烦。

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

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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/585490

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX