從前端下查詢,再用流匯出並下載Excel
完整的範例
前端
- 重點在於前端若使用JQuery封裝好的$.postJSON等方法,會因為二進位問題導致亂碼
- 使用原生的xhr可以解決
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button onclick="doExport()">export</button>
</body>
</html>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script>
function doExport() {
// 請求參數,最後會轉成JSON格式
var requestParams = {
"name": "Tom",
"age": 13,
"test": "xxx",
};
var url = "http://localhost:12222/export";
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Authorization", "xxx"); // 用來做身份校驗的自訂token等等
xhr.setRequestHeader("Content-Type", "application/json");
xhr.responseType = "blob"; // 返回類型blob
xhr.onload = function () { // 定義請求完成的處理函數
if (this.status === 200) {
var blob = this.response;
var reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (e) {
// 轉換完成後創建隱藏的a標籤執行下載
var a = document.createElement('a');
a.download = "export"; // 自訂的檔案名稱
a.href = e.target.result;
$("body").append(a);
a.click();
$(a).remove();
}
}
};
xhr.send(JSON.stringify(requestParams)) // 發送ajax請求,如果沒有要帶參數可以為空
}
</script>
後端
引包
- 這裡用糊塗工具包更方便,但要注意還需引poi,否則
ExcelUtil.getWriter();
報錯
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
Controller
- IDEA可以用插件RoboPOJOGenerator快速的從JSON生成POJO,用於接收@RequestBody特別方便
@Controller
public class MyController {
/**
* 從BeanList輸出excel到前端並且自訂欄位
*/
@RequestMapping("/export")
public void export(@RequestBody RequestParams req, HttpServletResponse response) throws IOException {
// 這是前端來的請求參數,可以依據此請求去做查詢等等
System.out.println("req = " + req);
// 這邊隨意生成一些假資料演示
XxxInfo build = XxxInfo.builder().aaa("asdfg").bbb("sreg").count(1).build();
XxxInfo build2 = XxxInfo.builder().aaa("tydrfkj").bbb("es5u").count(0).build();
XxxInfo build3 = XxxInfo.builder().aaa("dtykl").bbb("rfjt").count(2).build();
ExcelWriter writer = ExcelUtil.getWriter();
// 自定義標題別名
writer.addHeaderAlias("aaa", "姓名");
writer.addHeaderAlias("bbb", "年齡");
writer.addHeaderAlias("count", "分數");
writer.addHeaderAlias("type", "是否通過");
// 預設未添加alias的屬性也會寫出,如果想只寫出加了別名的字段,可以調用此方法排除之
writer.setOnlyAlias(true);
// 修改字體,預設size是10超級小
StyleSet styleSet = writer.getStyleSet().setFont(HSSFColor.HSSFColorPredefined.BLACK.getIndex(), (short) 14, null, false);
writer.setStyleSet(styleSet);
ArrayList<XxxInfo> xxxInfos = CollUtil.newArrayList(build, build2, build3);
// 一次性寫出內容,使用默認樣式,強制輸出標題
writer.write(xxxInfos, true);
// response為HttpServletResponse對象
response.setContentType("application/vnd.ms-excel;charset=utf-8");
// test.xls是彈出下載對話框的文件名,不能為中文,中文請自行編碼
response.setHeader("Content-Disposition", "attachment;filename=test.xls");
ServletOutputStream out = response.getOutputStream();
writer.flush(out, true);
// 關閉writer,釋放內存
writer.close();
// 此處記得關閉輸出Servlet流
IoUtil.close(out);
}
}
解決JSONObject順序問題
- 研究@ResponseBody附贈的探討,最初是在想用JSONObject與map到底差在哪,才發現JSONObject底層其實也是map
- 區別在於map可以存null、少數情況下(例如android SDK 4.3以下)用map在多層架構下可能會包不到內層的物件
- 所以最好都用JSONObject,建構時傳入一個
new LinkedHashMap()
可以確保呈現的順序等於put的順序
/**
* 解決JSONObject順序問題
*/
@RequestMapping("/res")
@ResponseBody
public String res() {
XxxInfo build = XxxInfo.builder().aaa("asdfg").bbb("sreg").count(1).build();
XxxInfo build2 = XxxInfo.builder().aaa("tydrfkj").bbb("es5u").count(0).build();
XxxInfo build3 = XxxInfo.builder().aaa("dtykl").bbb("rfjt").count(2).build();
ArrayList<XxxInfo> xxxInfos = CollUtil.newArrayList(build, build2, build3);
Map<String, Object> map = new HashMap<>();
map.put("list", xxxInfos);
map.put("abc", 123);
// 重點
JSONObject jo = new JSONObject(new LinkedHashMap());
jo.putOpt("list", xxxInfos);
jo.putOpt("afsg", "SRgrehg");
jo.putOpt("map", map);
return jo.toStringPretty();
}
上次修改於 2022-03-31