1.Excel文件的簡單導入和導出
項目源碼:https://github.com/zhongyushi-git/springboot-easypoi.git。后台在easypoi-demo-admin目錄下,前端在easypoi-demo目錄下。
!!!說明:源碼中可能與下面的介紹的代碼稍有差異,請以源碼為准。
1.1准備工作
1)首先新建一個SpringBoot的項目,搭建基本的環境訪問數據,詳見源碼。
2)導入easypoi依賴
定義版本
<easypoi.version>4.1.0</easypoi.version>
坐標:這里是以springmvc的坐標導入的,適用大部分功能。如果需求不多,可以直接導入springboot對應的坐標,二者選一。選擇依據就是如果報錯,就換另一種坐標即可。
<!--easypoi-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>${easypoi.version}</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>${easypoi.version}</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>${easypoi.version}</version>
</dependency>
springboot的坐標
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-spring-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
3)創建Excel操作的工具類ExcelUtils
package com.example.easypoidemoadmin.utils;
import cn.afterturn.easypoi.cache.manager.POICacheManager;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.ExcelXorHtmlUtil;
import cn.afterturn.easypoi.excel.entity.ExcelToHtmlParams;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.TemplateExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.word.WordExportUtil;
import cn.afterturn.easypoi.word.parse.ParseWord07;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Excel導入導出工具類
*/
public class ExcelUtils {
/**
* excel 導出
*
* @param list 數據列表
* @param fileName 導出時的excel名稱
* @param response
*/
public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, fileName, response);
}
/**
* 默認的 excel 導出
*
* @param list 數據列表
* @param fileName 導出時的excel名稱
* @param response
*/
private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
//把數據添加到excel表格中
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
downLoadExcel(fileName, response, workbook);
}
/**
* excel 導出
*
* @param list 數據列表
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param response
* @param exportParams 導出參數(標題、sheet名稱、是否創建表頭,表格類型)
*/
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) throws IOException {
//把數據添加到excel表格中
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
downLoadExcel(fileName, response, workbook);
}
/**
* excel 導出
*
* @param list 數據列表
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param exportParams 導出參數(標題、sheet名稱、是否創建表頭,表格類型)
* @param response
*/
public static void exportExcel(List<?> list, Class<?> pojoClass, String fileName, ExportParams exportParams, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* excel 導出
*
* @param list 數據列表
* @param title 表格內數據標題
* @param sheetName sheet名稱
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param response
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF));
}
/**
* excel 導出
*
* @param list 數據列表
* @param title 表格內數據標題
* @param sheetName sheet名稱
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param isCreateHeader 是否創建表頭
* @param response
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) throws IOException {
ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF);
exportParams.setCreateHeadRows(isCreateHeader);
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* excel下載
*
* @param fileName 下載時的文件名稱
* @param response
* @param workbook excel數據
*/
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
workbook.write(response.getOutputStream());
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 導入
*
* @param file excel文件
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file, Class<T> pojoClass) throws IOException {
return importExcel(file, 1, 1, pojoClass);
}
/**
* excel 導入
*
* @param filePath excel文件路徑
* @param titleRows 表格內數據標題行
* @param headerRows 表頭行
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
if (StringUtils.isBlank(filePath)) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
params.setNeedSave(true);
params.setSaveUrl("/excel/");
try {
return ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
} catch (NoSuchElementException e) {
throw new IOException("模板不能為空");
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 導入
*
* @param file 上傳的文件
* @param titleRows 表格內數據標題行
* @param headerRows 表頭行
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
if (file == null) {
return null;
}
try {
return importExcel(file.getInputStream(), titleRows, headerRows, pojoClass);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 導入
*
* @param inputStream 文件輸入流
* @param titleRows 表格內數據標題行
* @param headerRows 表頭行
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(InputStream inputStream, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
if (inputStream == null) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
params.setSaveUrl("/excel/");
params.setNeedSave(true);
try {
return ExcelImportUtil.importExcel(inputStream, pojoClass, params);
} catch (NoSuchElementException e) {
throw new IOException("excel文件不能為空");
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
}
4)創建數據庫db2020及表user,執行腳本在根目錄下。
5)excel表格要導入的數據文件在項目根路徑的template文件夾下
6)使用vue-cli新建一個vue的項目,並安裝需要的插件。項目對axios進行了封裝,調用的時候,直接在js中使用即可,詳見源碼。
7)最后一點,要配置文件中加一行配置
#easypoi啟用覆蓋
spring
main:
allow-bean-definition-overriding: true
1.2導入
excel文件的導入,主要就是把文件上傳之后把內容讀取出來進行相應的操作。
1)編寫controller導入接口,service及dao詳見源碼。
/**
* 導入數據
* @param file
* @return
* @throws IOException
*/
@RequestMapping(value = "/import", method = RequestMethod.POST)
public CommonResult importExcel(@RequestParam("file") MultipartFile file) throws IOException {
List<User> list = ExcelUtils.importExcel(file, User.class);
int i = userService.insertByBatch(list);
if (i != 0) {
return new CommonResult(200, "導入成功");
} else {
return new CommonResult(444, "導入失敗");
}
}
2)新建User實體類,給屬性添加@Excel注解
package com.example.easypoidemoadmin.entity;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author zhongyushi
* @date 2020/6/24 0024
* @dec 用戶實體
*/
@Data
@TableName(value = "User")
public class User {
/**
* 用戶名
*/
@TableId(value = "username")
@Excel(name = "用戶名",)
private String username;
/**
* 姓名
*/
@TableField(value = "name")
@Excel(name = "姓名")
private String name;
/**
* 年齡
*/
@TableField(value = "age")
@Excel(name = "年齡")
private Integer age;
/**
* 性別,0表示男,1表示女
*/
@TableField(value = "sex")
@Excel(name = "性別",replace = {"男_0", "女_1"})
private String sex;
/**
* 籍貫
*/
@TableField(value = "address")
@Excel(name = "籍貫")
private String address;
}
需要注意的是,上述的導入的excel內容必須包含表頭和標題,否則讀取不到內容。在性別這里,分別使用數字代替文字,存儲方便。
3)頁面導入的組件
<el-upload class="upload-demo" action="" :limit="1" :http-request="importExcel" :show-file-list="false" :file-list="fileList">
<el-button size="small" type="primary" icon="el-icon-upload">導入</el-button>
</el-upload>
4)頁面導入的方法
//導入
importExcel(param) {
const formData = new FormData()
formData.append('file', param.file)
home.upload(formData).then(res => {
if (res.code == 200) {
this.fileList = []
this.$message.success("導入成功")
this.getList()
} else {
this.$message.error("導入失敗")
}
}).catch(err =>{
console.log(err)
this.$message.error("導入失敗")
})
}
導入的模板在后台代碼的項目根目錄下的template目錄下。
5)注意事項
A:excel表格的表頭必須和@Excel的name屬性一樣,否則讀取不到數據。
B:若導入的字段包含日期類型,那么需要指定導入時的日期的格式並標明是必導入字段,如下所示,excel的內容的日期也需要是這種格式
@Excel(name = "日期",isImportField = "true", importFormat = "yyyy-MM-dd" ,databaseFormat = "yyyy-MM-dd")
C:若導出的字段包含日期類型,那么需要指定導出的格式
@Excel(name = "日期",exportFormat = "yyyy-MM-dd", databaseFormat = "yyyy-MM-dd")
二者綜合的代碼如下,下一小節的導出日期就不再說明。
@Excel(name = "日期",isImportField = "true",exportFormat = "yyyy-MM-dd", importFormat = "yyyy-MM-dd" ,databaseFormat = "yyyy-MM-dd")
1.3導出
導入就是根據查詢的條件把查詢結果先寫到excel表格中,然后下載這個excel即可。
1)編寫controller導出接口,service及dao詳見源碼。
/**
* 導出數據,使用map接收
*
* @param map
* @param response
* @throws IOException
*/
@PostMapping("/exportExcel")
public void exportExcel(@RequestBody Map<String, Object> map, HttpServletResponse response) throws IOException {
IPage<User> iPage = userService.getList((String) map.get("name"), (Integer) map.get("page"), (Integer) map.get("limit"));
ExcelUtils.exportExcel(iPage.getRecords(), (String) map.get("title"), (String) map.get("sheetName"), User.class, (String) map.get("fileName"), response);
}
2)給實體類@Excel注解添加其他屬性
package com.example.easypoidemoadmin.entity;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author zhongyushi
* @date 2020/6/24 0024
* @dec 用戶實體
*/
@Data
@TableName(value = "User")
public class User {
/**
* 用戶名
*/
@TableId(value = "username")
@Excel(name = "用戶名", orderNum = "0", width = 30)
private String username;
/**
* 姓名
*/
@TableField(value = "name")
@Excel(name = "姓名", orderNum = "1", width = 30)
private String name;
/**
* 年齡
*/
@TableField(value = "age")
@Excel(name = "年齡", orderNum = "2", width = 30)
private Integer age;
/**
* 性別,0表示男,1表示女
*/
@TableField(value = "sex")
@Excel(name = "性別", orderNum = "3", width = 30,replace = {"男_0", "女_1"})
private String sex;
/**
* 籍貫
*/
@TableField(value = "address")
@Excel(name = "籍貫", orderNum = "4", width = 30)
private String address;
}
3)頁面導出的方法
//導出
exportExcel() {
this.downloadLoading = true
home.exportExcel({
title: '用戶基本信息',
sheetName: '用戶信息',
fileName: '用戶信息表',
name: this.pageData.name,
page: this.pageData.page,
limit: this.pageData.limit,
}).then(res => {
//使用js下載文件
fileDownload(res, '用戶信息表.xlsx')
}).finally(() => {
this.downloadLoading = false;
});
},
這里使用到了js-file-download插件,它是用來幫助下載文件的。當下載文件時,很多時候都是在地址欄輸入url后瀏覽器自動幫忙下載,但是要統一請求方式,就把返回的二進制文件交給js-file-download進行處理后再下載。需要注意的是,這個導出的請求,我封裝了一個單獨的方法,需要指定響應的方式,否則無法下載后的文件是空的,方法截圖如下:

