1、背景
研究始于Web內(nèi)需要導(dǎo)出Excel,且不分頁(yè)或分批次下載纫版。待數(shù)據(jù)量過(guò)多,Apache POI不能很好的兼顧此場(chǎng)景客情。原因始于POI寫(xiě)Excel需要完整寫(xiě)完一個(gè)Exce內(nèi)部結(jié)構(gòu)才可以write其弊。
2、簡(jiǎn)介
FreeMarker是一種Java模板引擎膀斋,可以根據(jù)指定模板生成文件梭伐。且Excel支持已Xml數(shù)據(jù)格式(Ps:word也是可以)。
這里將一個(gè)Excel模板分成N個(gè)片段仰担,類似寫(xiě)文本文件那樣輸出Excel糊识。先輸出Excel頭部?jī)?nèi)容,再生成每個(gè)Sheet的數(shù)據(jù)內(nèi)容部分摔蓝。繼而可實(shí)現(xiàn)一個(gè)Excel赂苗,分段查詢,分段生成贮尉,分段輸出拌滋。服務(wù)端生成一部分,客戶端下載一部分猜谚。即可簡(jiǎn)化代碼败砂,也可降低服務(wù)端壓力。
但這也是有弊端的:
因分頁(yè)查詢魏铅,期間可能出現(xiàn)新增或刪除數(shù)據(jù)昌犹,將會(huì)造成Excel有重復(fù)數(shù)據(jù)或少數(shù)據(jù)。
3览芳、實(shí)現(xiàn)方案
1祭隔、引入FreeMarker
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
注:SpringBoot 有 FreeMarker 的自定裝配類:FreeMarkerAutoConfiguration,需要手動(dòng)忽略路操。
2疾渴、工具類:FreeMarkerExportUtils
/**
* FreeMarker Excel導(dǎo)出工具類<br/>
*
* Example: <br/>
* // 構(gòu)建下載<br/>
* FreeMarkerExportUtils.buildDownload(fileName);<br/>
* // 輸出數(shù)據(jù)<br/>
* FreeMarkerExportUtils.writeExcel(templateHeadName, Arrays.asList());<br/>
* // 關(guān)閉輸出流<br/>
* FreeMarkerExportUtils.flush();<br/>
*
* @author wangzhuhua
* @date 2018/08/26 下午1:39
*
**/
public class FreeMarkerExportUtils {
/** 當(dāng)前輸出流 */
private static ThreadLocal<Writer> writerThreadLocal = new ThreadLocal<>();
/** 默認(rèn)編碼 */
private final static String DefaultCharset = "UTF-8";
/** 模板綁定屬性名稱 */
private final static String TemplatePropName = "data";
/** 這里注意的是利用類加載器動(dòng)態(tài)獲得模板文件的位置 */
private static final String TemplateFolder = "freemarker";
/** 配置信息,代碼本身寫(xiě)的還是很可讀的,就不過(guò)多注解了 */
private static Configuration configuration;
static {
configuration = new Configuration(Configuration.getVersion());
configuration.setClassLoaderForTemplateLoading(FreeMarkerExportUtils.class.getClassLoader(),
"/" + TemplateFolder);
configuration.setDefaultEncoding(DefaultCharset);
configuration.setWhitespaceStripping(false);
}
private FreeMarkerExportUtils() {
}
/**
* 構(gòu)建Excel
*
* @param fileName
* 文件名稱
*
* @throws Exception
* 構(gòu)建異常
*/
public static void buildDownload(String fileName) throws Exception {
Writer writer = WriterThreadLocal.get();
if (writer == null) {
// 輸出頭部信息
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getResponse();
response.reset();
response.setContentType("application/octet-stream;");
response.setHeader("Content-disposition",
"attachment; filename=" + new String((fileName + ".zip").getBytes(DefaultCharset), "ISO8859-1"));
response.setCharacterEncoding(DefaultCharset);
ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
//設(shè)置壓縮方法
zipOutputStream.setMethod(ZipOutputStream.DEFLATED);
zipOutputStream.putNextEntry(new ZipEntry(fileName + ".xls"));
writer = new OutputStreamWriter(zipOutputStream, DefaultCharset);
// 當(dāng)前輸出流
WriterThreadLocal.set(writer);
}
}
/**
* 寫(xiě)Excel
*
* @param templateName
* 模板名稱
* @param obj
* 模板參數(shù)
* @throws Exception
* 輸出異常
*/
public static void writeExcel(String templateName, Object obj) throws Exception {
Writer writer = writerThreadLocal.get();
if (writer == null) {
throw new RuntimeException("請(qǐng)先調(diào)用buildDownload!");
}
// 模板參數(shù)
Map<String, Object> params = new HashMap<>(BigDecimal.ONE.intValue());
params.put(TemplatePropName, obj);
writeTemplate(params, templateName);
}
/**
* flush
*/
public static void flush() throws IOException {
writerThreadLocal.get().flush();
writerThreadLocal.get().close();
}
/**
* 利用FreeMarker輸出模板
*
* @param obj
* 待替換屬性對(duì)象
* @param templateName
* freemarker模板名稱
* @throws Exception
*/
public static void writeTemplate(Object obj, String templateName) throws Exception {
// 獲取模板
Template freemarkerTemplate = configuration.getTemplate(templateName);
// 輸出內(nèi)容
freemarkerTemplate.process(obj, writerThreadLocal.get());
}
}
3、調(diào)用示例
// 以下為示例代碼
String fileName = "測(cè)試導(dǎo)出";
String templateHeadName = "test/test_head.ftl";
String templateContentName = "test/test_content.ftl";
String templateFootName = "test/test_foot.ftl";
// 構(gòu)建下載
FreeMarkerExportUtils.buildDownload(fileName);
// 頭部
FreeMarkerExportUtils.writeExcel(templateHeadName, Arrays.asList());
// 內(nèi)容
for (int i = 0; i < 5; i++) {
// 行數(shù)據(jù)
List<Object> columnDatas = new ArrayList<>();
for (int j = 0; j < 100; j++) {
Map<String, String> map = new HashMap<>();
map.put("name", "wangzhuhua");
map.put("sex", "男");
map.put("age", "100");
columnDatas.add(map);
}
// 輸出行數(shù)據(jù)
FreeMarkerExportUtils.writeExcel(templateContentName, columnDatas);
// 停頓10s
Thread.sleep(10 * 1000);
}
// 底部
FreeMarkerExportUtils.writeExcel(templateFootName, Arrays.asList());
// 關(guān)閉輸出流
FreeMarkerExportUtils.flush();
4屯仗、模板制作
先制作好你需要生成的Excel搞坝,并填充第一行測(cè)試數(shù)據(jù)。右鍵另存為2003或者2004 *.xml格式即可魁袜。模板可以保留部分的Excel格式信息桩撮。大數(shù)據(jù)量Excel需要把模板按需分段輸出敦第。
模板下載:暫時(shí)不知道怎么上傳附件