excel流式生成

无论是easyExcel或者poi等excel处理框架,在生成excel时都需要同步等待操作。但如果数据量较大,生成excel的时间会比较长,对于用户来说需要等待,影响体验。这时候就需要考虑使用流式生成excel。

下面提供了几种方案实现流失生成,能够在页面上直接开启下载。

  1. 后端处理

excel2017版本之后,文件本身其实是一个zip文件,里面包含了多个xml文件,这些xml文件描述了excel的内容。所以我们可以通过流式生成xml文件,然后将这些xml文件打包成zip文件,最后返回给浏览器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public ExcelGenerator(InputStream template, Integer dataIndex, OutputStream outputStream) throws IOException {
this.index = dataIndex;
zos = new ZipOutputStream(outputStream);
try (ZipInputStream zipInputStream = new ZipInputStream(template)) {
ZipEntry ze = null;
String sheet1 = null;
while ((ze = zipInputStream.getNextEntry()) != null) {
if (StringUtils.equals(ze.getName(), "xl/worksheets/sheet1.xml")) {
sheet1 = IOUtils.toString(zipInputStream, StandardCharsets.UTF_8);
} else {
ZipEntry zeOut = new ZipEntry(ze.getName());
zeOut.setSize(ze.getSize());
zeOut.setTime(ze.getTime());
zos.putNextEntry(zeOut);
IOUtils.copy(zipInputStream, zos);
zos.flush();
zos.closeEntry();

}
}
//准备sheet1的数据
//FIXME: 玩弄xml
ZipEntry entry = new ZipEntry("xl/worksheets/sheet1.xml");
zos.putNextEntry(entry);
int sheetDataIndex = sheet1.indexOf("</sheetData>");
zos.write(sheet1.substring(0, sheetDataIndex).getBytes(StandardCharsets.UTF_8));
finishStr = sheet1.substring(sheetDataIndex);
}
}

//只修改sheetData -> row 的数据
public void render(List<T> views) throws IOException {
for (T t : views) {
zos.write(renderRowView(t));
}
zos.flush();
}

private byte[] renderRowView(T t) {
Map<Integer, String> cells = new TreeMap<>();
ReflectionUtils.doWithFields(t.getClass(), f -> {
ColumnMeta meta = f.getAnnotation(ColumnMeta.class);
try {
Object value = PropertyUtils.getProperty(t, f.getName());
ConvertMeta convert = f.getAnnotation(ConvertMeta.class);
if (convert != null) {
ConvertInfo convertInfo = new ConvertInfo();
convertInfo.setDefaultValue(convert.defaultVaule());
convertInfo.setTargetType(f.getType());
convertInfo.setFormat(convert.format());
//FIXME: 性能问题 缓存实例
IConvert instance = convert.convert().newInstance();
instance.setConvertInfo(convertInfo);
value = instance.convert(value);
}
cells.put(meta.index(), value.toString());
} catch (Exception e) {
}
}, f -> null != f.getAnnotation(ColumnMeta.class));
byte[] row = ("<row r=\"" + index + "\">" +
cells.entrySet().stream().map(e -> {
return "<c r=\"" + getColName(e.getKey()) + index + "\" t=\"inlineStr\"><is><t>" + e.getValue() + "</t></is></c>";
}).collect(Collectors.joining("")) + "</row>").getBytes(StandardCharsets.UTF_8);
index++;
return row;
}

private String getColName(Integer index) {
if (index <= 26) {
return String.valueOf((char) ('A' + index - 1));
} else {
return String.valueOf((char) ('A' + index / 26 - 1)) + String.valueOf((char) ('A' + index % 26 - 1));
}
}


public void finish() throws IOException {
//完成sheet1的xml
zos.write(finishStr.getBytes(StandardCharsets.UTF_8));
zos.flush();
zos.closeEntry();
zos.finish();
}
  1. 前端处理

前端可以通过restful接口获取到excel内容,然后通过blob对象生成excel文件,最后通过a标签的download属性下载excel文件。

https://medium.com/@Nopziiemoo/create-excel-files-using-javascript-without-all-the-fuss-2c4aa5377813