1.4圖片的導出
有了上面的導出基礎,圖片的導出就很簡單了。
1)新建一個實體類,用於和上面的實體類區分
package com.example.easypoidemoadmin.entity;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
/**
* @author zhongyushi
* @date 2020/6/26 0026
* @dec 描述
*/
@Data
public class Company {
@Excel(name = "公司名稱",width =20)
private String name;
/**
* type為 2 表示字段類型為圖片
* imageType為 1 表示從file讀取
*/
@Excel(name = "公司logo",width =20,type = 2,imageType = 1)
private String logo;
@Excel(name = "公司介紹",width =100)
private String dec;
public Company(String name,String logo,String dec){
this.name=name;
this.logo=logo;
this.dec=dec;
}
}
2)創建接口,圖片請自行下載。
/**
* 圖片的導出
*
* @param response
* @throws IOException
*/
@PostMapping("/imgexport")
public void imgExport(HttpServletResponse response,@RequestBody Map<String, Object> map) throws IOException {
List<Company> list = new ArrayList<>();
//圖片的路徑自定義,但必須要正確
list.add(new Company("百度", "E:/img/1.jpg", "百度一下你就知道"));
list.add(new Company("騰訊", "E:/img/3.jpg", "騰訊qq,交流的世界"));
list.add(new Company("阿里巴巴", "E:/img/2.jpg", "阿里巴巴,馬雲的驕傲"));
String fileName = map.get("fileName").toString();
ExcelUtils.exportExcel(list, fileName, fileName, Company.class, fileName, response);
}
3)在頁面添加導出的按鈕,點擊按鈕即可進行下載,下載的文件如圖

