作者:京東零售 柯賢銘
問題回溯
2023年Q2某日運營反饋一個問題,商品系統(tǒng)商家中心某批量工具模板無法下載,導致功能無法使用(因為模板是動態(tài)變化的)
商家中心報錯(JSON串):
{"code":-1,"msg":"失敗"}
?
負責的同事看到失敗后立即與我展開討論(因為不是關(guān)鍵業(yè)務(wù),所以不需要回滾,修復即可),我們發(fā)現(xiàn)新功能模板下載的代碼與之前的代碼有所不同,恰好之前的功能又可以正常運行,所以同事對現(xiàn)有代碼進行改造然后預(yù)發(fā)布測試完成后再次上線。
?
其他業(yè)務(wù)代碼:
/** * 模板下載 */ @RequestMapping("/doBatchWareSetAd") public void doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) { wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId()); }
?
問題業(yè)務(wù)代碼:
/** * 模板下載 */ @RequestMapping("/doBatchWareSetAdDemo") @ResponseBody public Map doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) { return wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId()); }
?
上線的結(jié)果是;仍然無法使用。
其實也正常:因為兩種代碼在預(yù)發(fā)布都可以正常運行,在線上出錯只可能是因為其他原因,只不過我們不了解底層原理,害怕它 "可能" 有問題罷了,最終查詢得到的結(jié)論是權(quán)限系統(tǒng)管理員在線上環(huán)境沒有給我們配置相應(yīng)的文件,導致請求為空,導致請求失敗。
?
探索 @ResponseBody 與主動寫入流的關(guān)系
我們都知道 @ResponseBody 注解可以幫助我們把返回對象轉(zhuǎn)化為JSON,方便展示和交互。
那它到底是如何工作的呢,請看下面的講解:
?
代碼案例1:
@RequestMapping("/test1") @ResponseBody public Map test1(HttpServletResponse response) { Map map = new HashMap?>(); map.put("1", "1"); return map; } // 響應(yīng) JSON報文
?
跟代碼發(fā)現(xiàn)其核心處理類為:RequestResponseBodyMethodProcessor.java
方法:org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue 會處理其相關(guān)返回值。
真正的核心處理方法:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor#writeWithMessageConverters
關(guān)鍵DEBUG記錄如圖所示:
?
后續(xù)內(nèi)容可以想象,肯定還有地方去把流按照指定的HEADER寫入,因為和本文無關(guān)所以不深究。
?
再來看代碼案例2:
@RequestMapping("/test2") @ResponseBody public Map test2(HttpServletResponse response) throws IOException { Map map = new HashMap?>(); map.put("1", "1"); response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", String.format( "attachment; filename=%s_%s.xls", "Demo", System.currentTimeMillis())); OutputStream out = response.getOutputStream(); out.flush(); out.close(); return map; } // 響應(yīng) 提示下載文件
?
關(guān)鍵DEBUG源碼截圖:
?
可以發(fā)現(xiàn)Spring對這種方式操作文件流視作異常情況,然后拋出,在后續(xù)邏輯中完成整個請求,簡單來說就是 @ResponseBody 注解沒起到任何作用。
因此答案呼之欲出:當時功能不可用的罪魁禍首就是相關(guān)人員沒有配置參數(shù)導致,與寫法沒有任何關(guān)系。
?
結(jié)論與啟發(fā)
結(jié)論:
1.我們要相信自己的代碼,至少是要相信已經(jīng)經(jīng)過測試的代碼。
2.在委托他人或者自己配置環(huán)境參數(shù),如權(quán)限、ZK等每次都保證預(yù)發(fā)布和線上同時配置,避免遺漏的情況。
?
啟發(fā):
聊了這么多,那我們這種類似場景的代碼應(yīng)該怎么寫?
既然主動寫入流會解除@ResponseBody的作用,反之又能發(fā)揮它的作用,那我們最佳方案是不是如下所示?
@RequestMapping("/test1") @ResponseBody public Map test1(HttpServletResponse response) { Map map = new HashMap(); if (獲取不到文件配置 == true) { return map.put("msg", "獲取不到文件配置"); } response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", String.format( "attachment; filename=%s_%s.xls", "Demo", System.currentTimeMillis())); OutputStream out = response.getOutputStream(); out.flush(); out.close(); return map; }
?
如此一來,當發(fā)生預(yù)期之外的情況,我們有非常明顯的報錯提示,當正常時又可以完美實現(xiàn)功能,妙哉(我覺得)~
審核編輯 黃宇
-
代碼
+關(guān)注
關(guān)注
30文章
4797瀏覽量
68707 -
JSON
+關(guān)注
關(guān)注
0文章
119瀏覽量
6980
發(fā)布評論請先 登錄
相關(guān)推薦
評論