小蜗牛,今天爬了多远?不急,继续爬总会到达终点。
朋友们,今天学习了多久?别慌,保持学习才会看到更好的自己。
觉得我的文章还不错的,欢迎大家还可以关注我的微信公众号:Java菜鸟进阶之路
最近会写一个系列的文章进行推出,值得期待和持续关注哦!

文章目录

  • 情景引入
  • EasyExcel的引入
  • 引入EasyExcel的依赖
  • 导出
    • 普通样式的模板导出
      • 步骤
        • 定义导出模板的实体
        • 编写导出excel的工具类
        • 定义导出模板的实体
        • 编写调用导出方法的服务类
    • 具有单元格下拉列表的模板导出
      • 步骤
        • 定义设置下拉列表内容的注解
        • 定义下拉列表接口
        • 定义下拉列表接口实现类
        • 定义下拉列表的excel模板导出方法
    • 分Sheet单元导出大量数据
      • 步骤
        • 分多Sheet导出excel数据
        • 设置响应流的头信息
      • 分sheet页导出数据方法
  • 导入
    • 步骤
      • 导入pom依赖
      • 上传excel导入模板对应实体
      • 上传处理成功的消息提示实体
      • 上传处理失败的消息提示实体
      • 编写处理上传数据逻辑返回的实体类
      • 编写处理上传数据的逻辑处理类
      • 编写上传处理excel监听
      • 导入excel的数据一般性校验工具类
      • 接受 excel导入和导出数据的工具类
      • Controller层接受文件上传方法以及处理导入数据
  • 彩蛋(读取项目模板直接下载)
    • 场景
    • 步骤
      • 方法一:HttpServletResponse实现
      • 方法二:Response返回实现
    • 惊喜发生
    • 原因分析
    • 解决办法
  • 总结

情景引入

小白:起床起床,快起床!!!
我:小白,你又怎么了,每次来都是这样火急火燎,成年人了能不能沉稳一点呢?
小白:我要被需求给打败了,因为老是碰到这样的功能;
我:咦,说说看,怎么样的需求把你折磨成这个样子,还打扰了我的美梦;
小白:就是老是碰到前端页面数据的导入和导出功能,而且还是用Excel格式的文件,可烦了。
我:这很正常呀,Excel的导入导出是常见的功能,特别是月末月初,各种统计表格来来回回。
小白:对呀,都要被各种Excel的模板给折磨疯了,能不能告诉我一些常用的导入导出的方法呀,拯救一下我;
我:好吧好吧,看你这么可怜的情面上,就给你科普科普,快去搬小板凳来吧!
小白:端端正正的坐好,等待上课!

EasyExcel的引入

对于EasyExcel,我想大家都不会太陌生,从它的名字就可以看出来,它就是为了Excel文件而生。那么,它到底是怎样的一个东西呢?
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
在这样的一种场景下,EasyExcel就诞生了,其是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称,因此,也受到了很多公司和个人的喜爱。

引入EasyExcel的依赖

建议项目采取Maven的方式,使用EasyExcel则添加如下的依赖即可:
PS:建议采取2.X的版本

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.0.5</version>
</dependency>

导出

**为什么要先说导出而不说导入呢?**其实,很简单,我们在导入数据的时候,其实都有一定的Excel格式,而我们在解析的时候,都不是随便解析一个Excel,而它肯定是包含有一定的格式。所以,既然要支持一定格式的Excel导入,那么当然是要先给用户提供一个 “数据模板”,否则,用户怎么知道要用什么Excel的格式呢?因此,导出Excel的功能就自然要先说了。

普通样式的模板导出

步骤

定义导出模板的实体

package com.hnu.scw.model;import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;import java.io.Serializable;/*** @ Author     :scw* @ Date       :Created in 下午 10:22 2020/3/16 0016* @ Description:普通样式excel导出模板实体* @ Modified By:* @Version: $version$*/
@Data
public class ExportMouldDto implements Serializable{@ExcelProperty(index = 0, value = "年龄")private Integer age;@ExcelProperty(index = 1, value = "名字")private String name;@ExcelProperty(index = 2, value = "性别")private String sex;public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}
}

PS:其中的还有很多注解这个就不多说了,如果不明白的可以百度看看哦。

编写导出excel的工具类

