前言

我有一个朋友,昨天和前端工程师联调一个接口,然后被狠狠鄙视了一番。

大家知道,自从前后端分离以后,像我一样一直以Java工程师为傲而自居的码圣们就砍掉了一半脊梁,从此被贴上了“Java服务端工程师”、“Java后端工程师”等等这样的标签。

同时,前端爸比越来越多,也让我们写个接口都如履薄冰。

那么到底发生了审麽事情咧?

经过

梳理出来,大体经过是这样滴:

1)、我朋友是Java工程师,入职公司四个月,刚转正一个月,目前正在参与一个紧急的项目开发;

2)、他写完了接口,自测没问题,然后发布到测试环境,再测没问题,欧克,输出文档给前端,准备联调咯;

3)、前端工程师是个爸比,三十出头,追求细节,人狠话不多,入职三年多,公司大半前端页面和数据绑定都由他完成,是前端扛把子,看完了文档,调了下接口,欧克,没问题,开干;

4)、一上午过去了,很简单的接口并没有联调完,甚至两人发生了些许不愉快;

5)、前端爸比认为接口正常情况下可以,但异常情况下状态给的不明确,没办法根据状态值给用户友好提示;

6)、我朋友来的时间短,敢怒不敢言,畏畏缩缩指出了自己接口自定义了返回对象,正常时状态返回200,异常时会触发全局异常处理,返回状态500,很明确并没有什么问题嘤嘤婴;

7)、前端爸比一声嗤笑,哼小伙子,你一看就道行尚浅,和我有一腿……有交集的后端如过江之鲫,我联调过的接口比你拉的SHI还多,你快坐回去好好看看代码,是不是接口加了trycatch,然后捕获异常时直接返回了自定义响应对象;

8)、我朋友心中一慌,这老银币有点东西,一个前端连我Java代码怎么写的都知道,赶忙跑回去重新审视代码,来回审视和自测了好几遍,终于发现了不算问题的问题;

9)、原来接口返回的业务状态有很多,但HTTP状态永远是200成功,但这对你前端有毛的影响?

10)、前端爸比说确实没啥影响,但我就是要判断HTTP状态码,我有强迫症;

11)、没办法,毕竟是爸比,我朋友之后参考了我所负责的项目里面的接口代码,顺利完成了之后的联调工作,但从此在前端爸比心里打上了菜鸟的标签。

问题重现

为了节省时间,我直接以renren-fast作为脚手架来重现这个问题。

首先,我们定义一个简单的接口,使用自定义响应对象R返回,对接口进行try..catch,成功时返回R.ok(),异常时在catch中返回R.error()及错误信息。

(PS:题外话,工作这些年换过几个公司,其实看到不少同事喜欢这么写,我想其他公司也不在少数。)

/**
* 自定义响应对象返回
* @return 结果
*/
@GetMapping("getUserInfo")
@ApiOperation("获取用户信息")
public R getUserInfo() {
Map<String, Object> map = new HashMap<>();
try {
map.put("id", "1001");
map.put("name", "张三");
map.put("age", "33");
map.put("address", "湖北省神农架野人洞");
} catch (Exception ex) {
return R.error("异常:" + ex.getMessage());
}
return R.ok().put("data", map);
}

使用Postman调试一下接口,嗯可以正常返回。

接下来,模拟接口发生一个异常。

/**
* 自定义响应对象返回
* @return 结果
*/
@GetMapping("getUserInfo")
@ApiOperation("获取用户信息")
public R getUserInfo() {
Map<String, Object> map = new HashMap<>();
try {
map.put("id", "1001");
map.put("name", "张三");
map.put("age", "33");
map.put("address", "湖北省神农架野人洞");
// 模拟异常
int i = 1/0;
} catch (Exception ex) {
return R.error("异常:" + ex.getMessage());
}
return R.ok().put("data", map);
}

再使用Postman调用下看看,是返回500异常了,HTTP状态是200,没啥问题啊。

如果抛出一个异常呢

/**
* 自定义响应对象返回
* @return 结果
*/
@GetMapping("getUserInfo")
@ApiOperation("获取用户信息")
public R getUserInfo() {
Map<String, Object> map = new HashMap<>();
try {
map.put("id", "1001");
map.put("name", "张三");
map.put("age", "33");
map.put("address", "湖北省神农架野人洞");
// 模拟异常
int i = 1/0;
} catch (Exception ex) {
// 抛出一个运行时异常
throw new RuntimeException(ex.getMessage());
}
return R.ok().put("data", map);
}

一般会交由项目的全局异常进行处理,实际返回的还是自定义的响应对象R.error()。

@ExceptionHandler(Exception.class)
public R handleException(Exception e){
logger.error(e.getMessage(), e);
return R.error();
}

然后Postman再调试,可以看到,HTTP状态不变,接口业务状态返回500并提示异常,和前面一样,确实牟闷提啊。

好,这里说下,程序实际上是发生了异常,由代码自行捕获并返回了自定义响应结果,HTTP状态是200表示接口连通性正常,业务状态是500表示业务程序发生了异常。

其实大部分项目都这么做的,本身没什么问题,但有时会给前端工程师对接口状态的逻辑判断产生误解,再有,如果你是给另一个厂家写接口,你是提供方,对方是消费方,这么写会给对方制造麻烦。



正常来讲,有经验的前端工程师一般会这么判断:

1)、先判断HTTP状态,不是200表示失败则给出友好提示,成功则继续判断接口业务状态;

2)、判断接口业务状态,若返回200表示成功,则绑定数据,若不是200,给出友好提示,若有特殊业务状态,另行判断并处理。



那么,当后端工程师返回的是如示例所示的自定义响应对象,且全局异常处理中返回的也是示例中的自定义响应对象时,就意味着我们的接口HTTP状态永远都是200成功,前端对这一块的判断完全是失效的,一旦线上的项目出现特殊情景,可能造成意外假象。



再者,如前面所说,你是给其他公司厂家甚至第三方组件提供接口,这么写的话HTTP状态永远是200,也存在隐患,比如本人第一家公司用的XXLJOB,我们需要写接口给XXLJOB进行任务调度,这个接口就是上面那样返回的,一开始是好的,后来有同事改代码改出点问题,线上刚好也出现了该异常,而XXLJOB就是判断HTTP_STATUS的,结果它怎么识别我们接口都是返回200成功,它就没有反馈任何异常警告,导致这个重要的调度任务虽然正常执行却是无效的,我们也没留意,直到一堆待退费订单没有处理才发现问题。

优化处理

上面展示的实际上本身不是问题,大部分项目这么写也能正常在线上运行,只是存在小概率的风险,当项目规模较大时,存在很多不确定性,接口的返回状态是消费方进行逻辑处理的唯一依赖,因此,我的建议是最好同时返回更准确的HTTP状态和接口业务状态。

处理方式十分简单,使用spring-web自带的ResponseEntity包装一下即可。

/**
* 自定义响应对象返回(外层包装ResponseEntity)
* @return 结果
*/
@GetMapping("getUserInfo2")
@ApiOperation("获取用户信息")
public ResponseEntity<R> getUserInfo2() {
Map<String, Object> map = new HashMap<>();
try {
map.put("id", "1001");
map.put("name", "张三");
map.put("age", "33");
map.put("address", "湖北省神农架野人洞");
// 模拟异常
int i = 1/0;
} catch (Exception ex) {
// return ResponseEntity.badRequest().body(R.error("异常:" + ex.getMessage()));
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(R.error("异常:" + ex.getMessage()));
}
return ResponseEntity.ok().body(R.ok().put("data", map));
}

效果:

ResponseEntity封装了几乎所有的HTTP状态,上面示例代码包含注释掉的那行,一共两种方式,都可以自行返回具体的HTTP状态给前端.

如果选择自定义响应对象作为返回,那么就放到body里面即可,相当于ResponseEntity做了一层外包装,这样就能保证返回的接口既有具体HTTP状态,也有具体的业务状态,前后端工程师从此成为相亲相爱一家人。

总结

我给大家的最终建议是这样的:

1)、整个项目都规范好以ResponseEntity作为响应对象;

2)、如果有使用自定义响应对象,最好用ResponseEntity进行一层外包装;

3)、如果嫌弃这种写法,还可以这样,接口依然返回自定义响应对象,但全局异常处理中返回对象进行ResponseEntity包装,最后在出问题的地方throw自定义异常即可。

现在各种新技术层出不穷且内卷的状况下,不要过分追求强大流行的技术,反而要多关注基本功和编码小细节。

尤其是对尚未工作及工作年限不久的同行们而言,不要小看写接口的能力,否则也会被公司的爸比所鄙视哦。


本人专注于分享各种技术、工作中的趣事及经验,喜欢或有收获的朋友们,不要吝啬您的一个小小推荐哦~~

也可以查看个人主页关注一下里面的信息哦~~

