手把手教你如何玩转EasyExcel的导入和导出

文章目录

  • 情景引入
  • 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的版本


  com.alibaba
  easyexcel
  2.0.5

导出

**为什么要先说导出而不说导入呢?**其实,很简单,我们在导入数据的时候,其实都有一定的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 explicitListConstraintMap = new HashMap();

        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();
    }

分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 currentExportList = new ArrayList<>();
            // 当前sheet的序号
            int belongSheetOrder = currentExportTotalNumber / pageEverySheetNumber;
            // 判断是否需要创建新的sheet
            if(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依赖


            org.springframework
            spring-web
            4.3.7.RELEASE
        
        
            com.alibaba
            easyexcel
            2.0.2
        

上传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 successDtoList;
    // 导入失败的消息列表
    private List failDtoList;

    public ExcelImportResultDto(List successDtoList, List failDtoList) {
        this.successDtoList = successDtoList;
        this.failDtoList = failDtoList;
    }

    public ExcelImportResultDto(List failDtoList) {
        this.failDtoList = failDtoList;
        this.successDtoList = new ArrayList<>();
    }

    public List getSuccessDtoList() {
        return successDtoList;
    }

    public void setSuccessDtoList(List successDtoList) {
        this.successDtoList = successDtoList;
    }

    public List getFailDtoList() {
        return failDtoList;
    }

    public void setFailDtoList(List 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  ExcelImportResultDto checkImportData(List 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  extends AnalysisEventListener{
    // 导入成功的数据
    private List importSuccessDtoList = new ArrayList<>();
    // 导入失败的数据
    private List importFailDtoList = new ArrayList<>();

    private List list = new ArrayList<>();
    // 处理导入数据的逻辑
    private HandleImportExcelService handleImportExcelService;

    private Class tClass;

    public List getImportSuccessDtoList() {
        return importSuccessDtoList;
    }

    public void setImportSuccessDtoList(List importSuccessDtoList) {
        this.importSuccessDtoList = importSuccessDtoList;
    }

    public List getImportFailDtoList() {
        return importFailDtoList;
    }

    public void setImportFailDtoList(List importFailDtoList) {
        this.importFailDtoList = importFailDtoList;
    }

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

    public HandleImportExcelService getHandleImportExcelService() {
        return handleImportExcelService;
    }

    public void setHandleImportExcelService(HandleImportExcelService handleImportExcelService) {
        this.handleImportExcelService = handleImportExcelService;
    }

    public Class gettClass() {
        return tClass;
    }

    public void settClass(Class tClass) {
        this.tClass = tClass;
    }

    public ExcelImportListener(HandleImportExcelService handleImportExcelService) {
        this.handleImportExcelService = handleImportExcelService;
    }

    public ExcelImportListener(HandleImportExcelService handleImportExcelService, Class tClass) {
        this.handleImportExcelService = handleImportExcelService;
        this.tClass = tClass;
    }

    /**
     * 导入数据的处理逻辑
     * @param t
     * @param analysisContext
     */
    @Override
    public 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
     */
    @Override
    public 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
     */
    @Override
    public void invokeHeadMap(Map headMap, AnalysisContext analysisContext){
        super.invokeHeadMap(headMap, analysisContext);
        if(tClass != null){
            try {
            // 获取Excel导入实体的单元格内容
                Map indexNameMap = getIndexName(tClass);
                Set 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 getIndexName(Class tClass) throws NoSuchFieldException {
        Map 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 
     * @return
     */
    public static  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 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 importFailDtoList = excelImportListener.getImportFailDtoList();
        if(!importFailDtoList.isEmpty()){
            List 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);
        }
    }

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

场景

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

步骤

注意:别忘记首先在项目中添加好模板文件哦!如下:
手把手教你如何玩转EasyExcel的导入和导出_第1张图片

方法一: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();
            }
        }
    }

惊喜发生

别以为上面这样一写就大功告成了,当我们运行之后,我们会发现确实文件是下载下来了,但是打开文件就会出现如下的错误;
手把手教你如何玩转EasyExcel的导入和导出_第2张图片
这个是为什么为什么呢?

原因分析

(1)下载的方式不对吗?
错,文件能下载说明下载的方式是OK的!
(2)Excel打开的限制?安全检查?
看到很多文章说到这个的原因,实际不是的,假设即使如此操作了还是无法解决呢?
手把手教你如何玩转EasyExcel的导入和导出_第3张图片

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

解决办法

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


				org.apache.maven.plugins
				2.6
				maven-resources-plugin
				
					UTF-8
					
						xlsx
					
				
			

总结

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

你可能感兴趣的:(JavaWeb)