1.5圖片的導入
1)給Company對象加上無參構造,否則會出現異常
public Company(){}
2)導入接口
/**
* 導入圖片
* @param file
* @return
* @throws IOException
*/
@PostMapping("/imgimport")
public CommonResult imgImport(@RequestParam("file") MultipartFile file) throws IOException {
List<Company> list = ExcelUtils.importExcel(file, Company.class);
return new CommonResult(200,"導入成功",list);
}
3)參考excel的導入,添加一個導入的按鈕和請求的方法,詳見源碼
4)點擊excel圖片上傳,把上一步導出的文件進行導入,看到瀏覽器返回的數據如圖

1.6excel模板導出文件
也可以使用固定的模板來導出excel。
1)在工具類添加方法
/**
* 根據模板生成excel后導出
* @param templatePath 模板路徑
* @param map 數據集合
* @param fileName 文件名
* @param response
* @throws IOException
*/
public static void exportExcel(TemplateExportParams templatePath, Map<String, Object> map,String fileName, HttpServletResponse response) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(templatePath, map);
downLoadExcel(fileName, response, workbook);
}
2)編寫模板excel。截圖如下,模板文件在項目根路徑的template文件夾下:

在兩個大括號里寫對應的數據名稱。$fe用來遍歷數據,fe的寫法 fe標志 : list數據 單個元素數據(默認t,不需要寫) {{$fe: maplist t.id }}
3)接口
/**
* 使用模板excel導出
*
* @param response
* @throws Exception
*/
@PostMapping("/excelTemplate")
public void makeExcelTemplate(HttpServletResponse response, @RequestBody Map<String, Object> param) throws Exception {
TemplateExportParams templatePath = new TemplateExportParams("E:/excel/用戶信息文件模板.xls");
Map<String, Object> map = new HashMap<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
map.put("date", sdf.format(new Date()));
map.put("user", "admin");
IPage<User> ipages = userService.getList("", 1, 10);
map.put("userList", ipages.getRecords());
ExcelUtils.exportExcel(templatePath, map, param.get("fileName").toString(), response);
}
在接口中,指定模板文件的路徑,然后給定數據,map的key值要和模板的值保持一致。
4)頁面添加按鈕和請求方法,見源碼。點擊即可下載。
1.7excel轉html
1)在工具類添加方法
/**
* excel轉html預覽
* @param filePath 文件路徑
* @param response
* @throws Exception
*/
public static void excelToHtml(String filePath,HttpServletResponse response) throws Exception{
ExcelToHtmlParams params = new ExcelToHtmlParams(WorkbookFactory.create(POICacheManager.getFile(filePath)),true);
response.getOutputStream().write(ExcelXorHtmlUtil.excelToHtml(params).getBytes());
}
2)編寫接口
/**
* EXCEL轉html預覽
*/
@GetMapping("previewExcel")
public void excelToHtml(HttpServletResponse response) throws Exception {
ExcelUtils.excelToHtml("E:/excel/用戶信息導入模板.xlsx",response);
}
3)頁面添加按鈕和請求方法,見源碼。點擊即可在彈框中顯示。
2.Word文件導出
2.1使用word模板導出
1)導入easypoi-base的依賴
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>${easypoi.version}</version>
</dependency>
2)在工具類加兩個方法
/**
* word下載
*
* @param fileName 下載時的文件名稱
* @param response
* @param doc
*/
private static void downLoadWord(String fileName, HttpServletResponse response, XWPFDocument doc) throws IOException {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/msword");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".docx" , "UTF-8"));
doc.write(response.getOutputStream());
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* word模板導出
* @param map
* @param templatePath
* @param fileName
* @param response
* @throws Exception
*/
public static void WordTemplateExport(Map<String, Object> map,String templatePath,String fileName,HttpServletResponse response) throws Exception {
XWPFDocument doc = WordExportUtil.exportWord07(templatePath, map);
downLoadWord(fileName,response,doc);
}
3)接口,模板文件在項目根路徑的template文件夾下,圖片自定義下載(注意:如果要設置圖片,必須把導入的jar的版本改為3.3.0,否則會報錯,原因是新版本沒有這個實體類):
/**
* 使用模板word導出數據
* @param param
* @param response
*/
@PostMapping("/wordTemplate")
public void makeWordTemplate(@RequestBody Map<String, Object> param,HttpServletResponse response) {
Map<String, Object> map = new HashMap<>();
map.put("name", "張三");
map.put("nativePlace", "湖北武漢");
map.put("age", "20");
map.put("nation", "漢族");
map.put("phone", "15685654524");
map.put("experience", "湖北武漢,工作三年,java工程師");
map.put("evaluate", "優秀,善良,老實");
//設置圖片,如果無圖片,不設置即可
WordImageEntity image = new WordImageEntity();
image.setHeight(200);
image.setWidth(150);
image.setUrl("E:/excel/pic.jpg");
image.setType(WordImageEntity.URL);
map.put("picture", image);
try {
ExcelUtils.WordTemplateExport(map,"E:/excel/個人簡歷模板.docx",param.get("fileName").toString(),response);
} catch (Exception e) {
e.printStackTrace();
}
}
4)頁面添加按鈕和請求方法,見源碼。點擊即可下載。上面案例導出時有圖片,如果不需要圖片,可不設置圖片路徑即可。
2.2使用word模板導出多頁
單模板生成多頁數據在合適的場景也是需要的,比如一個訂單詳情信息模板,但是有很多訂單,需要導入到一個word里面。
1)在工具類添加方法
/**
* word模板導出多頁
* @param list
* @param templatePath
* @param fileName
* @param response
* @throws Exception
*/
public static void WordTemplateExportMorePage(List<Map<String, Object>> list, String templatePath, String fileName, HttpServletResponse response) throws Exception {
XWPFDocument doc = new ParseWord07().parseWord(templatePath, list);
downLoadWord(fileName, response, doc);
}
2)接口
/**
* word模板導出多頁
* @param param
* @param response
*/
@PostMapping("/wordTemplateMorePage")
public void makeWordTemplateMorePage(@RequestBody Map<String, Object> param, HttpServletResponse response) {
List<Map<String, Object>> list=new ArrayList<>();
for (int i = 0; i < 5; i++) {
Map<String, Object> person = new HashMap<>();
person.put("name", "張三"+i);
person.put("nativePlace", "湖北武漢"+i);
person.put("age", 20+i);
person.put("nation", "漢族");
person.put("phone", "15685654524");
person.put("experience", "湖北武漢,工作三年,java工程師");
person.put("evaluate", "優秀,善良,老實");
person.put("picture", "");
list.add(person);
}
try {
ExcelUtils.WordTemplateExportMorePage(list, "E:/excel/個人簡歷模板.docx", param.get("fileName").toString(), response);
} catch (Exception e) {
e.printStackTrace();
}
}
3)頁面添加按鈕和請求方法,見源碼。點擊即可下載。
3.excel導入時驗證
有時候需要在導入時先驗證數據的合法性再進行導出,為了演示的完整性,需要使用新的頁面進行導入操作。步驟如下:
3.1環境准備
1)新建表student
CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(20) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年齡', `birth` date DEFAULT NULL COMMENT '出生日期', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
2)在ExcelUtils工具類添加方法(標紅)
package com.example.easypoidemoadmin.utils;
import cn.afterturn.easypoi.cache.manager.POICacheManager;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.ExcelXorHtmlUtil;
import cn.afterturn.easypoi.excel.entity.ExcelToHtmlParams;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.afterturn.easypoi.excel.entity.TemplateExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
import cn.afterturn.easypoi.word.WordExportUtil;
import cn.afterturn.easypoi.word.parse.ParseWord07;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Excel導入導出工具類
*/
public class ExcelUtils {
/**
* excel 導出
*
* @param list 數據列表
* @param fileName 導出時的excel名稱
* @param response
*/
public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, fileName, response);
}
/**
* 默認的 excel 導出
*
* @param list 數據列表
* @param fileName 導出時的excel名稱
* @param response
*/
private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) throws IOException {
//把數據添加到excel表格中
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
downLoadExcel(fileName, response, workbook);
}
/**
* excel 導出
*
* @param list 數據列表
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param response
* @param exportParams 導出參數(標題、sheet名稱、是否創建表頭,表格類型)
*/
private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) throws IOException {
//把數據添加到excel表格中
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list);
downLoadExcel(fileName, response, workbook);
}
/**
* excel 導出
*
* @param list 數據列表
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param exportParams 導出參數(標題、sheet名稱、是否創建表頭,表格類型)
* @param response
*/
public static void exportExcel(List<?> list, Class<?> pojoClass, String fileName, ExportParams exportParams, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* excel 導出
*
* @param list 數據列表
* @param title 表格內數據標題
* @param sheetName sheet名稱
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param response
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) throws IOException {
defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF));
}
/**
* 根據模板生成excel后導出
*
* @param templatePath 模板路徑
* @param map 數據集合
* @param fileName 文件名
* @param response
* @throws IOException
*/
public static void exportExcel(TemplateExportParams templatePath, Map<String, Object> map, String fileName, HttpServletResponse response) throws IOException {
Workbook workbook = ExcelExportUtil.exportExcel(templatePath, map);
downLoadExcel(fileName, response, workbook);
}
/**
* excel 導出
*
* @param list 數據列表
* @param title 表格內數據標題
* @param sheetName sheet名稱
* @param pojoClass pojo類型
* @param fileName 導出時的excel名稱
* @param isCreateHeader 是否創建表頭
* @param response
*/
public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) throws IOException {
ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF);
exportParams.setCreateHeadRows(isCreateHeader);
defaultExport(list, pojoClass, fileName, response, exportParams);
}
/**
* excel下載
*
* @param fileName 下載時的文件名稱
* @param response
* @param workbook excel數據
*/
private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) throws IOException {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));
workbook.write(response.getOutputStream());
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* word下載
*
* @param fileName 下載時的文件名稱
* @param response
* @param doc
*/
private static void downLoadWord(String fileName, HttpServletResponse response, XWPFDocument doc) throws IOException {
try {
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/msword");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".docx", "UTF-8"));
doc.write(response.getOutputStream());
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 導入
*
* @param file excel文件
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file, Class<T> pojoClass) throws IOException {
return importExcel(file, 1, 1, pojoClass);
}
/**
* excel 導入
*
* @param filePath excel文件路徑
* @param titleRows 表格內數據標題行
* @param headerRows 表頭行
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(String filePath, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
if (StringUtils.isBlank(filePath)) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
params.setNeedSave(true);
params.setSaveUrl("/excel/");
try {
return ExcelImportUtil.importExcel(new File(filePath), pojoClass, params);
} catch (NoSuchElementException e) {
throw new IOException("模板不能為空");
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 導入
*
* @param file 上傳的文件
* @param titleRows 表格內數據標題行
* @param headerRows 表頭行
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
if (file == null) {
return null;
}
try {
return importExcel(file.getInputStream(), titleRows, headerRows, pojoClass);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 導入
*
* @param inputStream 文件輸入流
* @param titleRows 表格內數據標題行
* @param headerRows 表頭行
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> List<T> importExcel(InputStream inputStream, Integer titleRows, Integer headerRows, Class<T> pojoClass) throws IOException {
if (inputStream == null) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
params.setSaveUrl("/excel/");
params.setNeedSave(true);
try {
return ExcelImportUtil.importExcel(inputStream, pojoClass, params);
} catch (NoSuchElementException e) {
throw new IOException("excel文件不能為空");
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel轉html預覽
*
* @param filePath 文件路徑
* @param response
* @throws Exception
*/
public static void excelToHtml(String filePath, HttpServletResponse response) throws Exception {
ExcelToHtmlParams params = new ExcelToHtmlParams(WorkbookFactory.create(POICacheManager.getFile(filePath)), true);
response.getOutputStream().write(ExcelXorHtmlUtil.excelToHtml(params).getBytes());
}
/**
* word模板導出
*
* @param map
* @param templatePath
* @param fileName
* @param response
* @throws Exception
*/
public static void WordTemplateExport(Map<String, Object> map, String templatePath, String fileName, HttpServletResponse response) throws Exception {
XWPFDocument doc = WordExportUtil.exportWord07(templatePath, map);
downLoadWord(fileName, response, doc);
}
/**
* word模板導出多頁
*
* @param list
* @param templatePath
* @param fileName
* @param response
* @throws Exception
*/
public static void WordTemplateExportMorePage(List<Map<String, Object>> list, String templatePath, String fileName, HttpServletResponse response) throws Exception {
XWPFDocument doc = new ParseWord07().parseWord(templatePath, list);
downLoadWord(fileName, response, doc);
}
/**
* excel 導入,有錯誤信息
*
* @param file 上傳的文件
* @param pojoClass pojo類型
* @param <T>
* @return
*/
public static <T> ExcelImportResult<T> importExcelMore(MultipartFile file, Class<T> pojoClass) throws IOException {
if (file == null) {
return null;
}
try {
return importExcelMore(file.getInputStream(), pojoClass);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
/**
* excel 導入
*
* @param inputStream 文件輸入流
* @param pojoClass pojo類型
* @param <T>
* @return
*/
private static <T> ExcelImportResult<T> importExcelMore(InputStream inputStream, Class<T> pojoClass) throws IOException {
if (inputStream == null) {
return null;
}
ImportParams params = new ImportParams();
params.setTitleRows(1);//表格內數據標題行
params.setHeadRows(1);//表頭行
params.setSaveUrl("/excel/");
params.setNeedSave(true);
params.setNeedVerify(true);
try {
return ExcelImportUtil.importExcelMore(inputStream, pojoClass, params);
} catch (NoSuchElementException e) {
throw new IOException("excel文件不能為空");
} catch (Exception e) {
throw new IOException(e.getMessage());
}
}
}
3)導入驗證構造器的依賴
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.0.Final</version>
</dependency>
4)創建easypoi的工具類
package com.example.easypoidemoadmin.utils;
import cn.afterturn.easypoi.excel.annotation.Excel;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Map;
/**
* easypoi工具類,
* 使用new方式創建對象並使用
*
* @param <T>
*/
public class EasyPoiTool<T> {
/**
* 需要被反射的對象,使用泛型規范傳入對象
*/
public T t;
/**
* 修改注解@Excel的屬性值
* @param attributeName
* @param columnName
* @param targetValue
* @throws Exception
*/
public void changeAttribute(String attributeName, String columnName, Object targetValue) throws Exception {
if (t == null) {
throw new ClassNotFoundException("未找到目標類");
}
if (StringUtils.isEmpty(attributeName)) {
throw new NullPointerException("傳入的注解屬性為空");
}
if (StringUtils.isEmpty(columnName)) {
throw new NullPointerException("傳入的屬性列名為空");
}
//獲取目標對象的屬性值
Field field = t.getClass().getDeclaredField(columnName);
//獲取注解反射對象
Excel excelAnion = field.getAnnotation(Excel.class);
//獲取代理
InvocationHandler invocationHandler = Proxy.getInvocationHandler(excelAnion);
Field excelField = invocationHandler.getClass().getDeclaredField("memberValues");
excelField.setAccessible(true);
Map memberValues = (Map) excelField.get(invocationHandler);
memberValues.put(attributeName, targetValue);
}
}
3.2實戰演練
需求:對導入的學生信息進行驗證,驗證通過后才能導入。要求學生姓名不能為空,出生日期必須是yyyy-MM-dd格式,年齡必須合法。導入后把驗證未通過的信息通過excel方式再下載到本地。
1)新建學生對象,添加注解驗證並實現IExcelModel接口
package com.example.easypoidemoadmin.entity;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.handler.inter.IExcelModel;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.util.Date;
@Data
@TableName(value = "student")
public class Student implements IExcelModel {
/**
* id
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
@TableField(value = "name")
@Excel(name = "姓名", width = 20)
@NotNull(message = "姓名不能為空")
private String name;
/**
* 年齡
*/
@TableField(value = "age")
private Integer age;
/**
* 年齡驗證
*/
@TableField(exist = false)
@Excel(name = "年齡")
@NotNull(message = "年齡不能為空")
@Pattern(regexp = "^(?:[1-9][0-9]?|1[01][0-9]|120)$", message = "年齡必須是整數,且在1-120之間")
private String ageStr;
/**
* 出生日期
*/
@TableField(value = "birth")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private Date birth;
/**
* 出生日期驗證
*/
@TableField(exist = false)
@Excel(name = "出生日期", isImportField = "true", importFormat = "yyyy-MM-dd", databaseFormat = "yyyy-MM-dd", width = 30)
@NotNull(message = "出生日期不能為空")
@Pattern(regexp = "^\\d{4}-\\d{1,2}-\\d{1,2}$", message = "日期格式必須是yyyy-MM-dd格式,如2020-01-01")
private String birthStr;
//錯誤信息
@TableField(exist = false)
@Excel(name = "錯誤信息", width = 50, isColumnHidden = true)
private String errorMsg;
}
實現此接口的原因是獲取其驗證的錯誤信息,並將其映射到字段errorMsg上,當對象不包含此字段時,就看不到錯誤信息。
2)新建接口StudentController
package com.example.easypoidemoadmin.controller;
import cn.afterturn.easypoi.excel.entity.result.ExcelImportResult;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.easypoidemoadmin.entity.CommonResult;
import com.example.easypoidemoadmin.entity.Student;
import com.example.easypoidemoadmin.service.StudentService;
import com.example.easypoidemoadmin.utils.EasyPoiTool;
import com.example.easypoidemoadmin.utils.ExcelUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/student")
public class StudentController {
@Autowired
private StudentService studentService;
/**
* 查詢用戶信息列表
*
* @param name
* @param page
* @param limit
* @return
*/
@GetMapping("/list")
public CommonResult getList(String name, Integer page, Integer limit) {
IPage<Student> iPage = studentService.getList(name, page, limit);
return new CommonResult(200, "查詢信息成功", iPage.getRecords(), iPage.getTotal());
}
/**
* 導入學生信息
*
* @param file
* @param response
* @return
*/
@PostMapping("/upload")
public CommonResult upload(@RequestParam("file") MultipartFile file, HttpServletResponse response) {
try {
ExcelImportResult<Student> importResult = ExcelUtils.importExcelMore(file, Student.class);
//驗證通過的數據
List<Student> list = importResult.getList();
//驗證未通過的數據
List<Student> failList = importResult.getFailList();
studentService.insertBatch(list);
if (failList != null && failList.size() > 0) {
//修改導出的日期格式
EasyPoiTool<Student> easyPoiUtil = new EasyPoiTool<>();
easyPoiUtil.t = failList.get(0);
//展示錯誤的列
easyPoiUtil.changeAttribute("isColumnHidden", "errorMsg", false);
//設置導出的格式
easyPoiUtil.changeAttribute("exportFormat", "birthStr", "");
//導出excel
String title = "導入異常的數據";
ExcelUtils.exportExcel(failList, title, title, Student.class, title, response);
return null;
}
return new CommonResult(200, "信息導入成功");
} catch (Exception e) {
e.printStackTrace();
return new CommonResult(444, "信息導入失敗");
}
}
@PostMapping("/exportTemplate")
public void exportTemplate(@RequestBody Map<String, Object> map, HttpServletResponse response) throws IOException {
List<Student> list = new ArrayList<>();
ExcelUtils.exportExcel(list, (String) map.get("title"), (String) map.get("sheetName"), Student.class, (String) map.get("fileName"), response);
}
}
對於后面的service和dao詳見源碼。
3)導入的頁面見源碼,這里主要說明導入的方法,在導入后需要根據返回的數據判斷是否有錯誤的信息,如果有則下載錯誤信息,若沒有則顯示成功。
importExcel(param) {
const file = param.file
if (file.name.lastIndexOf('.') < 0) {
this.$message.error('上傳文件只能是xls、xlsx格式!')
return
}
const testMsg = file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase()
const extensionXLS = testMsg == 'xls'
const extensionXLSX = testMsg == 'xlsx'
if (!extensionXLS && !extensionXLSX) {
this.$message.error('上傳文件只能是xls、xlsx格式!')
return
}
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error('上傳文件不能超過 2MB!')
return
}
this.importLoading = true
const formData = new FormData()
formData.append('file', param.file)
student.upload(formData).then(res => {
if (!res.code) {
this.$message.error("部分數據導入失敗,數據已下載到本地,請查看!")
fileDownload(res, '導入異常的數據.xlsx')
this.fileList = []
this.getList()
} else if (res.code == 200) {
this.$message.success("導入成功")
this.fileList = []
this.getList()
} else {
this.$message.error("導入失敗")
}
}).catch(err => {
console.log(err)
this.$message.error("導入失敗")
}).finally(()=>{
this.importLoading = false
})
},
也就是說對於這個上傳的請求,當返回的內容是json字符串時就是成功的,沒有錯誤的數據,若不是則返回的是arraybuff類型的數據,需要直接下載。
3.3注意事項
1)由於需要進行驗證,因此在工具類中必須要設置ImportParams的needVerify為true;
2)easypoi是使用springboot對應的版本,對於spring的版本,驗證在這里可能不生效;
3)對於驗證構造器hibernate的版本,springboot2對應的版本必須是5及以上,否則錯誤信息不會顯示;
4)對應上傳的方法,響應類型必須是arraybuff,否則下載的excel無法打開
5)要顯示錯誤的信息,必須設置errorMsg上@Excel注解的isColumnHidden為false
6)在@Excel中沒有設置導出(exportFormat)的日期格式,而是在需要導出的時候再通過反射的方式(調用EasyPoiUtil的方法)設置。若提前設置了,在導入時,輸入的格式不正確,在導出錯誤信息時則會拋出異常。
7)其自帶的正則驗證,要求字段的類型必須是字符串類型,其他類型會發生異常。因此,需要設置兩個字段,一個映射數據庫的字段,一個用於導出和導出。當然也可以使用兩個類進行分布對應。
8)當需要獲取錯誤的行號時,讓實體類繼承IExcelDataModel類並添加int類型的rowNum屬性即可。
4.Excel大批量數據導入
對於上萬級別(10萬+)的數據,若仍然使用第三章節的方法,則讀取速度非常慢。官方也提供了大批量導入的方法,新建的方法如下(在上述ExcelUtils 工具類的基礎上)
/**
* Excel 通過SAX解析方法,適合大數據導入,不支持圖片
* 導入 數據源本地文件,不返回校驗結果
*/
public static void importExcelBySax(MultipartFile file, Class<?> pojoClass, Integer titleRow, IReadHandler handler) {
try {
importExcelBySax(file.getInputStream(), pojoClass, titleRow, handler);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Excel 通過SAX解析方法,適合大數據導入,不支持圖片
* 導入 數據源本地文件,不返回校驗結果
*/
public static void importExcelBySax(InputStream inputstream, Class<?> pojoClass, Integer titleRow, IReadHandler handler) {
ImportParams params = new ImportParams();
params.setTitleRows(titleRow);//表格內數據標題行
params.setHeadRows(1);//表頭行
params.setSaveUrl("/excel/");
params.setNeedSave(true);
params.setNeedVerify(true);
importExcelBySax(inputstream, pojoClass, params, handler);
}
/**
* Excel 通過SAX解析方法,適合大數據導入,不支持圖片
* 導入 數據源本地文件,不返回校驗結果
*/
public static void importExcelBySax(InputStream inputstream, Class<?> pojoClass, ImportParams params, IReadHandler handler) {
new SaxReadExcel().readExcel(inputstream, pojoClass, params, handler);
}
其中的三個方法,第三才是最重要的,上面兩個方法是其擴展的方法,便於在程序中調用。(本人親測 15萬條數據 大概僅需45秒即可讀取完成)
在導入的接口中調用:(這里以用戶對象為例)
// 讀取Excel中的數據到list集合中
List<User> list = new ArrayList<>();
//大數據批量讀
ExcelUtils.importExcelBySax(file, User.class, 2, new IReadHandler<User>() {
@Override
public void handler(Usero) {
//解析完每行數據時會調用
list.add(o);
}
@Override
public void doAfterAll() {
//讀取完成時會調用
log.info("讀取完成");
}
});
說明:大批量數據的導入使用的是SaxReadExcel類的readExcel()方法,不支持圖片的導入,也不返回校驗結果。若需要校驗,需在讀取完數據后采用手動方式。在讀取時,需要實現IReadHandler接口,解析完每個對象都會調用handler方法;全部解析完成后會調用doAfterAll 方法。

