前言
最近在專案中遇到一個需求,需要後端提供一個下載Csv和Excel表格的介面。這個介面接收前端的查詢引數,針對這些引數對資料庫做查詢操作。將查詢到的結果生成Excel和Csv檔案,再以位元組流的形式返回給前端。
前端拿到這個流檔案之後,最開始用ajax來接收,但是前端傳送的請求卻被瀏覽器cancel掉了。後來發現,發展瞭如此之久的Ajax居然不支援流檔案下載。後來前端換成了最原始的XMLHttpRequest,才修復了這個問題。
首先給出專案原始碼的地址。這是原始碼,歡迎大家star或者提MR。
Csv
新建controller
先來一個簡單的例子。首先在controller中新建這樣一個介面。
@GetMapping("csv")
public void csv(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String fileName = this.getFileName(request, "測試資料.csv");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
LinkedHashMap<String, Object> header = new LinkedHashMap<>();
LinkedHashMap<String, Object> body = new LinkedHashMap<>();
header.put("1", "姓名");
header.put("2", "年齡");
List<LinkedHashMap<String, Object>> data = new ArrayList<>();
body.put("1", "小明");
body.put("2", "小王");
data.add(header);
data.add(body);
data.add(body);
data.add(body);
FileCopyUtils.copy(ExportUtil.exportCSV(data), response.getOutputStream());
}
複製程式碼
其中this.getFileName(request, "測試資料.csv")
函式是用來獲取匯出檔名的函式。單獨提出來是因為不同瀏覽器使用的預設的編碼不同。例如,如果使用預設的UTF-8編碼。在chrome瀏覽器中下載會出現中文亂碼。程式碼如下。
private String getFileName(HttpServletRequest request, String name) throws UnsupportedEncodingException {
String userAgent = request.getHeader("USER-AGENT");
return userAgent.contains("Mozilla") ? new String(name.getBytes(), "ISO8859-1") : name;
}
複製程式碼
response.getOutputStream()
則是用於建立位元組輸出流,在匯出csv檔案的controller程式碼結尾,通過工具類中的複製檔案函式將位元組流寫入到輸出流中,從而將csv檔案以位元組流的形式返回給客戶端。
當前端通過http請求訪問伺服器介面的時候,http中的所有的請求資訊都會封裝在HttpServletRequest
物件中。例如,你可以通過這個物件獲取到請求的URL地址,請求的方式,請求的客戶端IP和完整主機名,Web伺服器的IP和完整主機名,請求行中的引數,獲取請求頭的引數等等。
針對每一次的HTTP請求,伺服器會自動建立一個HttpServletResponse
物件和請求物件相對應。響應物件可以對當前的請求進行重定向,自定義響應體的頭部,設定返回流等等。
新建匯出工具類
我們新建一個匯出工具類,來專門負責匯出各種格式的檔案。程式碼如下。
public class ExportUtil {
public static byte[] exportCSV(List<LinkedHashMap<String, Object>> exportData) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
BufferedWriter buffCvsWriter = null;
try {
buffCvsWriter = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
// 將body資料寫入表格
for (Iterator<LinkedHashMap<String, Object>> iterator = exportData.iterator(); iterator.hasNext(); ) {
fillDataToCsv(buffCvsWriter, iterator.next());
if (iterator.hasNext()) {
buffCvsWriter.newLine();
}
}
// 重新整理緩衝
buffCvsWriter.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 釋放資源
if (buffCvsWriter != null) {
try {
buffCvsWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return out.toByteArray();
}
private static void fillDataToCsv(BufferedWriter buffCvsWriter, LinkedHashMap row) throws IOException {
Map.Entry propertyEntry;
for (Iterator<Map.Entry> propertyIterator = row.entrySet().iterator(); propertyIterator.hasNext(); ) {
propertyEntry = propertyIterator.next();
buffCvsWriter.write("\"" + propertyEntry.getValue().toString() + "\"");
if (propertyIterator.hasNext()) {
buffCvsWriter.write(",");
}
}
}
}
複製程式碼
fillDataToCsv
主要是抽離出來為csv填充一行一行的資料的。
執行
然後執行專案,呼叫http://localhost:8080/csv,就可以下載示例的csv檔案。示例如下。
Excel
新建controller
新建下載xlsx檔案的介面。
@GetMapping("xlsx")
public void xlsx(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
String fileName = this.getFileName(request, "測試資料.xlsx");
response.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\";");
List<LinkedHashMap<String, Object>> datas = new ArrayList<>();
LinkedHashMap<String, Object> data = new LinkedHashMap<>();
data.put("1", "姓名");
data.put("2", "年齡");
datas.add(data);
for (int i = 0; i < 5; i++) {
data = new LinkedHashMap<>();
data.put("1", "小青");
data.put("2", "小白");
datas.add(data);
}
Map<String, List<LinkedHashMap<String, Object>>> tableData = new HashMap<>();
tableData.put("日報表", datas);
tableData.put("週報表", datas);
tableData.put("月報表", datas);
FileCopyUtils.copy(ExportUtil.exportXlsx(tableData), response.getOutputStream());
}
複製程式碼
補充工具類
上面新建的匯出工具類中,只有匯出csv的函式,接下來我們要新增匯出xlsx的函式。
public static byte[] exportXlsx(Map<String, List<LinkedHashMap<String, Object>>> tableData) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
HSSFWorkbook workbook = new HSSFWorkbook();
// 建立多個sheet
for (Map.Entry<String, List<LinkedHashMap<String, Object>>> entry : tableData.entrySet()) {
fillDataToXlsx(workbook.createSheet(entry.getKey()), entry.getValue());
}
workbook.write(out);
} catch (IOException e) {
e.printStackTrace();
}
return out.toByteArray();
}
/**
* 將linkedHashMap中的資料,寫入xlsx表格中
*
* @param sheet
* @param data
*/
private static void fillDataToXlsx(HSSFSheet sheet, List<LinkedHashMap<String, Object>> data) {
HSSFRow currRow;
HSSFCell cell;
LinkedHashMap row;
Map.Entry propertyEntry;
int rowIndex = 0;
int cellIndex = 0;
for (Iterator<LinkedHashMap<String, Object>> iterator = data.iterator(); iterator.hasNext(); ) {
row = iterator.next();
currRow = sheet.createRow(rowIndex++);
for (Iterator<Map.Entry> propertyIterator = row.entrySet().iterator(); propertyIterator.hasNext(); ) {
propertyEntry = propertyIterator.next();
if (propertyIterator.hasNext()) {
String value = String.valueOf(propertyEntry.getValue());
cell = currRow.createCell(cellIndex++);
cell.setCellValue(value);
} else {
String value = String.valueOf(propertyEntry.getValue());
cell = currRow.createCell(cellIndex++);
cell.setCellValue(value);
break;
}
}
if (iterator.hasNext()) {
cellIndex = 0;
}
}
}
複製程式碼
fillDataToXlsx
的用途與csv一樣,為xlsx檔案的每一行刷上資料。
執行
然後執行專案,呼叫http://localhost:8080/xlsx,就可以下載示例的csv檔案。示例如下。
專案地址
最後再次給出專案地址,大家如果沒有理解到其中的一些地方,不妨把專案clone下來,自己親自操作一波。
參考
這是在解決請求被瀏覽器cancel掉的過程中,很重要的一個參考,分享給大家。
- https://www.cnblogs.com/cdemo/p/5225848.html