当前位置 : 主页 > 编程语言 > 其它开发 >

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

来源:互联网 收集:自由互联 发布时间:2022-05-18
前言 我有一个朋友,昨天和前端工程师联调一个接口,然后被狠狠鄙视了一番。 大家知道,自从前后端分离以后,像我一样一直以Java工程师为傲而自居的码圣们就砍掉了一半脊梁,从

前言

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

大家知道,自从前后端分离以后,像我一样一直以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调试一下接口,嗯可以正常返回。

1.png

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

/**
 * 自定义响应对象返回
 * @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,没啥问题啊。

2.png

如果抛出一个异常呢

/**
    * 自定义响应对象返回
    * @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并提示异常,和前面一样,确实牟闷提啊。

3.png

好,这里说下,程序实际上是发生了异常,由代码自行捕获并返回了自定义响应结果,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));
}

效果:

4.png

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

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


总结

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

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

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

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


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

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



本人专注于分享各种技术、工作中的趣事及经验,喜欢或有收获的朋友们,不要吝啬您的一个小小推荐哦~~
也可以查看个人主页关注一下里面的信息哦~~


喜欢就点一下推荐吧~~
网友评论