小白:起床起床,快起床!!!
我:小白,你又怎么了,每次来都是这样火急火燎,成年人了能不能沉稳一点呢?
小白:我要被需求给打败了,因为老是碰到这样的功能;
我:咦,说说看,怎么样的需求把你折磨成这个样子,还打扰了我的美梦;
小白:就是老是碰到前端页面数据的导入和导出功能,而且还是用Excel格式的文件,可烦了。
我:这很正常呀,Excel的导入导出是常见的功能,特别是月末月初,各种统计表格来来回回。
小白:对呀,都要被各种Excel的模板给折磨疯了,能不能告诉我一些常用的导入导出的方法呀,拯救一下我;
我:好吧好吧,看你这么可怜的情面上,就给你科普科普,快去搬小板凳来吧!
小白:端端正正的坐好,等待上课!
对于EasyExcel,我想大家都不会太陌生,从它的名字就可以看出来,它就是为了Excel文件而生。那么,它到底是怎样的一个东西呢?
Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc。
在这样的一种场景下,EasyExcel就诞生了,其是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称,因此,也受到了很多公司和个人的喜爱。
建议项目采取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:其中的还有很多注解这个就不多说了,如果不明白的可以百度看看哦。
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 模板
* @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();
}
场景:有时候我们会遇到导出的数据量较大,而每个单元页显示的内容的条数有一定的限制,那么如何实现“分页”单元页的导出呢?
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单元导出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();
}
}
上面已经针对导出功能进行了讲解,那么接下来就说说导入又是如何实现的呢?
org.springframework
spring-web
4.3.7.RELEASE
com.alibaba
easyexcel
2.0.2
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);
}
}
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;
}
}
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;
}
}
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
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);
}
}
在有的时候,我们已经存在着模板,并且已经存放在项目中的某个目录中,那么如何快速读取项目中的模板文件并且实现导出呢?
/**
* 通过 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();
}
}
}
/**
* 通过 将流返回给前端实现模板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格式的文件进行压缩处理即可。
org.apache.maven.plugins
2.6
maven-resources-plugin
UTF-8
xlsx