【Java分享客栈】我有一个朋友,和前端工程师联调接口被狠狠鄙视了一番。的更多相关文章

  1. 【Java分享客栈】我为什么极力推荐XXL-JOB作为中小厂的分布式任务调度平台

    前言   大家好,我是福隆苑居士,今天给大家聊聊XXL-JOB的使用.   XXL-JOB是本人呆过的三家公司都使用到的分布式任务调度平台,前两家都是服务于传统行业(某大型移动基地和某大型电网),现在 ...

  2. 【Java分享客栈】SpringBoot整合WebSocket+Stomp搭建群聊项目

    前言 前两周经常有大学生小伙伴私信给我,问我可否有偿提供毕设帮助,我说暂时没有这个打算,因为工作实在太忙,现阶段无法投入到这样的领域内,其中有两个小伙伴又问到我websocket该怎么使用,想给自己的 ...

  3. 【Java分享客栈】一文搞定CompletableFuture并行处理,成倍缩短查询时间。

    前言   工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回.   还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次f ...

  4. 【Java分享客栈】一文搞定京东零售开源的AsyncTool,彻底解决异步编排问题。

    一.前言 本章主要是承接上一篇讲CompletableFuture的文章,想了解的可以先去看看案例: https://juejin.cn/post/7091132240574283813 Comple ...

  5. 【Java分享客栈】SpringBoot线程池参数搜一堆资料还是不会配,我花一天测试换你此生明白。

    一.前言   首先说一句,如果比较忙顺路点进来的,可以先收藏,有时间或用到了再看也行:   我相信很多人会有一个困惑,这个困惑和我之前一样,就是线程池这个玩意儿,感觉很高大上,用起来很fashion, ...

  6. 【Java分享客栈】超简洁SpringBoot使用AOP统一日志管理-纯干货干到便秘

    前言 请问今天您便秘了吗?程序员坐久了真的会便秘哦,如果偶然点进了这篇小干货,就麻烦您喝杯水然后去趟厕所一边用左手托起对准嘘嘘,一边用右手滑动手机看完本篇吧. 实现 本篇AOP统一日志管理写法来源于国 ...

  7. 【Java分享客栈】我曾经的两个Java老师一个找不到工作了一个被迫转行了

    前言 写这篇文章的初衷主要是最近发生了两件事,让我感慨良多,觉得踏入这个行业的初始,有些事情就应该长远考虑,这样对职业发展才更有利,仅仅停留在技术的追求上固然能壮大自身,可逆水行舟的程序员们终究会面临 ...

  8. 【Java分享客栈】Java程序员为争一口气熬夜硬刚CSS实现掘金首页

    前言 如果我做不了最厉害的Java工程师,那我就做Java工程师中最厉害的前端工程师. 前段时间,我默默给自己又喂了这碗心灵鸡汤-- 我不是很厉害的Java工程师,哪怕我已经工作八年,我依然觉得自己和 ...

  9. 【Java分享客栈】从线上环境摘取了四个代码优化记录分享给大家

    前言 因为前段时间新项目已经完成目前趋于稳定,所以最近我被分配到了公司的运维组,负责维护另外一个项目,包含处理客户反馈的日常问题,以及对系统缺陷进行优化. 经过了接近两周的维护,除了日常问题以外,代码 ...

随机推荐

  1. Cesium入门9 - Loading and Styling Entities - 加载和样式化实体

    Cesium入门9 - Loading and Styling Entities - 加载和样式化实体 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://c ...

  2. gin框架中项目的初始化

    核心知识点 json配置文件解析成结构体 将路由对应的接口抽离到单独的文件中,main函数中直接注册路由即可 项目目录图 项目代码 app.json代码 { "app_name": ...

  3. Git安装详解

    官网地址: https://git-scm.com/ 查看 GNU 协议,可以直接点击下一步. 选择 Git 安装位置,要求是非中文并且没有空格的目录,然后下一步. Git 选项配置,推荐默认设置,然 ...

  4. Python3 生成激活码

    1.文档: string模块:https://docs.python.org/3/library/string.html random模块:https://docs.python.org/3/libr ...

  5. python3 requests的content和text方法

    text返回的是Unicode型的数据 content返回的是是二进制的数据. 也就是说,如果你想取文本,可以通过r.text. 如果想取图片,文件,则可以通过r.content >>&g ...

  6. React之redux学习日志(redux/react-redux/redux-saga)

    redux官方中文文档:https://www.redux.org.cn/docs/introduction/CoreConcepts.html react-redux Dome:https://co ...

  7. Python数据结构之“栈”与“队列”

    栈(Stacks): ·定义:是一种只能通过访问其一端来实现的数据存储于检索的线性数据结构,具有后进先出(last in first out,LIFO)的特征 ·主要操作: 1. Stack():建立 ...

  8. 微服务架构 | 10.2 使用 Papertrail 实现日志聚合

    目录 前言 1. Papertrail 基础知识 1.1 Papertrail 特点 1.2 Papertrail 是什么 2. 使用 Papertrail 进行日志聚合的示例 2.1 创建 Pape ...

  9. Nginx 配置 HTTPS 服务器

    Nginx 配置 HTTPS 服务器 Chrome 浏览器地址栏标志着 HTTPS 的绿色小锁头从心理层面上可以给用户专业安全的心理暗示,本文简单总结一下如何在 Nginx 配置 HTTPS 服务器, ...

  10. Redis为什么是单线程,高并发快的3大原因详解

    出处知乎:https://zhuanlan.zhihu.com/p/58038188 Redis的高并发和快速原因 1.redis是基于内存的,内存的读写速度非常快: 2.redis是单线程的,省去了 ...