問題回溯 2023年Q2某日運營反饋一個問題,商品系統商家中心某批量工具模板無法下載,導致功能無法使用(因為模板是動態變化的) 商家中心報錯(JSON串): {"code":-1,"msg":"失敗"} 負責的同事看到失敗後立即與我展開討論(因為不是關鍵業務,所以不需要回滾,修複即可),我們發現新功 ...
問題回溯
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<String, Object> doBatchWareSetAd(@RequestParam MultipartFile file, HttpServletResponse response) {
return wareBatchBusiness.doBatchWareSetAd(file, response, getLongOrgCode(), getCurrentUserPin(), getCurrentUserId());
}
上線的結果是;仍然無法使用。
其實也正常:因為兩種代碼在預發佈都可以正常運行,線上上出錯只可能是因為其他原因,只不過我們不瞭解底層原理,害怕它 "可能" 有問題罷了,最終查詢得到的結論是許可權系統管理員線上上環境沒有給我們配置相應的文件,導致請求為空,導致請求失敗。
探索 @ResponseBody 與主動寫入流的關係
我們都知道 @ResponseBody
註解可以幫助我們把返回對象轉化為JSON,方便展示和交互。
那它到底是如何工作的呢,請看下麵的講解:
代碼案例1:
@RequestMapping("/test1")
@ResponseBody
public Map<String, String> test1(HttpServletResponse response) {
Map<String, String> 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<String, String> test2(HttpServletResponse response) throws IOException {
Map<String, String> 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
註解沒起到任何作用。
因此答案呼之欲出:當時功能不可用的罪魁禍首就是相關人員沒有配置參數導致,與寫法沒有任何關係。
結論與啟發
結論:
- 我們要相信自己的代碼,至少是要相信已經經過測試的代碼。
- 在委托他人或者自己配置環境參數,如許可權、ZK等每次都保證預發佈和線上同時配置,避免遺漏的情況。
啟發:
聊了這麼多,那我們這種類似場景的代碼應該怎麼寫?
既然主動寫入流會解除@ResponseBody的作用,反之又能發揮它的作用,那我們最佳方案是不是如下所示?
@RequestMapping("/test1")
@ResponseBody
public Map<String, String> test1(HttpServletResponse response) {
Map<String, String> 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;
}
如此一來,當發生預期之外的情況,我們有非常明顯的報錯提示,當正常時又可以完美實現功能,妙哉(我覺得)~
作者:京東零售 柯賢銘
來源:京東雲開發者社區 轉載請註明來源