一、EasyPOI是什么
EasyPDI是對 Apche POI庫的封裝,可以通過一些簡單的操作就可以進行Excel文件的導入導出,這這個庫中還封裝了一些獨特的功能:
- 基於注解的導入導出,修改注解就可以修改Excel
- 支持常用的樣式自定義
- 基於map可以靈活定義的表頭字段
- 支持一堆多的導出,導入
- 支持模板的導出,一些常見的標簽,自定義標簽
- 支持HTML/Excel轉換,如果模板還不能滿足用戶的變態需求,請用這個功能
- 支持word的導出,支持圖片,Excel
一直以來,使用EasyPOI做了不少導入導出的需求,但是每次做完都是臨時去看官方文檔現學現用,正巧最近朋友遇到這么個需求,用到了EasyPOI來完成導入,我也正好整理整理EasyPOI的導入用法。
二、EasyPOI的使用
引入依賴
SpringBoot封裝了對EasyPOI的starter,可以直接引用,也可以單獨引入
<!--easypoi-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
單獨引入:
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.1.0</version>
</dependency>
EasyPOI的注解
EasyPOI是通過注解來表明需要導入導出的類、字段,一個實體類代表的是表格里的一行,實體類里面的字段代表着一行里面的一列。現在注解一共有5個,分別是:
- @Excel 作用到類字段上面,是對Excel一列的一個描述
- @ExcelCollection 表示一個集合,主要針對一對多的導出,比如一個老師對應多個科目,科目就可以用集合表示
- @ExcelEntity 表示一個繼續深入導出的實體,但他沒有太多的實際意義,只是告訴系統這個對象里面同樣有導出的字段
- @ExcelIgnore 和名字一樣表示這個字段被忽略跳過這個導導出
- @ExcelTarget 這個是作用於最外層的對象,描述這個對象的id,以便支持一個對象可以針對不同導出做出不同處理
注解基本使用
下面是對EasyPOI注解的基本使用示例;
使用示例:
船艇導出實體類
/**
* 船艇導出實體類
* @author jianhao
* @date 2021-11-10 13:58
*/@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@ExcelTarget("exportModelBoat")
public class ExportModelBoat {
/**
* 船名稱
*/
@NotBlank(message = "船名不能為空")
@Excel(name = "船名稱",orderNum = "0",needMerge = true,width = 15)
String name;
/**
* 船只類型
*/
@NotBlank(message = "類型不能為空")
@Excel(name = "船類型",orderNum = "1",needMerge = true)
String type;
/**
* 產品ID
*/
@Excel(name = "產品ID",orderNum = "2",needMerge = true)
long boatid;
/**
* 硬件ID
*/
@Excel(name = "硬件ID",orderNum = "3",needMerge = true)
String hardid;
/**
* 設備序列號
*/
@Excel(name = "設備序列號",orderNum = "4",needMerge = true)
String serial;
/**
* 設備序列編號
*/
@Excel(name = "設備序列編號",orderNum = "5",needMerge = true,width = 20)
String hashid;
/**
* 描述信息
*/
@Length(max = 200,message = "描述信息不能超過200個字符")
@Excel(name = "描述信息",orderNum = "6",needMerge = true,width = 30)
String description;
/**
* 創建時間
*/
@ExcelIgnore
String createTime;
/**
* 最后修改時間
*/
@ExcelIgnore
String lastUpdateTime;
/**
* 該船所屬設備集合
*/
@ExcelCollection(name = "該船所屬設備",orderNum = "9")
List<ExportModelDevice> deviceList = new ArrayList<>();
/**
* 該船所屬信道集合
*/
@ExcelCollection(name = "該船其他信息",orderNum = "10")
List<ExportModelChannel> channelList = new ArrayList<>();
/**
* 該船出廠校驗信息
*/
@ExcelCollection(name = "該船出廠校驗",orderNum = "11")
List<ExportModelInspection> inspectionList = new ArrayList<>();
}
Excel注解
以上示例中類字段使用了Excel注解來標注,來表示在導出該對象時顯示的字段名以及所屬單元格的格式,該注解有許多的值可以設置,如下表:
屬性 | 類型 | 默認值 | 功能 |
---|---|---|---|
name | String | null | 列名,支持name_id |
needMerge | boolean | fasle | 是否需要縱向合並單元格(用於含有list中,單個的單元格,合並list創建的多個row) |
orderNum | String | “0” | 列的排序,支持name_id |
replace | String[] | {} | 值得替換 導出是{a_id,b_id} 導入反過來 |
savePath | String | “upload” | 導入文件保存路徑,如果是圖片可以填寫,默認是upload/className/ IconEntity這個類對應的就是upload/Icon/ |
type | int | 1 | 導出類型 1 是文本 2 是圖片,3 是函數,10 是數字 默認是文本 |
exportFormat | String | “” | 導出的時間格式,以這個是否為空來判斷是否需要格式化日期 |
importFormat | String | “” | 導入的時間格式,以這個是否為空來判斷是否需要格式化日期 |
format | String | “” | 時間格式,相當於同時設置了exportFormat 和 importFormat |
databaseFormat | String | “yyyyMMddHHmmss” | 導出時間設置,如果字段是Date類型則不需要設置 數據庫如果是string 類型,這個需要設置這個數據庫格式,用以轉換時間格式輸出 |
isColumnHidden | boolean | false | 導出隱藏列 |
width | double | 10 | 列寬 |
ExcelCollcetions注解
該注解是表明該字段是一個集合,會讓多條數據以對象的一個字段來顯示(多條數據也就意味着多行數據)
屬性 | 類型 | 默認值 | 功能 |
---|---|---|---|
id | String | null | 定義ID |
name | String | null | 定義集合列名,支持nanm_id |
orderNum | int | 0 | 排序,支持name_id |
type | Class<?> | ArrayList.class | 導入時創建對象使用 |
ExcelEntity注解
標記是不是導出excel 標記為實體類,一遍是一個內部屬性類,標記是否繼續穿透,可以自定義內部id
屬性 | 類型 | 默認值 | 功能 |
---|---|---|---|
id | String | null | 定義ID |
使用EasyPOI進行導入導出
沿用船艇實體類,在服務類代碼中完成表格的導出;
在EasyPOI中分別針對導入和導出定義了工具類,分別是 ExcelExportUtil
和 ExcelImportUtil
2個類,針對這2個類可以進行導入導出操作。
數據導出成Excel表格
數據導出成Excel表格需要完成幾個步驟:
- 准備好數據源
- 設置好導出Excel的文件格式以及sheet名
- 返回輸出流
數據源從數據庫中獲取,將獲取到的數據轉化成被Excel注解標注的類,數據源就准備好了;然后通過 ExcelExportUtils
提供的方法來構建 Workbook
對象,最后將文件流寫入到response中即可。
使用示例:
// 從數據庫獲取數據
List<ModelBoat> boatList = boatService.findAll();
//從數據庫獲取數據並且將獲取的數據進行轉化
//這里使用的是Springboot Reactive 異步框架,獲取數據和轉化的操作自己定義即可。
List<ExportModelBoat> exportModelBoatList = new ArrayList<>();
dataStorage.getBoatRepository()
.findAll(new Sort(Sort.Direction.ASC,"createTime"))
.collectList()
.flatMap(list ->{
//根據船id去查對應的信道對象、出廠校驗對象、設備對象
for (ModelBoat boat : list) {
ExportModelBoat exportModelBoat = new ExportModelBoat(boat);
exportModelBoatList.add(exportModelBoat);
}
return Mono.empty();
}).block();
//構建ExportParms對象,該對象可以設置該Excel文件的標題行、sheet名稱以及導出的文件類型(HSSF、XSSF)
ExportParams params = new ExportParams("標題行","sheet名",ExcelType.XSSF);
//然后將該params對象傳入ExcelExportUtil類的exportExcel方法中,獲取到Workbook對象
//exportExcel方法需要傳入3個參數,一個是params對象,一個是被注解標注的導出類,另外一個就是數據源
//需要注意的是,第二個參數和第三個參數的類型需要對應上
Workbook sheets = ExcelExportUtil.exportExcel(params, ExportModelBoat.class, exportModelBoatList);
//設置返回流的格式,避免亂碼
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 這里URLEncoder.encode可以防止中文亂碼 當然和easyexcel沒有關系
String fileName = URLEncoder.encode("ExayPOI文件導出", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
//最后返回寫入返回流中
sheets.write(response.getOutputStream());
根據以上的步驟,數據導出成Excel文件也就完成了。
導出效果:
需要注意的是,如果導出對象中存在一對多的情況,也就是有字段是集合類型的,那么該類其他字段的Excel注解需要加上一個值,
needMerge
= true,如果不設置的話,同一個對象的字段就不會縱向合並。
Excel表格數據導入
表格的數據導入與數據導出的步驟是一樣的,通過ExcelImportUtil
工具類來實現數據導入。但是要注意的是文件表格的列名格式要和代碼中被注解標注類的字段要保持一致,字段不一致的就不會解析成對象。
導入文件需要進行以下這幾個步驟:
- 新建 ImportParams 對象,用於設置導入文件時的一些信息
- 獲取文件,讀取該文件
- 最終獲得結果List
下面是示例:
//新建 Importarams 對象
ImportParams importParams = new ImportParams();
//設置表頭所占行數、標題行所占行數
importParams.setHeadRows(2);
importParams.setTitleRows(0);
//導入文件
//第一個參數是文件(可以是File對象,也可以是文件流),第二個對象是要被解析成的pojo類,第三個是導入參數對象
//返回值是解析成功的對象集合
List<ExportModelBoat> list = ExcelImportUtil.importExcel(file.getInputStream(), ExportModelBoat.class, importParams);
//數據存儲操作
boatService.saveAll(List);
數據導入時的數據校驗
在數據導入時,會進行字段的校驗,如果校驗不通過則解析失敗並且提示返回錯誤的行數以及消息。EasyPOI已經提供了這個功能選項, EasyPOI 支持 Hibernate Validator
,所以直接使用就可以了,因為要將錯誤信息以及錯誤行號返回,需要用到 EasyPOI 的高級用法,實現 IExcelDataModel
與 IExcelModel
接口,IExcelDataModel
負責設置行號,IExcelModel
負責設置錯誤信息。
實現數據導入的數據校驗要進行以下步驟:
- 新建的ImportParms對象需要開啟校驗功能
- 實體類加入
Hibernate Validator
注解,開啟校驗功能 - 導入的解析類對象需要實現
IExcelDataModel
和IExcelModel
接口 - 數據校驗
- 返回校驗結果以及錯誤提示
在上面的導入數據示例中,新建了一個ImportParams
對象,如果需要開啟校驗功能,需要多加一行代碼:
ImportParams importParams = new ImportParams();
//開啟校驗功能
importParams.setNeedVerify(true);
開啟校驗功能后,在實體類中需要實現IExcelDataModel
和 IExcelModel
接口,重寫接口方法
如果使用到了 @Pattern 注解,則字段類型必須是 String 類型,否則會拋出異常
public class ExportModelBoat implements IExcelDataModel, IExcelModel {
/**
* 船名稱
*/
@NotBlank(message = "船名不能為空")
@Excel(name = "船名稱",orderNum = "0",needMerge = true,width = 15)
String name;
/**
* 描述信息
*/
@Length(max = 200,message = "描述信息不能超過200個字符")
@Excel(name = "描述信息",orderNum = "6",needMerge = true,width = 30)
String description;
/**
* 內部ip地址
*/
@Pattern(regexp = "^((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.){3}(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])$",
message = "ip地址格式錯誤")
@Excel(name = "IP地址",orderNum = "1",width = 15)
String ip;
//省略其他字段
/**
* 行號
*/
private int rowNum;
/**
* 錯誤消息
*/
private String errorMsg;
@Override
Integer getRowNum(
//這里設置加一是因為行數是從0開始算
return rowNum + 1;
);
@Override
void setRowNum(Integer var1){
this.rowNum = var1;
};
@Override
String getErrorMsg(
return errorMsg;
);
@Override
void setErrorMsg(String var1){
this.errorMsg = var1;
};
}
如果開啟數據校驗,那么數據導入的方式也將改變,原本是調用 ExcelImportUtil
類的 importExcel
方法實現導入,現在需要切換到另外一個方法來實現導入,通過 importExcelMore()
方法導入,該方法所需參數與未開啟校驗導入是一致的,這兩者的區別是返回值。
該方法的返回值是 ExcelImportResult<T>
類型的,代碼如下:
public class ExcelImportResult<T> {
//校驗通過的結果集合
private List<T> list;
//校驗失敗的結果集合
private List<T> failList;
//是否校驗失敗
private boolean verifyFail;
private Workbook workbook;
private Workbook failWorkbook;
private Map<String, Object> map;
}
進行校驗之后,會將校驗成功的結果放入 list
字段中,校驗失敗的放入 failist
字段中,並且設置 verifyFail
值;
校驗過程中,如果校驗失敗,那么就會將失敗所在的行以及錯誤信息寫入到該對象的 rowNum
字段和 errorMsg
字段中,這2個字段是實現了接口而擁有的。
示例代碼:
//創建數據引入設置對象,設置標題行、表頭位置,以及開啟校驗功能
ImportParams importParams = new ImportParams();
importParams.setHeadRows(2);
importParams.setTitleRows(0);
importParams.setNeedVerify(true);
ExcelImportResult<ExportModelBoat> resultList = ExcelImportUtil.importExcelMore(file.getInputStream(), ExportModelBoat.class, importParams);
//獲取校驗信息
System.out.println("是否校驗失敗: " + resultList.isVerfiyFail());
System.out.println("校驗失敗的集合:" + JSONObject.toJSONString(resultList.getFailList()));
System.out.println("校驗通過的集合:" + JSONObject.toJSONString(resultList.getList()));
//輸出
for (ExportModelBoat entity : result.getFailList()) {
String msg = "第" + entity.getRowNum() + "行的錯誤是:" + entity.getErrorMsg();
System.out.println(msg);
}
以上就是校驗的代碼情況,但是這部分只能進行基本部分的校驗,但是對ExcelCollection標注集合里的對象卻校驗不了。
對Excel集合里的對象進行校驗
EasyPOI對被ExcelColleciton注解所標注的集合對象校驗不了,那么我們就可以自己手動開始校驗,使用Hibernate Validator
框架的校驗功能進行手動校驗。這里需要封裝工具類,示例代碼如下:
/**
* 校驗工具類
* @author jianhao.chen
*/
public class ValidatorUtils {
private static Validator validatorFast = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
private static Validator validatorAll = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
/**
* 校驗遇到第一個不合法的字段直接返回不合法字段,后續字段不再校驗
* @Time 2021年11月11日 上午11:36:13
* @param <T>
* @param domain
* @return
* @throws Exception
*/
public static <T> Set<ConstraintViolation<T>> validateFast(T domain) throws Exception {
Set<ConstraintViolation<T>> validateResult = validatorFast.validate(domain);
if(validateResult.size()>0) {
System.out.println(validateResult.iterator().next().getPropertyPath() +":"+ validateResult.iterator().next().getMessage());
}
return validateResult;
}
/**
* 校驗所有字段並返回不合法字段
* @Time 2021年1月11日 上午11:37:55
* @param <T>
* @param domain
* @return
* @throws Exception
*/
public static <T> Set<ConstraintViolation<T>> validateAll(T domain) throws Exception {
Set<ConstraintViolation<T>> validateResult = validatorAll.validate(domain);
if(validateResult.size()>0) {
Iterator<ConstraintViolation<T>> it = validateResult.iterator();
while(it.hasNext()) {
ConstraintViolation<T> cv = it.next();
System.out.println(cv.getPropertyPath()+":"+cv.getMessage());
}
}
return validateResult;
}
}
以上工具類方法會返回一個Set集合,該集合存放這 ConstraintViolation
對象,該對象可以根據方法 getPropertyPath()
來獲取校驗失敗的字段,根據 getMessage()
方法獲取錯誤信息。
示例代碼:
//創建數據引入設置對象,設置標題行、表頭位置,以及開啟校驗功能
ImportParams importParams = new ImportParams();
importParams.setHeadRows(2);
importParams.setTitleRows(0);
importParams.setNeedVerify(true);
ExcelImportResult<ExportModelBoat> resultList = ExcelImportUtil.importExcelMore(file.getInputStream(), ExportModelBoat.class, importParams);
//獲取校驗信息
System.out.println("是否校驗失敗: " + resultList.isVerfiyFail());
System.out.println("校驗失敗的集合:" + JSONObject.toJSONString(resultList.getFailList()));
System.out.println("校驗通過的集合:" + JSONObject.toJSONString(resultList.getList()));
//對所有數據進行集合校驗
ArrayList<ExportModelBoat> exportModelBoats = new ArrayList<>();
exportModelBoats.addAll(resultList.getList());
exportModelBoats.addAll(resultList.getFailList());
//假設對象里面有一個集合名較deviceList,
for (ExportModelDevice exportModelDevice : boat.getDeviceList()) {
//獲取校驗結果
Set<ConstraintViolation<T>> validateResult = ValidatorUtils.validateAll(exportModelDevice);
//如果校驗失敗,那么該返回值對象的size會大於0,反之則校驗成功
if(validateResult.size()>0) {
//獲取迭代器
Iterator<ConstraintViolation<T>> it = validateResult.iterator();
while(it.hasNext()) {
ConstraintViolation<T> cv = it.next();
//根據getPropertyPath()方法、getMessage()方法來獲取校驗失敗的字段以及錯誤提示
System.out.println(cv.getPropertyPath()+":"+cv.getMessage());
}
}
}
根據以上代碼之后就可以校驗集合對象的數據,可以滿足導入導出的Excel表格需求。