title: 想在 Java 中实现 Excel 和 Csv 的导出吗?看这就对了 date: 2019-03-01 20:07:07tags: Javakeywords: Java 导出 Excel 和 Csv
description:
前言
最近在项目中遇到一个需求,需要后端提供一个下载 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…