我们平时开发会需要做一些 Excel 导出的需求,简单快捷的方式是使用 EasyExcel 来做,如果是简单的数据导出,我们可以在POJO上打ExcelProperty注解,然后直接调用 EasyExcel.write 静态方法,一行代码就搞定了!如:
// 定义模型
@Data
public class Student {
@ExcelProperty(value = "姓名", index = 0)
private String name;
@ExcelProperty(value = "身份证号", index = 1)
private String idCard;
@ExcelProperty(value = "年龄", index = 2)
private int age;
@ExcelProperty(value = "性别", index = 3)
private String gender;
}
List<Student> students = ...
// 写出 Excel 文件
EasyExcel.write(fileName, Student.class).sheet("Sheet1").doWrite(students);
但是,如果我们的要导出的 Excel 文件格式有比较复杂的要求,那问题就变得复杂了。使用 EasyExcel 或者其他的工具来操作 Excel 做复杂的格式,都不是一件容易的事情,你不得不写大量枯燥冗余的代码来实现,容易出错,也不易维护。怎么办?可以使用模板填充的方式来做。
事实上,EasyExcel 直持 fill 模式,即模板填充,简单来说,就是你提供一个 Excel 模板,根据规则放一些占位符,然后调用相关的 API 将数据填充进去,就可以了。
我们来看一个示例:
注:这里使用 {} 来表示你要用的变量 如果本来就有"{" 或 “}” 特殊字符,则用"\{" 和 "\}"代替。{xxx} 代表普通变量 {.xxx} 代表是list的变量
如果你的模板只有 list 的变量需要填充,例如这样:
可以一行代码搞定
// Student 对象中包含模板占位符中的属性
List<Student> list = ...
// out 可以是文件路径也可以是 OutputStream
EasyExcel.write(out)
// 选择模板,可以是文件路径也可以传 InputStream
.withTemplate(template)
// 选择显示 sheet
.sheet()
// 填充数据
.doFill(list);
但是就像上面的示例模板那样,即有普通变量又有 list 变量时,就要稍微复杂一点:
复杂填充完整代码
// Student 对象中包含模板占位符中的属性
List<Student> list = ...
// 设置输出目标和模板,out和template 可以是文件路径或流
ExcelWriter excelWriter = EasyExcel.write(out).withTemplate(template).build();
// 创建 Sheet
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。但是这个就会把所有数据放到内存 会很耗内存
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(list, fillConfig, writeSheet);
// 填充普通变量
Map<String, Object> map = new HashMap<String, Object>();
map.put("className", "软件工程3班");
excelWriter.fill(map, writeSheet);
excelWriter.finish();
模板的功能很强大,但是第一次使用时,我们有个疑问:模板文件要放到哪里?现在,我们都使用 SpringBoot 它会将应用打包成 FatJar,我们早已习惯了将各种配置文件放到 resources 目录下,所以 resources 目录是存放模板文件的最佳位置。
要将模板文件放在 resources 目录下,必须在 pom.xml 文件中添加以下配置:
<build>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-resources-pluginartifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>xlsxnonFilteredFileExtension>
<nonFilteredFileExtension>xlsnonFilteredFileExtension>
nonFilteredFileExtensions>
configuration>
plugin>
<resources>
<resource>
<directory>src/main/resourcesdirectory>
<filtering>truefiltering>
<includes>
<include>**/*.*include>
includes>
resource>
resources>
build>
实战中,我们导出一个 Excel 文件的时候,并不想也不需要写到服务器磁盘里,而是做为请求的响应,让用户去下载。或者是上传到 OSS 对象存储仓库中,然后返回一个下载链接。如何实现呢?
这里演示一下,从 resources 目录读取模板,填充数据,然后得到一个 InputStream,用于响应用户请求或者上传 OSS
// 从 /resources 目录下加载模板
try (InputStream templateInputStream = getClass().getClassLoader().getResourceAsStream("excel-template/学生名单模板.xlsx");
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
// 设置输出目标和模板
ExcelWriter excelWriter = EasyExcel.write(out).withTemplate(templateInputStream).build();
// 创建 Sheet
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 注意:forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。这会把所有数据放到内存,数据量大时会很耗内存
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(list, fillConfig, writeSheet);
// 填充普通变量
Map<String, Object> map = new HashMap<String, Object>(2);
map.put("className", "软件工程3班");
excelWriter.fill(map, writeSheet);
excelWriter.finish();
// 将 outputStream 转换成 InputStream 并上传到 oss 中
try (InputStream in = new ByteArrayInputStream(out.toByteArray())) {
// todo 在这里处理这个 InputStream,可以上传到 OSS 并返回下载接,也可以做为响应返回给用户
}
} catch (IOException e) {
log.error("export excel error", e);
}