從前端下查詢,再用流匯出並下載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