背景
日常开发中会经常遇到数据需要下载/导出的情况,如果是比较简单的excel,没有指定特殊格式,借助第三方工具(如:hutool)几行代码即可完成,如果需要生成复杂格式的excel,代码处理起来就非常麻烦,此时可以采用加载模板文件渲染数据的方式实现。
功能实现
简单excel生成
引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
代码实现
直接生成文件
代码
@Test
public void testCreateSimpleCreate() {
String outFilePath = "F:\\test-excel\\simple-create.xlsx";
ExcelWriter writer = ExcelUtil.getWriter(outFilePath);
List<Map<String, Object>> maps = this.mockSimpleExcelData();
this.writeHeader(writer);
writer.write(maps);
writer.close();
}
/**
* 写表头
* @param writer
*/
private void writeHeader(ExcelWriter writer ) {
writer.addHeaderAlias("id", "ID");
writer.addHeaderAlias("name", "名称");
writer.addHeaderAlias("gender", "性别");
writer.addHeaderAlias("age", "年龄");
writer.addHeaderAlias("idCard", "身份证号");
}
/**
* 模拟写入 简单的 5 列数据:id 姓名 性别 年龄 身份证号
* @return
*/
private List<Map<String, Object>> mockSimpleExcelData() {
List<Map<String, Object>> list = new ArrayList<>();
int dataCount = 10;
for (int i = 0; i < dataCount; i++) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("id", IdUtil.fastSimpleUUID());
map.put("name", RandomUtil.randomString("赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张", 1).concat(RandomUtil.randomString("一二三四五六七八九十", RandomUtil.randomInt(1,2))));
map.put("gender", i % 2 == 0 ? "男": "女");
map.put("age", RandomUtil.randomInt(18, 35));
map.put("idCard", String.valueOf(RandomUtil.randomString("1234567890", 18)));
list.add(map);
}
return list;
}
结果
文件下载方式
以下代码为示例
public void testDownloadFile(HttpServletResponse response) {
// 通过工具类创建writer,默认创建xls格式
ExcelWriter writer = ExcelUtil.getWriter(true);
this.writeHeader(writer);
List<Map<String, Object>> maps = this.mockSimpleExcelData();
writer.write(maps);
ServletOutputStream out = null;
response.setContentType("application/vnd.ms-excel;charset=utf-8");
try {
// 中文需要使用encode加密
String fileName = URLEncoder.encode("文件下载测试", CharsetUtil.UTF_8);
response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
out = response.getOutputStream();
writer.flush(out, true);
} catch (Exception e) {
e.printStackTrace();
} finally {
writer.close();
}
IoUtil.close(out);
}
通过模板生成
导入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.4</version>
</dependency>
<dependency>
<!-- 可以使用poi的实现也可以用jexcelapi的 -->
<groupId>org.jxls</groupId>
<artifactId>jxls-poi</artifactId>
<version>1.0.15</version>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-jexcel</artifactId>
<version>1.0.7</version>
</dependency>
代码实现
定义模板
下载模板文件
直接生成文件
代码
@Test
public void testExcelByTemplate() throws IOException {
String templatePath = "F:\\test-excel\\template_user.xlsx";
String targetFilePath = "F:\\test-excel\\user.xlsx";
Context context = new Context();
List<Map<String, Object>> data = this.mockSimpleExcelData();
// 这里的参数和模板中定义的参数名需要保持一致
context.putVar("list", data);
BufferedInputStream is = FileUtil.getInputStream(templatePath);
BufferedOutputStream os = FileUtil.getOutputStream(targetFilePath);
JxlsHelper jxlsHelper = JxlsHelper.getInstance();
Transformer transformer = jxlsHelper.createTransformer(is, os);
jxlsHelper.processTemplate(context, transformer);
is.close();
os.flush();
os.close();
}
运行结果
文件下载方式
public void testDownloadFileByTemplate(HttpServletResponse response) throws IOException {
String templateFilePath = "F:\\test-excel\\template_user.xlsx";
File templateFile = new File(templateFilePath);
// 生成临时文件
String templateDir = FileUtil.getParent(templateFilePath, 1);
String randomFileName = IdUtil.fastSimpleUUID();
String outFilePath = StrUtil.concat(true, templateDir, File.separator, randomFileName, ".", FileUtil.getSuffix(templateFile));
File outFile = new File(outFilePath);
List<Map<String, Object>> data = this.mockSimpleExcelData();
Map<String, Object> dataMap = new HashMap<>();
// 这里的参数和模板中定义的参数名需要保持一致
dataMap.put("list", data);
this.renderExcelData(FileUtil.getInputStream(templateFile), FileUtil.getOutputStream(outFile), dataMap);
// 中文需要使用encode加密
String fileName = URLEncoder.encode("模板文件下载测试", CharsetUtil.UTF_8);
// 创建输出流对象
ServletOutputStream outputStream = response.getOutputStream();
//以字节数组的形式读取文件
byte[] bytes = FileUtil.readBytes(outFilePath);
// 设置返回内容格式
response.setContentType("application/octet-stream");
// 设置下载弹窗的文件名和格式(文件名要包括名字和文件格式)
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setHeader("FileName", fileName);
// 返回数据到输出流对象中
outputStream.write(bytes);
// 关闭流对象
IoUtil.close(outputStream);
// 异步删除临时文件
this.asyncDeleteTempFile(outFile);
}
private void renderExcelData(InputStream templateFileStream, OutputStream targetFileStream, Map<String, Object> dataMap) throws IOException {
Context context = new Context();
if (dataMap != null) {
for (String key : dataMap.keySet()) {
context.putVar(key, dataMap.get(key));
}
}
JxlsHelper jxlsHelper = JxlsHelper.getInstance();
Transformer transformer = jxlsHelper.createTransformer(templateFileStream, targetFileStream);
jxlsHelper.processTemplate(context, transformer);
templateFileStream.close();
targetFileStream.flush();
targetFileStream.close();
}
/**
* 异步删除的临时文件
* @param file 待删除的文件数组
*/
public void asyncDeleteTempFile(File ...file) {
final Integer targetDelayTime = 10 ;
// 异步执行删除临时文件方法
CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(targetDelayTime);
// 删除临时文件
if (ArrayUtil.isNotEmpty(file)) {
for (File tempFile : file) {
FileUtil.del(tempFile);
}
}
} catch (Exception e) {
log.info("异步执行的删除临时文件时出错:{}", e);
}
});
}
总结
工具类 | 说明 |
---|---|
hutools | 生成简单的excel文件只需要几行代码即可完成。 |
jxls | 1、生成excel文件需要定对应的模板文件,相对而言生成格式比较复杂的Excel文件难度更小。 2、文件下载时需要先生成临时文件,然后获取到临时文件流才可以下载。 |
jxls相关操作可查看:http://jxls.sourceforge.net/