咱們不造輪子,只是輪子的搬運工。(其實最好是造輪子,造比別人好的輪子)java
開發中常常會遇到excel的處理,導入導出解析等等,java中比較流行的用poi,可是每次都要寫大段工具類來搞定這事兒,此處推薦一個別人造好的輪子【easypoi】,下面介紹下「輪子」的使用。web
一、 在pom.xml
中加入依賴
<!--excel操做--> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>3.3.0</version> </dependency>
或者引入下面的依賴:spring
<dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-base</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-web</artifactId> <version>3.0.3</version> </dependency> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-annotation</artifactId> <version>3.0.3</version> </dependency>
二、編寫實體類
此處注意必需要有空構造函數,不然會報錯「對象建立錯誤」 關於註解@Excel
,其餘還有@ExcelCollection
,@ExcelEntity
,@ExcelIgnore
,@ExcelTarget
等,此處咱們用不到,能夠去官方查看更多數據庫
屬性 | 類型 | 類型 | 說明 |
---|---|---|---|
name | String | null | 列名 |
needMerge | boolean | fasle | 縱向合併單元格 |
orderNum | String | "0" | 列的排序,支持name_id |
replace | String[] | {} | 值得替換 導出是{a_id,b_id} 導入反過來 |
savePath | String | "upload" | 導入文件保存路徑 |
type | int | 1 | 導出類型 1 是文本 2 是圖片,3 是函數,10 是數字 默認是文本 |
width | double | 10 | 列寬 |
height | double | 10 | 列高,後期打算統一使用@ExcelTarget的height,這個會被廢棄,注意 |
isStatistics | boolean | fasle | 自動統計數據,在追加一行統計,把全部數據都和輸出這個處理會吞沒異常,請注意這一點 |
isHyperlink | boolean | false | 超連接,若是是須要實現接口返回對象 |
isImportField | boolean | true | 校驗字段,看看這個字段是否是導入的Excel中有,若是沒有說明是錯誤的Excel,讀取失敗,支持name_id |
exportFormat | String | "" | 導出的時間格式,以這個是否爲空來判斷是否須要格式化日期 |
importFormat | String | "" | 導入的時間格式,以這個是否爲空來判斷是否須要格式化日期 |
format | String | "" | 時間格式,至關於同時設置了exportFormat 和 importFormat |
databaseFormat | String | "yyyyMMddHHmmss" | 導出時間設置,若是字段是Date類型則不須要設置 數據庫若是是string類型,這個須要設置這個數據庫格式,用以轉換時間格式輸出 |
numFormat | String | "" | 數字格式化,參數是Pattern,使用的對象是DecimalFormat |
imageType | int | 1 | 導出類型 1 從file讀取 2 是從數據庫中讀取 默認是文件 一樣導入也是同樣的 |
suffix | String | "" | 文字後綴,如% 90 變成90% |
isWrap | boolean | true | 是否換行 即支持\n |
mergeRely | int[] | {} | 合併單元格依賴關係,好比第二列合併是基於第一列 則{1}就能夠了 |
mergeVertical | boolean | fasle | 縱向合併內容相同的單元格 |
import cn.afterturn.easypoi.excel.annotation.Excel; import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; @Data @EqualsAndHashCode(callSuper = false) public class PersonExportVo implements Serializable { private static final long serialVersionUID = 1L; /** * 姓名 */ @Excel(name = "姓名", orderNum = "0", width = 15) private String name; /** * 登陸用戶名 */ @Excel(name = "用戶名", orderNum = "1", width = 15) private String username; @Excel(name = "手機號碼", orderNum = "2", width = 15) private String phoneNumber; /** * 人臉圖片 */ @Excel(name = "人臉圖片", orderNum = "3", width = 15, height = 30, type = 2) private String imageUrl; }
三、導入導出公用工具類
import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.ExcelImportUtil; import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; import freemarker.template.Configuration; import freemarker.template.Template; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; public class ExcelUtils { /** * excel 導出 * * @param list 數據 * @param title 標題 * @param sheetName sheet名稱 * @param pojoClass pojo類型 * @param fileName 文件名稱 * @param isCreateHeader 是否建立表頭 * @param response */ public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, boolean isCreateHeader, HttpServletResponse response) { ExportParams exportParams = new ExportParams(title, sheetName, ExcelType.XSSF); exportParams.setCreateHeadRows(isCreateHeader); defaultExport(list, pojoClass, fileName, response, exportParams); } /** * excel 導出 * * @param list 數據 * @param title 標題 * @param sheetName sheet名稱 * @param pojoClass pojo類型 * @param fileName 文件名稱 * @param response */ public static void exportExcel(List<?> list, String title, String sheetName, Class<?> pojoClass, String fileName, HttpServletResponse response) { defaultExport(list, pojoClass, fileName, response, new ExportParams(title, sheetName, ExcelType.XSSF)); } /** * excel 導出 * * @param list 數據 * @param pojoClass pojo類型 * @param fileName 文件名稱 * @param response * @param exportParams 導出參數 */ public static void exportExcel(List<?> list, Class<?> pojoClass, String fileName, ExportParams exportParams, HttpServletResponse response) { defaultExport(list, pojoClass, fileName, response, exportParams); } /** * excel 導出 * * @param list 數據 * @param fileName 文件名稱 * @param response */ public static void exportExcel(List<Map<String, Object>> list, String fileName, HttpServletResponse response) { defaultExport(list, fileName, response); } /** * 默認的 excel 導出 * * @param list 數據 * @param pojoClass pojo類型 * @param fileName 文件名稱 * @param response * @param exportParams 導出參數 */ private static void defaultExport(List<?> list, Class<?> pojoClass, String fileName, HttpServletResponse response, ExportParams exportParams) { Workbook workbook = ExcelExportUtil.exportExcel(exportParams, pojoClass, list); downLoadExcel(fileName, response, workbook); } /** * 默認的 excel 導出 * * @param list 數據 * @param fileName 文件名稱 * @param response */ private static void defaultExport(List<Map<String, Object>> list, String fileName, HttpServletResponse response) { Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF); downLoadExcel(fileName, response, workbook); } /** * 下載 * * @param fileName 文件名稱 * @param response * @param workbook excel數據 */ private static void downLoadExcel(String fileName, HttpServletResponse response, Workbook workbook) { try { response.setCharacterEncoding("UTF-8"); response.setHeader("content-Type", "application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + "." + ExcelTypeEnum.XLSX.getValue(), "UTF-8")); workbook.write(response.getOutputStream()); } catch (Exception e) { throw new IOException(e.getMessage(), 500); } } /** * 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) { 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("模板不能爲空", 500); } catch (Exception e) { throw new IOException(e.getMessage(), 500); } } /** * excel 導入 * * @param file excel文件 * @param pojoClass pojo類型 * @param <T> * @return */ public static <T> List<T> importExcel(MultipartFile file, Class<T> pojoClass) { return importExcel(file, 1, 1, pojoClass); } /** * excel 導入 * * @param file excel文件 * @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) { return importExcel(file, titleRows, headerRows, false, pojoClass); } /** * excel 導入 * * @param file 上傳的文件 * @param titleRows 標題行 * @param headerRows 表頭行 * @param needVerfiy 是否檢驗excel內容 * @param pojoClass pojo類型 * @param <T> * @return */ public static <T> List<T> importExcel(MultipartFile file, Integer titleRows, Integer headerRows, boolean needVerfiy, Class<T> pojoClass) { if (file == null) { return null; } try { return importExcel(file.getInputStream(), titleRows, headerRows, needVerfiy, pojoClass); } catch (Exception e) { throw new IOException(e.getMessage(), 500); } } /** * excel 導入 * * @param inputStream 文件輸入流 * @param titleRows 標題行 * @param headerRows 表頭行 * @param needVerfiy 是否檢驗excel內容 * @param pojoClass pojo類型 * @param <T> * @return */ public static <T> List<T> importExcel(InputStream inputStream, Integer titleRows, Integer headerRows, boolean needVerfiy, Class<T> pojoClass) { if (inputStream == null) { return null; } ImportParams params = new ImportParams(); params.setTitleRows(titleRows); params.setHeadRows(headerRows); params.setSaveUrl("/excel/"); params.setNeedSave(true); params.setNeedVerfiy(needVerfiy); try { return ExcelImportUtil.importExcel(inputStream, pojoClass, params); } catch (NoSuchElementException e) { throw new IOException("excel文件不能爲空", 500); } catch (Exception e) { throw new IOException(e.getMessage(), 500); } } /** * Excel 類型枚舉 */ enum ExcelTypeEnum { XLS("xls"), XLSX("xlsx"); private String value; ExcelTypeEnum(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } }
四、測試
導出:express
/** * 導出 * * @param response */ @RequestMapping(value = "/export", method = RequestMethod.GET) public void exportExcel(HttpServletResponse response) { long start = System.currentTimeMillis(); List<MonitorPersonExportVo> personList = new ArrayList<>(); for (int i = 0; i < 5; i++) { MonitorPersonExportVo personVo = new MonitorPersonExportVo(); personVo.setName("張三" + i); personVo.setUsername("張三" + i); personVo.setPhoneNumber("18888888888"); personVo.setImageUrl(Constant.DEFAULT_IMAGE); personList.add(personVo); } log.debug("導出excel所花時間:" + (System.currentTimeMillis() - start)); ExcelUtils.exportExcel(personList, "員工信息表", "員工信息", MonitorPersonExportVo.class, "員工信息", response); }
導入:apache
/** * 導入 * * @param file */ @RequestMapping(value = "/import", method = RequestMethod.POST) public ResultView importExcel(@RequestParam("file") MultipartFile file) { long start = System.currentTimeMillis(); List<MonitorPersonImportVo> personVoList = ExcelUtils.importExcel(file, MonitorPersonImportVo.class); log.debug(personVoList.toString()); log.debug("導入excel所花時間:" + (System.currentTimeMillis() - start)); return getResponse(true, "導入成功"); }
五、注意:
SpringBoot
中,若是excel
圖片文件路徑指向了SpringBoot
的資源文件,那麼當項目打成jar
包後,easypoi
將沒法讀取到該資源文件。網絡
解決辦法:app
重寫easypoi
的IFileLoader
接口的實現類:less
原代碼實現方式:ide
/** * Copyright 2013-2015 JueYue (qrb.jueyue@gmail.com) * <p> * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.afterturn.easypoi.cache.manager; import cn.afterturn.easypoi.util.PoiPublicUtil; import org.apache.poi.util.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.URL; import java.net.URLConnection; /** * 文件加載類,根據路徑加載指定文件 * * @author JueYue * 2014年2月10日 * @version 1.0 */ public class FileLoaderImpl implements IFileLoader { private static final Logger LOGGER = LoggerFactory.getLogger(FileLoaderImpl.class); @Override public byte[] getFile(String url) { InputStream fileis = null; ByteArrayOutputStream baos = null; try { //判斷是不是網絡地址 if (url.startsWith("http")) { URL urlObj = new URL(url); URLConnection urlConnection = urlObj.openConnection(); urlConnection.setConnectTimeout(30); urlConnection.setReadTimeout(60); urlConnection.setDoInput(true); fileis = urlConnection.getInputStream(); } else { //先用絕對路徑查詢,再查詢相對路徑 try { fileis = new FileInputStream(url); } catch (FileNotFoundException e) { //獲取項目文件 fileis = FileLoaderImpl.class.getClassLoader().getResourceAsStream(url); } } baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = fileis.read(buffer)) > -1) { baos.write(buffer, 0, len); } baos.flush(); return baos.toByteArray(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } finally { IOUtils.closeQuietly(fileis); IOUtils.closeQuietly(baos); } LOGGER.error(fileis + "這個路徑文件沒有找到,請查詢"); return null; } }
修改後的代碼:
import cn.afterturn.easypoi.cache.manager.IFileLoader; import org.apache.poi.util.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; /** * 文件加載類,根據路徑加載指定文件 * * @author user * @date 2018/12/18 */ public class FileLoaderImpl implements IFileLoader { private static final Logger LOGGER = LoggerFactory.getLogger(cn.afterturn.easypoi.cache.manager.FileLoaderImpl.class); @Override public byte[] getFile(String url) { InputStream fileis = null; ByteArrayOutputStream baos = null; try { //判斷是不是網絡地址 if (url.startsWith("http")) { URL urlObj = new URL(url); URLConnection urlConnection = urlObj.openConnection(); urlConnection.setConnectTimeout(30); urlConnection.setReadTimeout(60); urlConnection.setDoInput(true); fileis = urlConnection.getInputStream(); } else { //先用絕對路徑查詢,再查詢相對路徑 try { fileis = new FileInputStream(url); } catch (FileNotFoundException e) { //獲取項目文件 fileis = FileLoaderImpl.class.getClassLoader().getResourceAsStream(url); if (fileis == null) { fileis = FileLoaderImpl.class.getResourceAsStream(url); } } } baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = fileis.read(buffer)) > -1) { baos.write(buffer, 0, len); } baos.flush(); return baos.toByteArray(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } finally { IOUtils.closeQuietly(fileis); IOUtils.closeQuietly(baos); } LOGGER.error(fileis + "這個路徑文件沒有找到,請查詢"); return null; } }
在原代碼中加入了一下代碼,就能讀取到文件了:
if (fileis == null) { fileis = FileLoaderImpl.class.getResourceAsStream(url); }
最後經過做者提供的POICacheManager
的靜態方法進行設置:
public static void setFileLoder(IFileLoader fileLoder) { POICacheManager.fileLoder = fileLoder; } /** * 一次線程有效 * @param fileLoder */ public static void setFileLoderOnce(IFileLoader fileLoder) { if (fileLoder != null) { LOCAL_FILELOADER.set(fileLoder); } }
寫一個監聽器,監聽項目啓動時,對POICacheManager
進行全局替換:
import cn.afterturn.easypoi.cache.manager.POICacheManager; import com.***.**.framework.common.util.FileLoaderImpl; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; @Component public class ExcelListener implements ApplicationListener<ApplicationReadyEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent event) { POICacheManager.setFileLoader(new FileLoaderImpl()); } }