Java使用EasyExcel模版導(dǎo)出詳細(xì)操作教程
一、官方提供方法
easyexcel官方文檔 填充Excel | Easy Excel
官方demo是利用本地模版文件填充并下載到本地
/**
* 復(fù)雜的填充
*
* @since 2.1.1
*/
@Test
public void complexFill() {
// 模板注意 用{} 來表示你要用的變量 如果本來就有"{","}" 特殊字符 用"\{","\}"代替
// {} 代表普通變量 {.} 代表是list的變量
String templateFileName =
TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx";
String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx";
// 方案1
try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) {
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。
// forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用
// 簡(jiǎn)單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
// 如果數(shù)據(jù)量大 list不是最后一行 參照下一個(gè)
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
excelWriter.fill(data(), fillConfig, writeSheet);
excelWriter.fill(data(), fillConfig, writeSheet);
Map<String, Object> map = MapUtils.newHashMap();
map.put("date", "2019年10月9日13:28:28");
map.put("total", 1000);
excelWriter.fill(map, writeSheet);
}
}二、根據(jù)業(yè)務(wù)場(chǎng)景的方法
我用的是web項(xiàng)目,將輸出改為了輸出流 OutputStream
/**
* 導(dǎo)出質(zhì)檢任務(wù)詳情
*
* @param id 任務(wù)主鍵,用于獲取數(shù)據(jù)
*/
@Override
public void exportTask(Long id, HttpServletResponse response) {
ExcelWriter excelWriter = null;
try {
// outputStream:要導(dǎo)出的文件的輸出流
OutputStream outputStream = response.getOutputStream();
// 模版文件
ClassPathResource classPathResource = new ClassPathResource("template/taskTemplate.xlsx");
// 使用模版文件的兩種方式:
// 1、文件路徑:.withTemplate(templateFileName)
// 2、輸入流:.withTemplate(inputStream)
String templateFileName = classPathResource.getFile().getPath();
InputStream inputStream = classPathResource.getInputStream();
excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
// 獲取數(shù)據(jù) dataList columnList formData
Map<String, Object> mapDetail = qmsQcTaskDetailService.getDataListByTaskId(id);
// 調(diào)用微服務(wù)獲取字典 determine 用來翻譯判定結(jié)果
List<SysDictItem> determineItem = remoteDictService.getDictByType("determine").getData();
// 調(diào)用微服務(wù)獲取字典 product_unit 用來翻譯零件單位
List<SysDictItem> unitItem = remoteDictService.getDictByType("product_unit").getData();
/* 1、List以外的數(shù)據(jù) */
QmsQcTask formData = (QmsQcTask) mapDetail.get("formData");
determineItem.stream().filter(item -> StrUtil.equals(formData.getResult(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setResult(sysDictItem.getLabel()));
unitItem.stream().filter(item -> StrUtil.equals(formData.getMaterialUnit(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setMaterialUnit(sysDictItem.getLabel()));
excelWriter.fill(formData, writeSheet);
/* 2、List cols數(shù)據(jù) */
List<Map<String, Object>> cols = new ArrayList<>();
// 查找抽檢數(shù)是多少,即有幾列
int num = formData.getSampleNum();
for (int i = 1; i <= num; i++) {
Map<String, Object> map = new HashMap<>();
// 列名
map.put("label", "" + i);
cols.add(map);
}
// 橫向填充
FillConfig fillConfigCols = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
excelWriter.fill(new FillWrapper("cols", cols), fillConfigCols, writeSheet);
/* 3、List details數(shù)據(jù) */
// 這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。
// forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用
// 簡(jiǎn)單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> data = dataList.get(i);
// 翻譯判定合不合格(因?yàn)闃I(yè)務(wù)和字典不在一個(gè)數(shù)據(jù)庫,所以放在代碼里處理)
String determine = (String) data.get("determine");
determineItem.stream().filter(item -> StrUtil.equals(determine, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put("determine", sysDictItem.getLabel()));
// 如果沒有質(zhì)檢標(biāo)準(zhǔn),每一個(gè)質(zhì)檢值都要轉(zhuǎn)為合不合格
if (StrUtil.isBlank((String) data.get("inspection_standard"))){
int samplingNum = Optional.ofNullable((Integer) data.get("sampling_num")).orElse(0);
for (int j = 1; j <= samplingNum; j++) {
String val = (String) data.get(""+j);
int finalJ = j;
determineItem.stream().filter(item -> StrUtil.equals(val, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put(""+ finalJ, sysDictItem.getLabel()));
}
}
// 給數(shù)據(jù)加上序號(hào)
data.put("index", i + 1);
}
excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);
// 設(shè)置輸出流格式以及文件名:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("質(zhì)檢任務(wù)", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
// 千萬別忘記close關(guān)閉流
if (excelWriter != null) {
excelWriter.close();
}
}
}模版:

效果:

三、說明
1、獲取輸出流
// outputStream:要導(dǎo)出的文件的輸出流 OutputStream outputStream = response.getOutputStream();
2、獲取模版文件
// 模版文件
ClassPathResource classPathResource = new ClassPathResource("template/taskTemplate.xlsx");
// 方式一:路徑
String templateFileName = classPathResource.getFile().getPath();
// 方式二:輸入流
InputStream inputStream = classPathResource.getInputStream();3、創(chuàng)建 ExcelWriter 和 WriteSheet
excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).excelType(ExcelTypeEnum.XLSX).autoCloseStream(Boolean.FALSE).build(); WriteSheet writeSheet = EasyExcel.writerSheet().build();
使用模版文件有兩種方式,使用其中一種就可以:
1.文件路徑:.withTemplate(templateFileName)
2.輸入流:.withTemplate(inputStream)
4、獲取數(shù)據(jù)
Map<String, Object> mapDetail = qmsQcTaskDetailService.getDataListByTaskId(id);
我這里直接調(diào)用了方法,map包含
dataList主要列表數(shù)據(jù) formData 列表外的數(shù)據(jù)
5、填充數(shù)據(jù)
1.獲取字典數(shù)據(jù)
// 調(diào)用微服務(wù)獲取字典 determine 用來翻譯判定結(jié)果
List<SysDictItem> determineItem = remoteDictService.getDictByType("determine").getData();
// 調(diào)用微服務(wù)獲取字典 product_unit 用來翻譯零件單位
List<SysDictItem> unitItem = remoteDictService.getDictByType("product_unit").getData();我的業(yè)務(wù)數(shù)據(jù)和字典不在同一個(gè)數(shù)據(jù)庫內(nèi),sql無法翻譯,所以需要在代碼里翻譯
2.填充列表外的數(shù)據(jù)
/* 1、List以外的數(shù)據(jù) */
QmsQcTask formData = (QmsQcTask) mapDetail.get("formData");
determineItem.stream().filter(item -> StrUtil.equals(formData.getResult(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setResult(sysDictItem.getLabel()));
unitItem.stream().filter(item -> StrUtil.equals(formData.getMaterialUnit(), item.getItemValue())).findFirst().ifPresent(sysDictItem -> formData.setMaterialUnit(sysDictItem.getLabel()));
excelWriter.fill(formData, writeSheet);只要對(duì)象屬性名和模版里寫的標(biāo)記已知就可以了
3.填充缺少的列名
模版:

效果:

因?yàn)槲液蟀氩糠至忻婕暗叫修D(zhuǎn)列,且不固定列數(shù),所以需要橫著填充上列名。注意列表的名字“cols”要對(duì)應(yīng)起來。
使用.direction(WriteDirectionEnum.HORIZONTAL) 設(shè)置為橫向填充。
/* 2、List cols數(shù)據(jù) */
List<Map<String, Object>> cols = new ArrayList<>();
// 查找抽檢數(shù)是多少,即有幾列
int num = formData.getSampleNum();
for (int i = 1; i <= num; i++) {
Map<String, Object> map = new HashMap<>();
// 列名
map.put("label", "" + i);
cols.add(map);
}
// 橫向填充
FillConfig fillConfigCols = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
excelWriter.fill(new FillWrapper("cols", cols), fillConfigCols, writeSheet);4.填充主列表數(shù)據(jù)
/* 3、List details數(shù)據(jù) */
// 這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。
// forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用
// 簡(jiǎn)單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");
/*以下為個(gè)人業(yè)務(wù)處理*/
for (int i = 0; i < dataList.size(); i++) {
Map<String, Object> data = dataList.get(i);
// 翻譯判定合不合格(因?yàn)闃I(yè)務(wù)和字典不在一個(gè)數(shù)據(jù)庫,所以放在代碼里處理)
String determine = (String) data.get("determine");
determineItem.stream().filter(item -> StrUtil.equals(determine, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put("determine", sysDictItem.getLabel()));
// 如果沒有質(zhì)檢標(biāo)準(zhǔn),每一個(gè)質(zhì)檢值都要轉(zhuǎn)為合不合格
if (StrUtil.isBlank((String) data.get("inspection_standard"))){
int samplingNum = Optional.ofNullable((Integer) data.get("sampling_num")).orElse(0);
for (int j = 1; j <= samplingNum; j++) {
String val = (String) data.get(""+j);
int finalJ = j;
determineItem.stream().filter(item -> StrUtil.equals(val, item.getItemValue())).findFirst().ifPresent(sysDictItem -> data.put(""+ finalJ, sysDictItem.getLabel()));
}
}
// 給數(shù)據(jù)加上序號(hào)
data.put("index", i + 1);
}
/*以上為個(gè)人業(yè)務(wù)處理*/
excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);最重要的三句話:
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
List<Map<String, Object>> dataList = (List<Map<String, Object>>) mapDetail.get("dataList");
excelWriter.fill(new FillWrapper("details", dataList), fillConfig, writeSheet);這里注意 入?yún)⒂昧薴orceNewRow 代表在寫入list的時(shí)候不管list下面有沒有空行 都會(huì)創(chuàng)建一行,然后下面的數(shù)據(jù)往后移動(dòng)。默認(rèn) 是false,會(huì)直接使用下一行,如果沒有則創(chuàng)建。forceNewRow 如果設(shè)置了true,有個(gè)缺點(diǎn) 就是他會(huì)把所有的數(shù)據(jù)都放到內(nèi)存了,所以慎用簡(jiǎn)單的說 如果你的模板有l(wèi)ist,且list不是最后一行,下面還有數(shù)據(jù)需要填充 就必須設(shè)置 forceNewRow=true 但是這個(gè)就會(huì)把所有數(shù)據(jù)放到內(nèi)存 會(huì)很耗內(nèi)存
5.設(shè)置輸出流格式以及文件名
// 設(shè)置輸出流格式以及文件名:
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("質(zhì)檢任務(wù)", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");四、思想
我的模版使用了動(dòng)態(tài)的列,不確定有幾列,所以我就寫了一百多個(gè)。

還有另外一種設(shè)想:
如果先把列表外的數(shù)據(jù)填充,然后將主列表的列填充完畢,再把主列表的標(biāo)記寫上,這些操作完畢之后把文件輸出到另外的輸出流上,再將此輸出流轉(zhuǎn)為輸入流,再把主列表的數(shù)據(jù)填充,輸出到response。
理論上可行,但我沒試。
總結(jié)
到此這篇關(guān)于Java使用EasyExcel模版導(dǎo)出的文章就介紹到這了,更多相關(guān)使用EasyExcel模版導(dǎo)出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud網(wǎng)關(guān)(Zuul)如何給多個(gè)微服務(wù)之間傳遞共享參數(shù)
這篇文章主要介紹了SpringCloud網(wǎng)關(guān)(Zuul)如何給多個(gè)微服務(wù)之間傳遞共享參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java的四種常見線程池及Scheduled定時(shí)線程池實(shí)現(xiàn)詳解
這篇文章主要介紹了Java的四種常見線程池及Scheduled定時(shí)線程池實(shí)現(xiàn)詳解,在Java中,我們可以通過Executors類來創(chuàng)建ScheduledThreadPool,Executors類提供了幾個(gè)靜態(tài)方法來創(chuàng)建不同類型的線程池,包括ScheduledThreadPool,需要的朋友可以參考下2023-09-09
idea中maven使用tomcat7插件運(yùn)行run報(bào)錯(cuò)Could not start T
這篇文章主要介紹了idea中maven使用tomcat7插件運(yùn)行run報(bào)錯(cuò)Could not start Tomcat問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09
MyBatis中的關(guān)聯(lián)關(guān)系配置與多表查詢的操作代碼
本文介紹了在MyBatis中配置和使用一對(duì)多和多對(duì)多關(guān)系的方法,通過合理的實(shí)體類設(shè)計(jì)、Mapper接口和XML文件的配置,我們可以方便地進(jìn)行多表查詢,并豐富了應(yīng)用程序的功能和靈活性,需要的朋友可以參考下2023-09-09
Spring?注入靜態(tài)對(duì)象使用三種方式示例
這篇文章主要為大家介紹了Spring注入靜態(tài)對(duì)象使用的三種方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
java學(xué)習(xí)筆記之eclipse+tomcat 配置
俗話說:工欲善其事必先利其器,既然要學(xué)習(xí)java,首先把java的開發(fā)環(huán)境搗鼓一下吧,這里我們來談?wù)別clipse+tomcat的配置方法。2014-11-11
SpringBoot3實(shí)戰(zhàn)教程之實(shí)現(xiàn)接口簽名驗(yàn)證功能
接口簽名是一種重要的安全機(jī)制,用于確保 API 請(qǐng)求的真實(shí)性、數(shù)據(jù)的完整性以及防止重放攻擊,這篇文章主要介紹了SpringBoot3實(shí)戰(zhàn)教程之實(shí)現(xiàn)接口簽名驗(yàn)證功能,需要的朋友可以參考下2025-04-04
Java動(dòng)態(tài)修改配置即時(shí)生效的方式WatchService
這篇文章給大家分享了Java動(dòng)態(tài)修改配置即時(shí)生效的方式WatchService的相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友可以參考學(xué)習(xí)下。2018-06-06

