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