使用EasyPoi完成復(fù)雜一對(duì)多excel表格導(dǎo)出功能全過(guò)程
業(yè)務(wù)需求
從一個(gè)簡(jiǎn)單的倉(cāng)庫(kù)業(yè)務(wù)說(shuō)起,倉(cāng)庫(kù)業(yè)務(wù),會(huì)有進(jìn)庫(kù)記錄,會(huì)有出庫(kù)記錄,會(huì)有庫(kù)存,客戶的需求就是需要一個(gè)庫(kù)存盤點(diǎn)單,盤點(diǎn)單通俗來(lái)講:將庫(kù)存中每個(gè)商品的出入庫(kù)記錄都統(tǒng)計(jì)出來(lái),看看每個(gè)商品出過(guò)多少貨物,入過(guò)多少貨物,本月庫(kù)存多少,上月庫(kù)存多少。
需求難點(diǎn)
一個(gè)貨物會(huì)出過(guò)多次貨物,入過(guò)多次貨物,導(dǎo)出的 excel 就要做成 一對(duì)多 格式的導(dǎo)出
簡(jiǎn)單舉例:
啤酒:入庫(kù)2次,出庫(kù)3次,最終體現(xiàn)在 excel 中效果如下圖:

通過(guò) EasyPoi 實(shí)現(xiàn)需求
EasyPoi 文檔地址:http://doc.wupaas.com/docs/easypoi/easypoi-1c0u4mo8p4ro8
SpringBoot 使用:
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.2.0</version>
</dependency>
Gradle 使用:
implementation 'cn.afterturn:easypoi-base:4.2.0' implementation 'cn.afterturn:easypoi-annotation:4.2.0' implementation 'cn.afterturn:easypoi-web:4.2.0'
使用 EasyPoi 提供的注解,自定義導(dǎo)出類模板
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import cn.afterturn.easypoi.excel.annotation.ExcelIgnore;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 導(dǎo)出 excel 模板類
*/
@Getter
@Setter
public class ExportTemplate implements Serializable {
@Excel(name = "序號(hào)", needMerge = true, type = 10)
private int index;
@Excel(name = "商品名稱", needMerge = true, width = 30.0)
private String goodName;
@Excel(name = "商品單位", needMerge = true)
private String goodUnit;
@Excel(name = "上月庫(kù)存數(shù)量", needMerge = true, type = 10)
private Integer lastMonthSurplusNum;
@Excel(name = "本月庫(kù)存數(shù)量", needMerge = true, type = 10)
private Integer thisMonthSurplusNum;
@ExcelCollection(name = "本月入庫(kù)信息")
private List<GoodInItem> goodInItems;
@ExcelCollection(name = "本月出庫(kù)信息")
private List<GoodOutItem> goodOutItems;
@Excel(name = "備注", needMerge = true, width = 30.0)
private String remark;
/**
* 入庫(kù)信息
*/
@Getter
@Setter
public static class GoodInItem {
@Excel(name = "入庫(kù)日期", exportFormat = "yyyy-MM-dd", width = 20.50)
private Date purchaseDate;
@Excel(name = "入庫(kù)號(hào)", width = 25.50)
private String purchaseNum;
@Excel(name = "入庫(kù)單價(jià)", type = 10)
private BigDecimal unitPrice;
@Excel(name = "入庫(kù)數(shù)量", type = 10)
private Integer totalNum;
}
/**
* 出庫(kù)信息
*/
@Getter
@Setter
public static class GoodOutItem {
@Excel(name = "出庫(kù)日期", exportFormat = "yyyy-MM-dd", width = 20.50)
private Date outDate;
@Excel(name = "出庫(kù)號(hào)", width = 25.50)
private String sellNum;
@Excel(name = "出庫(kù)數(shù)量", type = 10)
private Integer totalNum;
@Excel(name = "成本金額", type = 10)
private BigDecimal priceIn;
@Excel(name = "銷售金額", type = 10)
private BigDecimal priceOut;
}
}
實(shí)體類中使用的注解作用解釋:
- 1.@Getter lombok 注解,用于給所有屬性提供 getter 方法
- 2.@Setter lombok 注解,用于給所有屬性提供 setter 方法
- 3.@Excel easypoi 注解,name 就等于導(dǎo)出 excel 的列名稱,width 就是寬度,type 就是這個(gè)屬性的類型,1表示文本,默認(rèn)也是文本,10就是數(shù)字,needMerge 表示是否縱向合并單元格,也就是上下列合并
- 4.@ExcelCollection easypoi 注解,name 就等于導(dǎo)出 excel 的列名稱,被此注解標(biāo)注的集合,就等于在其列下面創(chuàng)建對(duì)等數(shù)量的行,就類似于這種

最后模板弄好之后,就可以通過(guò)easypoi 的工具類來(lái)導(dǎo)出,easypoi 推薦的導(dǎo)出工具類如下:

