文章目录
- 前言
- 正文
- 一、项目依赖
- 二、封装表格实体和Sheet实体
- 2.1 表格实体
- 2.2 Sheet实体
- 三、核心实现
- 3.1 核心实现之导出为输出流
- 3.2 web导出
- 3.3 导出为字节数组
- 四、调试
- 4.1 构建调试用的实体类
- 4.2 控制器调用
- 4.3 测试结果
- 五、注册大数转换器,长度大于15时,转换为字符串
- 5.1 实现转换器
- 5.2 使用转换器
前言
工作中遇到一个需求,一次导出多个Excel 文件,并且每个excel中可能存在1到多个sheet页。
好在没有那种单元格合并的要求。
总体的思路是,设计两个实体,一个表示表格,一个表示sheet 数据。并且表格包含一个list 类型的sheet对象。
然后再使用ZipOutputStream
、ExcelWriterBuilder
、EasyExcel#writerSheet(...)
等类和方法去组装表格,最终进行压缩。
项目整体使用 java 8 和 阿里的easyexcel工具包。
正文
一、项目依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.11</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
二、封装表格实体和Sheet实体
2.1 表格实体
/**
* excel数据
*/
@Data
public static class ExcelData {
// sheet的列表
private final List<ExcelShellData<?>> shellDataList = new ArrayList<>();
// 表格文件名
private String filename;
public void addShellData(ExcelShellData<?> excelShellData) {
this.shellDataList.add(excelShellData);
}
}
2.2 Sheet实体
/**
* sheet数据
*/
@Data
@AllArgsConstructor
public static class ExcelShellData<T> {
// 数据
private List<T> list;
// sheet名
private String sheetName;
// 数据实体类型
private Class<T> clazz;
}
三、核心实现
3.1 核心实现之导出为输出流
这一步主要组装表格数据,以及生成sheet。最终将数据放到输出流outputStream
中 。
private static void exportZipStream(List<ExcelData> excelDataList, OutputStream outputStream) {
try {
// 开始存入
try (ZipOutputStream zipOut = new ZipOutputStream(outputStream)) {
try {
for (ExcelData excelData : excelDataList) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
com.alibaba.excel.ExcelWriter excelWriter = null;
try {
ExcelWriterBuilder builder = EasyExcel.write(byteArrayOutputStream).autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
excelWriter = builder.build();
zipOut.putNextEntry(new ZipEntry(excelData.getFilename()));
// 开始写入excel
for (ExcelShellData<?> shellData : excelData.getShellDataList()) {
WriteSheet writeSheet = EasyExcel.writerSheet(shellData.getSheetName()).head(shellData.getClazz()).build();
excelWriter.write(shellData.getList(), writeSheet);
}
} catch (Exception e) {
throw new RuntimeException("导出Excel异常", e);
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
byteArrayOutputStream.writeTo(zipOut);
zipOut.closeEntry();
}
} catch (Exception e) {
throw new RuntimeException("导出Excel异常", e);
}
}
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
3.2 web导出
可以调用本方法,直接在Controller中调用之后,当访问对应url,会直接下载到浏览器。
/**
* 导出多个sheet到多个excel文件,并压缩到一个zip文件
*/
public static void exportZip(String zipFilename, List<ExcelData> excelDataList, HttpServletResponse response) {
try {
// 这里URLEncoder.encode可以防止中文乱码
zipFilename = URLEncoder.encode(zipFilename, "utf-8");
// 指定文件名
response.setHeader("Content-disposition", "attachment;filename=" + zipFilename);
response.setContentType("application/x-msdownload");
response.setCharacterEncoding("utf-8");
exportZipStream(excelDataList, response.getOutputStream());
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
3.3 导出为字节数组
当我们需要导出为字节数组时,可以调用本方法。之后随你怎么加工。
/**
* 导出多个sheet到多个excel文件,并压缩到一个zip文件。最终得到一个字节数组。
*/
public static byte[] exportZip(List<ExcelData> excelDataList) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
exportZipStream(excelDataList, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
}
四、调试
4.1 构建调试用的实体类
指定简单的几个字段作为导出数据。
@Data
@AllArgsConstructor
public static class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
}
4.2 控制器调用
组装假数据,进行导出。
@Controller
@RequestMapping("/excel")
public class ExcelDemoController {
@GetMapping("/exportTransportDetail")
public String exportTransportDetail(HttpServletResponse response) throws IOException {
// 压缩包文件名
String fileName = "结算单运单明细-" + System.currentTimeMillis() + ".zip";
List<ExcelData> excelDataList = new ArrayList<>();
// 第一个Excel
ExcelData excelData1 = new ExcelData();
excelData1.setFilename("结算单运单明细-1-" + System.currentTimeMillis() + ".xlsx");
List<DemoData> demoData1 = new ArrayList<>();
demoData1.add(new DemoData("excel-sheet1", new Date(), 123112.321));
demoData1.add(new DemoData("excel-sheet1", new Date(), 34.3));
List<DemoData> demoData2 = new ArrayList<>();
demoData2.add(new DemoData("excel-sheet2", new Date(), 123112.321));
demoData2.add(new DemoData("excel-sheet2", new Date(), 34.3));
ExcelShellData<DemoData> shellData1 = new ExcelShellData<>(demoData1, "sheet1", DemoData.class);
ExcelShellData<DemoData> shellData2 = new ExcelShellData<>(demoData2, "sheet2", DemoData.class);
excelData1.addShellData(shellData1);
excelData1.addShellData(shellData2);
// 第2个Excel
ExcelData excelData2 = new ExcelData();
excelData2.setFilename("结算单运单明细-2-" + System.currentTimeMillis() +".xlsx");
List<DemoData> demoData21 = new ArrayList<>();
demoData21.add(new DemoData("excel-sheet21", new Date(), 123112.321));
demoData21.add(new DemoData("excel-sheet22", new Date(), 34.3));
List<DemoData> demoData22 = new ArrayList<>();
demoData22.add(new DemoData("excel-sheet21", new Date(), 123112.321));
demoData22.add(new DemoData("excel-sheet22", new Date(), 34.3));
ExcelShellData<DemoData> shellData21 = new ExcelShellData<>(demoData21, "sheet1", DemoData.class);
ExcelShellData<DemoData> shellData22 = new ExcelShellData<>(demoData22, "sheet2", DemoData.class);
excelData2.addShellData(shellData21);
excelData2.addShellData(shellData22);
excelDataList.add(excelData1);
excelDataList.add(excelData2);
// 写法1///
// exportZip(fileName, excelDataList , response);
// 写法2///
byte[] bytes = exportZip(excelDataList);
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
response.setContentType("application/x-msdownload");
response.setCharacterEncoding("utf-8");
response.getOutputStream().write(bytes);
response.getOutputStream().flush();
return "succ";
}
}
4.3 测试结果
可以看到压缩包解压后的效果:
其中一个文件内容如下:
sheet1:
sheet2:
五、注册大数转换器,长度大于15时,转换为字符串
5.1 实现转换器
/**
* Excel 数值长度大于maxLength的数值转换为字符串
*/
public static class ExcelBigNumberConvert implements Converter<Long> {
private final int maxLength;
public ExcelBigNumberConvert() {
this(15);
}
public ExcelBigNumberConvert(Integer maxLength) {
this.maxLength = maxLength;
}
@Override
public Class<Long> supportJavaTypeKey() {
return Long.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public Long convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
Object data = cellData.getData();
if (data == null) {
return null;
}
String s = String.valueOf(data);
if (s.matches("^\\d+$")) {
return Long.parseLong(s);
}
return null;
}
@Override
public CellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (object != null) {
String str = object.toString();
if (str.length() > maxLength) {
return new CellData<>(str);
}
}
return null;
}
}
5.2 使用转换器
在构建建造器时,增加注册转换器即可。