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屬性即可。