package com.hnu.scw.utils;import com.alibaba.excel.EasyExcel;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.List;/*** @ Author     :scw* @ Date       :Created in 下午 9:53 2020/3/16 0016* @ Description:Excel操作常用的工具类* @ Modified By:* @Version: $version$*/
public class ExcelUtil {/*** 默认excel文件名和单元sheet名一样的 Excel文件导出* @param httpServletResponse* @param data* @param fileName* @param clazz* @throws IOException*/public static void writeExcel(HttpServletResponse httpServletResponse, List data, String fileName, Class clazz) throws IOException {writeExcel(httpServletResponse, data, fileName, fileName, clazz);}/*** 导出数据为Excel文件* @param response  响应实体* @param data  导出数据* @param fileName 文件名* @param sheetName 单元格名* @param clazz  定义excel导出的实体* @throws IOException*/public static void writeExcel(HttpServletResponse response, List data, String fileName, String sheetName, Class clazz) throws IOException {//防止中文乱码fileName = URLEncoder.encode(fileName, "UTF-8");response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//防止导入excel文件名中文不乱码response.setHeader("Content-disposition", "attachment;fileName=" + fileName + ".xlsx" + ";fileName*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);}}

定义导出模板的实体

package com.hnu.scw.model;import com.alibaba.excel.annotation.ExcelProperty;
import com.hnu.scw.annotation.DownExcelValue;
import com.hnu.scw.service.CustomDownExcelService;
import lombok.Data;/*** @ Author     :scw* @ Date       :Created in 下午 10:50 2020/3/16 0016* @ Description:具有下拉列表的excel实体* @ Modified By:* @Version: $version$*/
@Data
public class ExportMouldDownDto {private Integer age;@DownExcelValue(source = {"小白", "小黑"})private String name;@DownExcelValue(sourceClass = CustomDownExcelService.class)private String sex;public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}
}

编写调用导出方法的服务类

package com.hnu.scw.service;import com.hnu.scw.model.ExportMouldDto;
import com.hnu.scw.utils.ExcelUtil;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @ Author     :scw* @ Date       :Created in 下午 10:20 2020/3/16 0016* @ Description:Excel的导入和导出实现类* @ Modified By:* @Version: $version$*/
public class ExcelServiceImpl {/*** 导出 普通样式的excel 模板* @param response*/public void exportExcelMould(HttpServletResponse response) throws IOException {//定义模板的样例数据(PS:如果不需要模板有样例数据那么则不需要处理)ExportMouldDto example = new ExportMouldDto();example.setAge(18);example.setName("小白");example.setSex("男");//调用工具类ExcelUtil.writeExcel(response, Arrays.asList(example), "导入数据模板", ExportMouldDto.class);}
}

PS:这里就是模拟一下导出方法的使用的关键代码,这个也是根据实际的情况看进行选择性的对应处理;

具有单元格下拉列表的模板导出

我们在平常中,会经常看到有Excel中的单元格是不让进行编辑,而只能通过下拉列表的值进行选择,那么这样的功能的Excel表格是怎样实现的呢?

步骤

定义设置下拉列表内容的注解

package com.hnu.scw.annotation;import java.lang.annotation.*;/*** @ Author     :scw* @ Date       :Created in 下午 10:44 2020/3/16 0016* @ Description:${description}* @ Modified By:* @Version: $version$*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface DownExcelValue {//定义固定下拉的内容String[] source() default {};//定义动态下拉的内容,Class[] sourceClass() default {};
}

定义下拉列表接口

package com.hnu.scw.service;/*** @ Author     :scw* @ Date       :Created in 下午 10:46 2020/3/16 0016* @ Description:${description}* @ Modified By:* @Version: $version$*/
public interface CustomDownExcelService {/*** 下拉列表的内容数组* PS:主要用于定义下拉列表的内容* @return*/String[] source();
}

定义下拉列表接口实现类

package com.hnu.scw.service;/*** @ Author     :scw* @ Date       :Created in 下午 10:48 2020/3/16 0016* @ Description:定义性别的下拉列表的内容* @ Modified By:* @Version: $version$*/
public class CustomDownExcelServiceImpl implements  CustomDownExcelService {public String[] source() {return new String[]{"男","女","不详"};}
}

定义下拉列表的excel模板导出方法

/*** 导出 单元格具有下拉列表样式的excel 模板* @param response*/public void exportDownExcelMould(HttpServletResponse response) throws IOException {//存储下拉列表集合Map<Integer, String[]> explicitListConstraintMap = new HashMap<Integer, String[]>();Field[] declaredFields = ExportMouldDownDto.class.getDeclaredFields();for (int i = 0; i < declaredFields.length; i++) {Field field = declaredFields[i];DownExcelValue explicitConstraint = field.getAnnotation(DownExcelValue.class);//解析注解信息String[] explicitArray = dealDownExcelAnnotation(explicitConstraint);if (explicitArray != null && explicitArray.length > 0) {explicitListConstraintMap.put(i, explicitArray);}}ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream(), ExportMouldDownDto.class).registerWriteHandler(new SheetWriteHandler() {public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {}public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {//通过sheet处理下拉信息Sheet sheet = writeSheetHolder.getSheet();DataValidationHelper helper = sheet.getDataValidationHelper();explicitListConstraintMap.forEach((k, v) -> {CellRangeAddressList rangeList = new CellRangeAddressList();CellRangeAddress addr = new CellRangeAddress(1, 1000, k, k);rangeList.addCellRangeAddress(addr);DataValidationConstraint constraint = helper.createExplicitListConstraint(v);DataValidation validation = helper.createValidation(constraint, rangeList);sheet.addValidationData(validation);});}}).build();WriteSheet sheet = EasyExcel.writerSheet().build();//设置样例数据ExportMouldDownDto example = new ExportMouldDownDto();example.setAge(18);example.setName("哈哈哈哈");example.setSex("男");excelWriter.write(null,sheet).finish();}
/*** 处理下拉框内容接口或者固定下拉列表值的处理* @param explicitConstraint* @return*/private String[] dealDownExcelAnnotation(DownExcelValue explicitConstraint) {if (explicitConstraint == null) {return null;}//固定下拉信息String[] source = explicitConstraint.source();if (source.length > 0) {return source;}//动态下拉信息Class<? extends CustomDownExcelService>[] classes = explicitConstraint.sourceClass();if (classes.length>0){CustomDownExcelService explicitInterface = null;try {explicitInterface = classes[0].newInstance();String[] source1 = explicitInterface.source();if (source1.length>0){return source1;}} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}return null;}

分Sheet单元导出大量数据

场景:有时候我们会遇到导出的数据量较大,而每个单元页显示的内容的条数有一定的限制,那么如何实现“分页”单元页的导出呢?

步骤

分多Sheet导出excel数据

设置响应流的头信息

package com.hnu.scw.utils;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;/*** @ Author     :scw* @ Date       :Created in 下午 9:53 2020/3/16 0016* @ Description:Excel操作常用的工具类* @ Modified By:* @Version: $version$*/
public class ExcelUtil {/*** 设置响应头信息* @param response* @param fileName*/public static void setHead(HttpServletResponse response, String fileName){response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//防止导入excel文件名中文不乱码response.setHeader("Content-disposition", "attachment;fileName=" + fileName + ".xlsx" + ";fileName*=utf-8''" + fileName + ".xlsx");}}

分sheet页导出数据方法

/*** 分sheet单元导出excel数据* @param response*/public void pageExportExcelData(HttpServletResponse response) throws IOException {OutputStream outputStream;ExcelWriter excelWriter;// 设置导出文件名(PS:防止中文乱码)String fileName = URLEncoder.encode("分sheet导出数据", "UTF-8");// 设置响应流ExcelUtil.setHead(response, fileName);// 获取响应流outputStream = response.getOutputStream();// 设置导出格式excelWriter = new ExcelWriter(outputStream, ExcelTypeEnum.XLSX);// 当前导出的总数据int currentExportTotalNumber = 0;// 每个sheet的数据条数int pageEverySheetNumber = 2000;// TODO 查询要导出的数据总条数(PS:这个就根据对应需求处理即可)int totalExportNumber = 10000;// 当前数据所需要导出的sheet序号int currentSheetOrder = 0;// 需要导出的sheet数(PS:这个规则根据需求即可,这里模拟每个sheet就200条数据)int totalPage = totalExportNumber % pageEverySheetNumber == 0 ? totalExportNumber / pageEverySheetNumber : (totalExportNumber / pageEverySheetNumber) + 1;// 创建第一个sheet单元Sheet sheet = new Sheet(1, 0, TemplateExportBean.class, fileName, null);for (int i = 0; i < totalPage; i++) {// TODO 查询当前sheet需要导出的数据内容(PS:这里的话就不处理了)List<TemplateExportBean> currentExportList = new ArrayList<>();// 当前sheet的序号int belongSheetOrder = currentExportTotalNumber / pageEverySheetNumber;// 判断是否需要创建新的sheetif(belongSheetOrder == currentSheetOrder){// 设置sheet序号currentSheetOrder = belongSheetOrder;}else{// 将数据写入不同的sheet单元sheet = new Sheet(belongSheetOrder + 1, 0, TemplateExportBean.class, fileName, null);}if(sheet != null){// 设置开始写excel表格的sheet位置sheet.setStartRow(currentExportTotalNumber - belongSheetOrder * pageEverySheetNumber);// 写入数据excelWriter.write(currentExportList, sheet);// 增加已经导出数据的条数currentExportTotalNumber += currentExportList.size();// 释放资源currentExportList.clear();}}if(excelWriter != null){excelWriter.finish();}}

导入

上面已经针对导出功能进行了讲解,那么接下来就说说导入又是如何实现的呢?

步骤

导入pom依赖

<dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>4.3.7.RELEASE</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.0.2</version></dependency>

上传excel导入模板对应实体

PS:这里就是简单的模拟,后续根据需求进行自己对应补充哦!

package com.hnu.scw.model;import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;/*** @ Author     :scw* @ Date       :Created in 下午 10:01 2020/3/31 0031* @ Description:Excel导入模板类* @ Modified By:* @Version: $version$*/
@Data
public class ExcelImportTemplateDto {@ExcelProperty(value = "名称", index = 0)private String name;
}

上传处理成功的消息提示实体

PS:如果不需要进行任何的提示,而只需要处理上传任务,这可以不需要;

package com.hnu.scw.model;import lombok.Data;import java.io.Serializable;/*** @ Author     :scw* @ Date       :Created in 下午 8:56 2020/3/31 0031* @ Description:导入成功的提示实体* @ Modified By:* @Version: $version$*/
@Data
public class ImportSuccessDto implements Serializable{private Object object;public ImportSuccessDto(Object object) {this.object = object;}
}

上传处理失败的消息提示实体

PS:如果不需要进行任何的提示,而只需要处理上传任务,这可以不需要;

package com.hnu.scw.model;import lombok.Data;import java.io.Serializable;/*** @ Author     :scw* @ Date       :Created in 下午 8:56 2020/3/31 0031* @ Description:导入失败的提示实体* @ Modified By:* @Version: $version$*/
@Data
public class ImportFailDto implements Serializable{// 导入实体信息private Object object;// 导入错误的提示private String errMsg;public ImportFailDto(Object object, String errMsg) {this.object = object;this.errMsg = errMsg;}public Object getObject() {return object;}public void setObject(Object object) {this.object = object;}public String getErrMsg() {return errMsg;}public void setErrMsg(String errMsg) {this.errMsg = errMsg;}
}

编写处理上传数据逻辑返回的实体类

PS:如果不需要对数据逻辑校验后的响应信息的提示,那么也可以不需要该实体;

package com.hnu.scw.model;import lombok.Data;import java.util.ArrayList;
import java.util.List;/*** @ Author     :scw* @ Date       :Created in 下午 9:19 2020/3/31 0031* @ Description:Excel处理结果* @ Modified By:* @Version: $version$*/
@Data
public class ExcelImportResultDto {// 导入成功的消息列表private List<ImportSuccessDto> successDtoList;// 导入失败的消息列表private List<ImportFailDto> failDtoList;public ExcelImportResultDto(List<ImportSuccessDto> successDtoList, List<ImportFailDto> failDtoList) {this.successDtoList = successDtoList;this.failDtoList = failDtoList;}public ExcelImportResultDto(List<ImportFailDto> failDtoList) {this.failDtoList = failDtoList;this.successDtoList = new ArrayList<>();}public List<ImportSuccessDto> getSuccessDtoList() {return successDtoList;}public void setSuccessDtoList(List<ImportSuccessDto> successDtoList) {this.successDtoList = successDtoList;}public List<ImportFailDto> getFailDtoList() {return failDtoList;}public void setFailDtoList(List<ImportFailDto> failDtoList) {this.failDtoList = failDtoList;}
}

编写处理上传数据的逻辑处理类

PS:这里只是简单的梳理了处理流程,而具体的处理逻辑,则根据需求来进行即可。

package com.hnu.scw.service;import com.hnu.scw.model.ExcelImportResultDto;import java.util.List;/*** @ Author     :scw* @ Date       :Created in 下午 9:02 2020/3/31 0031* @ Description:处理导入数据的逻辑类* @ Modified By:* @Version: $version$*/
public class HandleImportExcelService {public <T> ExcelImportResultDto checkImportData(List<T> list){// TODO 编写校验的逻辑return new ExcelImportResultDto(null);}
}

编写上传处理excel监听

package com.hnu.scw.listener;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.context.AnalysisContextImpl;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.hnu.scw.model.ExcelImportResultDto;
import com.hnu.scw.model.ImportFailDto;
import com.hnu.scw.model.ImportSuccessDto;
import com.hnu.scw.service.HandleImportExcelService;
import com.hnu.scw.utils.ImportExcelValidHelper;import java.lang.reflect.Field;
import java.util.*;/*** @ Author     :scw* @ Date       :Created in 下午 8:54 2020/3/31 0031* @ Description:导入excel的监听处理类* @ Modified By:* @Version: $version$*/
public class ExcelImportListener<T>  extends AnalysisEventListener<T>{// 导入成功的数据private List<ImportSuccessDto> importSuccessDtoList = new ArrayList<>();// 导入失败的数据private List<ImportFailDto> importFailDtoList = new ArrayList<>();private List<T> list = new ArrayList<>();// 处理导入数据的逻辑private HandleImportExcelService handleImportExcelService;private Class<T> tClass;public List<ImportSuccessDto> getImportSuccessDtoList() {return importSuccessDtoList;}public void setImportSuccessDtoList(List<ImportSuccessDto> importSuccessDtoList) {this.importSuccessDtoList = importSuccessDtoList;}public List<ImportFailDto> getImportFailDtoList() {return importFailDtoList;}public void setImportFailDtoList(List<ImportFailDto> importFailDtoList) {this.importFailDtoList = importFailDtoList;}public List<T> getList() {return list;}public void setList(List<T> list) {this.list = list;}public HandleImportExcelService getHandleImportExcelService() {return handleImportExcelService;}public void setHandleImportExcelService(HandleImportExcelService handleImportExcelService) {this.handleImportExcelService = handleImportExcelService;}public Class<T> gettClass() {return tClass;}public void settClass(Class<T> tClass) {this.tClass = tClass;}public ExcelImportListener(HandleImportExcelService handleImportExcelService) {this.handleImportExcelService = handleImportExcelService;}public ExcelImportListener(HandleImportExcelService handleImportExcelService, Class<T> tClass) {this.handleImportExcelService = handleImportExcelService;this.tClass = tClass;}/*** 导入数据的处理逻辑* @param t* @param analysisContext*/@Overridepublic void invoke(T t, AnalysisContext analysisContext) {//错误提示String msg = "";try {msg = ImportExcelValidHelper.checkDataValid(t);}catch (Exception e){msg = "解析处理校验异常";}// 如果校验失败if(msg.length() != 0){ImportFailDto importFailDto = new ImportFailDto(t, msg);importFailDtoList.add(importFailDto);}else{list.add(t);}// 每1000条处理一次(PS:根据需求来即可)if(list.size() > 1000){ExcelImportResultDto excelImportResultDto = handleImportExcelService.checkImportData(list);if(excelImportResultDto.getSuccessDtoList().size() >= 0){importSuccessDtoList.addAll(excelImportResultDto.getSuccessDtoList());}if(excelImportResultDto.getFailDtoList().size() >= 0){importFailDtoList.addAll(excelImportResultDto.getFailDtoList());}list.clear();}}/*** 所有数据处理完之后的处理方法* @param analysisContext*/@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {// PS:之所以最后这里还进行处理是防止最后一次的数据量没有达到批量值ExcelImportResultDto excelImportResultDto = handleImportExcelService.checkImportData(list);if(excelImportResultDto.getSuccessDtoList().size() >= 0){importSuccessDtoList.addAll(excelImportResultDto.getSuccessDtoList());}if(excelImportResultDto.getFailDtoList().size() >= 0){importFailDtoList.addAll(excelImportResultDto.getFailDtoList());}list.clear();}/*** 校验导入的表格的头是否匹配* @param headMap* @param analysisContext*/@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext analysisContext){super.invokeHeadMap(headMap, analysisContext);if(tClass != null){try {// 获取Excel导入实体的单元格内容Map<Integer, String> indexNameMap = getIndexName(tClass);Set<Integer> keySet = indexNameMap.keySet();for (Integer key: keySet) {// 头表是否存在空值if(headMap.get(key).length() == 0){throw new ExcelAnalysisException("Excel格式非法");}// 对比导入Excel实体模板和当前上传Excel是否匹配if(!headMap.get(key).equals(indexNameMap.get(key))){throw new ExcelAnalysisException("Excel格式非法");}}}catch (Exception e){e.printStackTrace();}}}private Map<Integer, String> getIndexName(Class<T> tClass) throws NoSuchFieldException {Map<Integer, String> result = new HashMap<>();Field[] declaredFields = tClass.getDeclaredFields();for (Field currentField: declaredFields) {currentField.setAccessible(true);ExcelProperty annotation = currentField.getAnnotation(ExcelProperty.class);if(annotation != null){int index =annotation.index();String[] value = annotation.value();StringBuilder sb = new StringBuilder();for (String cur : value) {sb.append(cur);}result.put(index, sb.toString());}}return result;}}

导入excel的数据一般性校验工具类

PS:主要是对上传数据的数据类型或者简单合法性的校验处理

package com.hnu.scw.utils;/*** @ Author     :scw* @ Date       :Created in 下午 9:06 2020/3/31 0031* @ Description:导入excel数据的校验类* @ Modified By:* @Version: $version$*/
public class ImportExcelValidHelper {/*** 执行数据的校验处理* @param obj* @param <T>* @return*/public static <T> String checkDataValid(T obj){// TODO 执行数据的校验处理return null;}
}

接受 excel导入和导出数据的工具类

PS:主要用于接受前端上传的文件和excel数据监听处理

package com.hnu.scw.utils;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;/*** @ Author     :scw* @ Date       :Created in 下午 9:53 2020/3/16 0016* @ Description:Excel操作常用的工具类* @ Modified By:* @Version: $version$*/
public class ExcelUtil {/*** 默认excel文件名和单元sheet名一样的 Excel文件导出* @param httpServletResponse* @param data* @param fileName* @param clazz* @throws IOException*/public static void writeExcel(HttpServletResponse httpServletResponse, List data, String fileName, Class clazz) throws IOException {writeExcel(httpServletResponse, data, fileName, fileName, clazz);}/*** 导出数据为Excel文件* @param response  响应实体* @param data  导出数据* @param fileName 文件名* @param sheetName 单元格名* @param clazz  定义excel导出的实体* @throws IOException*/public static void writeExcel(HttpServletResponse response, List data, String fileName, String sheetName, Class clazz) throws IOException {//防止中文乱码fileName = URLEncoder.encode(fileName, "UTF-8");response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//防止导入excel文件名中文不乱码response.setHeader("Content-disposition", "attachment;fileName=" + fileName + ".xlsx" + ";fileName*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), clazz).sheet(sheetName).doWrite(data);}/***  easyExcel处理上传的文件* @param request 请求实体* @param fileName 请求文件名(PS:要注意与前端上传的匹配)* @param sheetNo 单元格* @param rowNumber 行数* @param analysisEventListener  上传处理监听* @param clazz  上传处理excel类* @return* @throws Exception*/public static List<Object> readExcel(HttpServletRequest request, String fileName, Integer sheetNo, Integer rowNumber, AnalysisEventListener analysisEventListener, Class clazz) throws Exception {// 获取解析器CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext());MultipartHttpServletRequest multipartHttpServletRequest = commonsMultipartResolver.resolveMultipart(request);MultipartFile file = multipartHttpServletRequest.getFile(fileName);String originalFilename = file.getOriginalFilename();if(StringUtils.isEmpty(originalFilename)){throw new Exception("上传的文件不能为空");}if(!originalFilename.toLowerCase().endsWith(ExcelTypeEnum.XLS.getValue()) ||!originalFilename.toLowerCase().endsWith(ExcelTypeEnum.XLSX.getValue())   ){throw new Exception("上传的文件格式不匹配");}InputStream   inputStream = file.getInputStream();return EasyExcel.read(inputStream, clazz, analysisEventListener).sheet(sheetNo).headRowNumber(rowNumber).doReadSync();}}

Controller层接受文件上传方法以及处理导入数据

PS:(1)主要是用于接受前端请求的excel文件和调用数据的处理逻辑,以及返回处理之后的响应结果的导出;
(2)当然不一定是Controller层,如果是采取的微服务的架构,那么也就对应数据暴露接口的层;

/*** 执行 Excel的数据的导入* @param response* @param request*/public void importExcel(HttpServletResponse response, HttpServletRequest request) throws Exception {ExcelImportListener excelImportListener = new ExcelImportListener(handleImportExcelService, ExcelImportTemplateDto.class);// 读取数据ExcelUtil.readExcel(request, "file", 0, 1, excelImportListener, ExcelImportTemplateDto.class);// 错误集List<ImportFailDto> importFailDtoList = excelImportListener.getImportFailDtoList();if(!importFailDtoList.isEmpty()){List<ExcelTemplateCompleteDto> excelTemplateCompleteDtoList = importFailDtoList.stream().map(current->{ExcelTemplateCompleteDto excelTemplateCompleteDto = new ExcelTemplateCompleteDto();BeanUtils.copyProperties(current.getObject(), excelTemplateCompleteDto);// 设置错误信息excelTemplateCompleteDto.setErrMsg(current.getErrMsg());return excelTemplateCompleteDto;}).collect(Collectors.toList());//导出excel(PS:主要用于将错误的数据导出)String fileName = URLEncoder.encode("导入结果", "UTF-8");ExcelUtil.writeExcel(response, excelTemplateCompleteDtoList, fileName, ExcelTemplateCompleteDto.class);}}

彩蛋(读取项目模板直接下载)

场景

在有的时候,我们已经存在着模板,并且已经存放在项目中的某个目录中,那么如何快速读取项目中的模板文件并且实现导出呢?

步骤

注意:别忘记首先在项目中添加好模板文件哦!如下:

方法一:HttpServletResponse实现

/*** 通过 http response实现模板Excel文件的下载* @param response*/public void downloadExcelTemplate(HttpServletResponse response) throws IOException {InputStream in = null;OutputStream outputStream = null;try {// 设置导出文件名(PS:防止中文乱码)String fileName = URLEncoder.encode("测试文件.xlsx", "UTF-8");// 读取项目excel模板流in = this.getClass().getResourceAsStream("/template/" + fileName);// 设置response响应信息response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//防止导入excel文件名中文不乱码response.setHeader("Content-disposition", "attachment;fileName=" + fileName + ";fileName*=utf-8''" + fileName);outputStream = response.getOutputStream();int readSize = 0;byte[] buff = new byte[1024];while((readSize = in.read(buff)) > -1){outputStream.write(buff, 0, readSize);}outputStream.flush();}finally {if(in != null){in.close();}if(outputStream != null){outputStream.close();}}}

方法二:Response返回实现

/*** 通过 将流返回给前端实现模板Excel文件的下载*/public Response downloadExcelTemplate() throws IOException {InputStream in = null;OutputStream outputStream = null;try {// 设置导出文件名(PS:防止中文乱码)String fileName = URLEncoder.encode("测试文件.xlsx", "UTF-8");// 读取项目excel模板流in = this.getClass().getResourceAsStream("/template/" + fileName);/* 这一段可以简单的变为后续的那种写法ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();int readSize = 0;byte[] buff = new byte[1024];while((readSize = in.read(buff)) > -1){byteArrayOutputStream.write(buff, 0, readSize);}if(in != null){in.close();}StreamingOutput streamingOutput = out -> out.write(byteArrayOutputStream.toByteArray());*/// 简化版的写法byte[] bytes = IOUtils.toByteArray(in);StreamingOutput streamingOutput = out -> out.write(bytes);Response.ResponseBuilder responseBuilder = Response.ok(streamingOutput);responseBuilder.header("Content-disposition", "attachment;fileName=" + fileName + ";fileName*=utf-8''" + fileName);responseBuilder.type(MediaType.APPLICATION_OCTET_STREAM);return responseBuilder.build();}finally {if(in != null){in.close();}if(outputStream != null){outputStream.close();}}}

惊喜发生

别以为上面这样一写就大功告成了,当我们运行之后,我们会发现确实文件是下载下来了,但是打开文件就会出现如下的错误;

这个是为什么为什么呢?

原因分析

(1)下载的方式不对吗?
错,文件能下载说明下载的方式是OK的!
(2)Excel打开的限制?安全检查?
看到很多文章说到这个的原因,实际不是的,假设即使如此操作了还是无法解决呢?

(3)那么真实的原因呢?
这其实原因在于,当我们把excel作为项目目录中时,假设我们采取的框架是Spring或者SpringBoot时,其实是会将我们的excel文件进行压缩。而问题来了,正是因为压缩,导致我们下载下来的文件肯定就是缺失了某些字节文件的,因为我们下载的时候并没有还原文件。因此,这样下载下来肯定就是有问题的啦!!!!

解决办法

在项目的pom文件中,设置项目不需要将excel格式的文件进行压缩处理即可。

<plugin><groupId>org.apache.maven.plugins</groupId><version>2.6</version><artifactId>maven-resources-plugin</artifactId><configuration><encoding>UTF-8</encoding><nonFilteredFileExtensions><nonFilteredFileExtension>xlsx</nonFilteredFileExtension></nonFilteredFileExtensions></configuration></plugin>

总结

  1. 针对EasyExcel的常用的导入导出,在上面都已经进行了详细的描述。当然,这对于我们实际项目中可能遇到的场景还远远不够丰富,但是,主要是通过阅读这样的处理方式,而让我们能有“举一反三”的思想,这样才能更好的实现需求。
  2. 在实际中,easyExcel还是存在很多的弊端,因为它毕竟是同步调用的,而我们很多情况都是采取异步的处理方式。那么,这样我们又能如何去做呢?(1)将处理方式通过异步方式进行后台异步的处理,当处理完成再回调;(2)可以通过公司中的文件服务器进行作为中间层,然后再利用消息队列的方式或者redis的形式将需要处理的内容进行告知程序再进行处理。(3)还有很多的方法的,打开你的小脑瓜,相信你一定可以的。
  3. 此外,建议大家可以多看看easyExcel的源码,然后自己封装一层,能作为更好更通用的“导入导出”工具。这也是很体现一个人的能力的呢。
  4. 建议大家如果有需要的话,直接拷贝对应内容即可,而不需要我一一发送源码了,因为关键的内容都在文章中进行了详细的说明;
  5. 最后,感谢各位的阅读哦!!
  6. 如果你想利用闲暇零散的学习技术,那么不妨关注我的公众号阅读你想要的文章哦!
    公众号搜索:Java菜鸟进阶之路

手把手教你如何玩转EasyExcel的导入和导出相关推荐

  1. 手把手教你如何玩转消息中间件(ActiveMQ)

    原 手把手教你如何玩转消息中间件(ActiveMQ) 2018年07月15日 18:07:39 Cs_hnu_scw 阅读数 6494 </div><div class=" ...

  2. 【手把手教你】玩转Python金融量化利器之Pandas

    前言 "手把手教你"系列将为Python初学者一一介绍Python在量化金融中运用最广泛的几个库(Library): NumPy(数组.线性代数).SciPy(统计).pandas ...

  3. Cobo钱包v2公测版来了,手把手教你怎么玩

    之前有很多朋友问悦动智能,为什么Cobo钱包不需要导入私钥和助记词,很简单,考虑到个人保管私钥对于很多初入门的朋友来说操作繁琐,且很容易丢失.因此,Cobo首先做了一款对小白用户非常友好的托管钱包,由 ...

  4. 手把手教你如何用Python从PDF文件中导出数据(附链接)

    作者:Mike Driscoll :翻译:季洋:校对:丁楠雅 本文约4000字,建议阅读10分钟. 本文介绍了在提取出想要的数据之后,如何将数据导出成其他格式的方法. 有很多时候你会想用Python从 ...

  5. EasyExcel的导入和导出

    导入: 下面是从http请求流中读取数据流,其他形式的数据流道理相同: 逻辑代码: @PostMapping(value = "import", consumes = " ...

  6. 手把手教你如何玩转插件:通用Mapper和Mybatis generator

    情景引入: 小白:起床起床了,,,,太阳都晒屁股了. 我:好不容易有个睡懒觉的机会,你又把我吵起来干嘛呢?不能给我放一个假吗? 小白:不可以不可以,我又遇到了一个问题,我需要你的帮忙,你赶紧帮我想想办 ...

  7. 怎么把分钟转化成秒_怎么利用抖音短视频引流,手把手教你如何玩转抖音流量...

    现在抖音的影响力越来越大,用户也是越来越多:一个流量这么聚集的地方,营销者们怎么可能会放弃这么个好的流量池?各行各业纷纷出动,只为在这块"肥地"分的一杯羹. 2018年,抖音APP ...

  8. 五阿哥钢铁电商资深运维工程师手把手教你这样玩企业组网

    虽说干的是信息化智能化的行当,但每个IT工程师都必定踩过"IT系统不智能"的坑.就拿企业组建局域网来说,为了对网络接入用户身份进行确认,确保用户权限不受办公地点变更的影响,许多IT ...

  9. 手把手教你如何玩转插件:分页插件(Pagehelper)

    情景引入: 小白:起床起床,,,快起床!!! 我:怎么怎么了,小白你到底又怎么了.. 小白:我发现在Web系统中,分页是一种很常见的功能,可是,我之前写的方法都比较麻烦,移植性不是很高,有没有什么好办 ...

最新文章

  1. android 自定义ViewGroup和对view进行切图动画实现滑动菜单SlidingMenu[转]
  2. iOS10系统下调用系统功能权限以及相关设置
  3. 一张图探秘施耐德电气全生命周期服务的无限可能
  4. wget提示失败的解决办法
  5. 搜索推荐炼丹笔记:CVR预估中的延迟反馈问题
  6. 【项目介绍】协程——C语言实现的用户态非抢占式轻量级线程
  7. 【渝粤题库】国家开放大学2021春3929电气安全技术题目
  8. SiameseSentenceSimilarity相似句子匹配分类项目
  9. 2020年中国餐饮配送机器人行业研究报告
  10. 【Elasticsearch】es 远程调试
  11. 【Qt】使用QProcess调用其它程序或脚本
  12. 形式化方法对软件开发的挑战:一些历史与现实
  13. 矩阵的生成及对角线运算
  14. FreeMarker模板导出pdf,页码,页脚,分页等
  15. python已知两条直角边求斜边,Python实现“已知三角形两个直角边,求斜边”
  16. Glassfish Request URI is too large 问题的解决
  17. 微信小程序学习第6周————模块化
  18. MySQL是如何解决幻读
  19. 工业级洗地机器人_基于多传感器融合的自动洗地机器人避障研究
  20. Oracle 10.2 概念 第一章 Oracle介绍

热门文章

  1. ppt怎么加注解文字_演讲PPT注释字幕添加方法
  2. 从零开始的2.5D游戏开发
  3. 关于Axure RP 的授权,我猜你还想知道......
  4. android中的长度单位介绍(dp,sp,px,in,pt,mm)
  5. 【图像去噪】基于matlab高斯+均值+中值+双边滤波图像去噪【含Matlab源码 1872期】
  6. linux fastQC 操作命令,10 月 17 日 Linux Fastqc 软件安装
  7. C++错误 C1189
  8. 东半球最好的SecureCRT高级教程
  9. “科林明伦杯”哈尔滨理工大学暑假训练赛 B吃雪糕 (异或思维题)(补题)
  10. SAP中常见的Debug技巧(02)-跳过代码执行