前言
我有一個朋友,昨天和前端工程師聯調一個接口,然后被狠狠鄙視了一番。
大家知道,自從前后端分離以后,像我一樣一直以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自定義異常即可。
現在各種新技術層出不窮且內卷的狀況下,不要過分追求強大流行的技術,反而要多關注基本功和編碼小細節。
尤其是對尚未工作及工作年限不久的同行們而言,不要小看寫接口的能力,否則也會被公司的爸比所鄙視哦。
本人專注於分享各種技術、工作中的趣事及經驗,喜歡或有收獲的朋友們,不要吝嗇您的一個小小推薦哦~~
也可以查看個人主頁關注一下里面的信息哦~~