咱们不造轮子,只是轮子的搬运工。(其实最好是造轮子,造比别人好的轮子)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()); } }