這個(gè)方法的三個(gè)參數(shù)表示含義解釋:
ExportParams:參數(shù)表示Excel 導(dǎo)出參數(shù)設(shè)置類,easypoi 自定義的類pojoClass:你要導(dǎo)出的類模板dataSet:數(shù)據(jù)集合
具體實(shí)現(xiàn)
@GetMapping(value = "export")
public void export(HttpServletRequest req, HttpServletResponse resp) {
List<ExportTemplate> exportData = new ArrayList();
// 步驟1:構(gòu)建要導(dǎo)出excel的數(shù)據(jù)集合
for (int i = 0; i < 5; i++) {
ExportTemplate data = new ExportTemplate();
data.setIndex(i);
data.setGoodName("測(cè)試商品");
data.setGoodUnit("瓶");
data.setLastMonthSurplusNum(5); // 上月庫(kù)存
data.setThisMonthSurplusNum(3); // 本月庫(kù)存
// ... 剩下的就是類似的加值
exportData.add(data);
}
try {
// 步驟2:開(kāi)始導(dǎo)出 excel
ExportParams params = new ExportParams();
params.setTitle("庫(kù)存盤點(diǎn)單標(biāo)題");
params.setSheetName("庫(kù)存盤點(diǎn)單工作表名稱");
params.setType(ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(params, ExportTemplate.class, exportData);
String nowStr = DateTimeFormatter.ofPattern(LocalDateTime.now()).format("yyyyMMddHHmm"); // 時(shí)間串
String fileName = nowStr + "_庫(kù)存盤點(diǎn)單"; // 文件名稱
String tempDir = "C:/Users/huxim/Downloads";
File filePath = new File(tempDir + File.separator);
if (!filePath.exists()) filePath.mkdirs(); // 如果文件目錄不存在就創(chuàng)建這個(gè)目錄
FileOutputStream fos = new FileOutputStream(tempDir + File.separator + fileName);
workbook.write(fos);
fos.close();
resp.setContentType("application/octet-stream");
resp.setCharacterEncoding("utf-8");
response.addHeader("Content-disposition", "attachment; filename="
+ this.makeDownloadFileName(req, fileName));
IOUtils.copy(new FileInputStream(tempFile), response.getOutputStream());
System.out.println("導(dǎo)出成功~~~");
} catch (Exception e) {
throw new RuntimeException("導(dǎo)出 excel 失敗~~~");
}
}
/**
* 判斷是否是 IE 瀏覽器
* 返回對(duì)應(yīng)的字符串格式
*/
public static String makeDownloadFileName(HttpServletRequest request, String fileName) {
String agent = request.getHeader("User-Agent");
byte[] bytes = fileName.getBytes(StandardCharsets.UTF_8);
if (agent.contains("MSIE") || agent.contains("Trident") || agent.contains("Edge")) {
// IE
return new String(bytes, StandardCharsets.UTF_8);
} else {
return new String(bytes, StandardCharsets.ISO_8859_1);
}
}
導(dǎo)出成功后的excel 就類似于如下這種:

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java 中的 Class.forName(類名) 使用及原理解析
Class.forName是Java中用于動(dòng)態(tài)加載類的強(qiáng)大工具,廣泛應(yīng)用于數(shù)據(jù)庫(kù)驅(qū)動(dòng)加載、反射機(jī)制和插件系統(tǒng)等場(chǎng)景,它通過(guò)ClassLoader加載類并執(zhí)行靜態(tài)初始化代碼,但在使用時(shí)需要注意類路徑、初始化副作用和類加載器的選擇等問(wèn)題,感興趣的朋友一起看看吧2024-12-12
Java無(wú)需解壓直接讀取ZIP壓縮包里的文件及內(nèi)容
最近開(kāi)發(fā)的時(shí)候遇到要獲取到zip壓縮包里面的文件內(nèi)容,解決方案就是通過(guò)ZipInputStream來(lái)讀取,下面通過(guò)實(shí)例代碼介紹Java無(wú)需解壓直接讀取ZIP壓縮包里的文件及內(nèi)容,感興趣的朋友跟隨小編一起看看吧2024-03-03
重學(xué)SpringBoot3之日志Logging使用方式
在日常開(kāi)發(fā)中會(huì)遇到不同的異常,日志方便我們?nèi)ヅ挪樘幚?這篇文章主要給大家介紹了關(guān)于重學(xué)SpringBoot3之日志Logging使用方式的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-06-06
詳解如何把Java中if-else代碼重構(gòu)成高質(zhì)量代碼
這篇文章主要介紹了詳解如何把Java中if-else代碼重構(gòu)成高質(zhì)量代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Java中正則表達(dá)式的語(yǔ)法以及matches方法的使用方法
正則表達(dá)式(Regular Expression)是一門簡(jiǎn)單語(yǔ)言的語(yǔ)法規(guī)范,是強(qiáng)大、便捷、高效的文本處理工具,這篇文章主要給大家介紹了關(guān)于Java中正則表達(dá)式的語(yǔ)法以及matches方法的使用方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05
Java雜談之類和對(duì)象 封裝 構(gòu)造方法以及代碼塊詳解
在現(xiàn)實(shí)世界中,真實(shí)存在的東西,比如吉普車,卡丁車,貨車。我們?cè)谡J(rèn)識(shí)它的時(shí)候就會(huì)在腦海中將它抽象為一種類別叫做車。 好了,那再計(jì)算機(jī)世界中,它同樣的也會(huì)這樣做2021-09-09

