首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Web 大數據導出 Excel 超時 (504) 終極解決方案:CSV 流式分段輸出

     編輯:Java 相關知識

大數據量導出 Excel 時,後端查詢數據庫、組裝對象、生成 Workbook 往往超過 Nginx/網關的 60s/120s 超時限制,導致 504 Gateway Timeout。本文提供一個 **CSV 流式輸出工具類**,利用 ServletOutputStream 分批 flush(),讓瀏覽器持續接收數據,徹底規避超時問題。

核心思路

  1. **不生成完整 Excel 對象**,直接輸出 CSV 文本流
  2. **分批寫入、即時 flush**,保持 HTTP 連接活躍
  3. **編碼用 GBK**,兼容 Excel/WPS/LibreOffice 直接打開
  4. **反射獲取字段值**,支援任意 POJO,含日期格式化、逗號轉義

工具類完整代碼

public class CSV {
    private final OutputStream stream;
    private final Map headers; // fieldName -> displayName
    private static final Map FIELD_CACHE = new HashMap<>();

    public CSV(HttpServletResponse response, Map headers,
               String fileName, Class clazz) throws IOException {
        if (response == null || headers == null || fileName == null || clazz == null)
            throw new IllegalArgumentException();
        initFieldCache(clazz);
        this.stream = response.getOutputStream();
        this.headers = headers;

        response.setContentType("application/octet-stream;charset=GBK");
        response.setHeader("Content-Disposition", "attachment;fileName=" + fileName);

        // 寫表頭
        StringBuilder hb = new StringBuilder();
        for (String h : headers.values()) hb.append(h).append(",");
        stream.write(hb.substring(0, hb.length()-1).getBytes("GBK"));
        stream.flush();
    }

    private static void initFieldCache(Class clazz) {
        if (!FIELD_CACHE.isEmpty()) return;
        for (Field f : clazz.getDeclaredFields()) {
            f.setAccessible(true);
            FIELD_CACHE.put(f.getName(), f);
        }
        if (clazz.getSuperclass() != null) initFieldCache(clazz.getSuperclass());
    }

    public void write(List data) throws IOException, IllegalAccessException {
        for (Object obj : data) {
            StringBuilder sb = new StringBuilder("\n");
            for (String fieldName : headers.keySet()) {
                Field f = FIELD_CACHE.get(fieldName);
                Object val = f.get(obj);
                if (val == null) { sb.append(" ,"); continue; }
                if (f.getType() == Date.class) {
                    sb.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(val)).append(",");
                } else if (f.getType() == DateTime.class) { // Joda-Time
                    sb.append(((DateTime)val).toString("yyyy-MM-dd HH:mm:ss")).append(",");
                } else {
                    String s = val.toString();
                    if (s.contains(",")) s = s.replace(",", "\",\""); // 簡易逗號轉義
                    sb.append(s).append(",");
                }
            }
            stream.write(sb.substring(0, sb.length()-1).getBytes("GBK"));
            stream.flush(); // 關鍵:每行即時推送給瀏覽器
        }
    }

    public void close() throws IOException { stream.close(); }
}

使用範例

 headers = new LinkedHashMap<>();
    headers.put("id", "ID");
    headers.put("name", "用戶名");
    headers.put("createTime", "註冊時間");

    CSV csv = new CSV(response, headers, "users.csv", User.class);
    int pageSize = 5000;
    long offset = 0;
    while (true) {
        List batch = userService.selectPage(offset, pageSize);
        if (batch.isEmpty()) break;
        csv.write(batch);
        offset += pageSize;
    }
    csv.close();
}

關鍵優勢對比

傳統 POI 導出CSV 流式導出
需在記憶體構建整個 Workbook,OOM 風險高流式寫入,記憶體佔用恆定
耗時長,易觸發網關超時邊查邊寫邊 flush,長連接保活
輸出 .xlsx,格式豐富但體積大輸出 .csv,純文本,體積小、打開快
樣式、合併儲存格等高級功能無樣式,純數據導出場景適用

注意事項

  • 僅處理逗號轉義,雙引號、換行符未處理,嚴格場景請用成熟 CSV 库(如 Apache Commons CSV)
  • GBK 編碼兼容中文 Windows 環境下的 Excel/WPS,Linux 服務端需確保支援 GBK
  • 字段緩存靜態 Map,同類多次導出無需重複反射
  • 生產環境建議加入異步導出、進度通知、檔案壓縮下載等增強

此方案已在百萬級數據導出場景驗證穩定,徹底解決 504 超時痛點。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved