【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自定義異常即可。


現在各種新技術層出不窮且內卷的狀況下,不要過分追求強大流行的技術,反而要多關注基本功和編碼小細節。

尤其是對尚未工作及工作年限不久的同行們而言,不要小看寫接口的能力,否則也會被公司的爸比所鄙視哦。



本人專注於分享各種技術、工作中的趣事及經驗,喜歡或有收獲的朋友們,不要吝嗇您的一個小小推薦哦~~
也可以查看個人主頁關注一下里面的信息哦~~



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM