easyexcel介绍与使用
序言
本文介绍 导入、导出 主要功能操作 ,包括了以压缩包形式导出、模板导出、以及写导出、多个sheet页导出、复杂树形,层级导出、自定义合并单元格,动态填充头等操作示例
1.介绍
Java解析、生成Excel比较有名的框架有Apache
poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI
sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
1.1网址
官方网站:https://easyexcel.opensource.alibaba.com/
github地址:https://github.com/alibaba/easyexcel
gitee地址:https://gitee.com/easyexcel/easyexcel
1.普通导出
1.1引入jar包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
1.2处理导出列
@ContentRowHeight(20)
@HeadRowHeight(30)
@ColumnWidth(25)
@Data
public class ModelExtractionExcel {
@ExcelProperty(
value = {"字段名称"},
index = 0
)
private String key;
@ExcelProperty(
value = {"字段值"},
index = 1
)
private String value;
}
1.3 具体导出逻辑
public void downloadModelExtraction(HttpServletResponse response,Long id,String name) {
ExcelWriter excelWriter = null;
try {
//查询提取信息
List<ModelOperationExtractionDtl> attachmentExtractionList = modelOperationExtractionDtlMapperExt.getAttachmentExtraction(id,null);
List<ModelExtractionExcel> modelExtractionExcels = BeanUtil.copyToList(attachmentExtractionList, ModelExtractionExcel.class);
//构造模板导入信息
response.setContentType(Constants.CONTENT_TYPE_EXCEL);
response.setCharacterEncoding(Charsets.UTF_8.name());
// 防止中文乱码
String encodeFileName = URLEncoder.encode(name, Charsets.UTF_8);
response.setHeader(Constants.FIELD_CONTENT_DISPOSITION, "attachment;filename=" + encodeFileName + Constants.FIELD_SUFFIX_XLSX);
excelWriter = EasyExcel.write(response.getOutputStream()).build();
WriteSheet importSheet = EasyExcel.writerSheet(0, "提取数据").head(ModelExtractionExcel.class).build();
//write
excelWriter.write(modelExtractionExcels, importSheet);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
}
2.普通导入
@PostMapping("/import")
@Operation(summary = "导入")
public Result imports(@RequestParam("file") MultipartFile file) throws IOException {
InputStream inputStream = file.getInputStream();
List<CoachRecordImport> reqCustomerImports = EasyExcel.read(inputStream)
.head(CoachRecordImport.class)
// 注册监听器,可以在这里校验字段
.registerReadListener(new `CustomerCoachImportListener`(coachService))
// 设置sheet,默认读取第一个
.sheet(Constant.SHEET_NUM)
// 设置标题所在行数
.headRowNumber(Constant.HEAD_ROW_NUMBER)
.doReadSync();
return coachService.importAddCoach(reqCustomerImports);
}
@Data
public class CoachRecordImport implements Serializable {
@Serial
private static final long serialVersionUID = -3119830383757608829L;
@ExcelProperty(index = 0, value = "姓名")
private String name;
@ExcelProperty(index = 1, value = "性别", converter = SexConverter.class)
private String gender;
@ExcelProperty(index = 2, value = "身份证号")
private String idCard;
@ExcelProperty(index = 3, value = "年龄")
private Integer age;
@ExcelProperty(index = 4, value = "手机号")
private String phoneNumber;
@ExcelProperty(index = 5, value = "服务课程")
private String serviceCourseIds;
}```
```java
package com.insoundai.childrenplatform.operation.service.listener;
import cn.hutool.core.util.IdcardUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.insoundai.childrenplatform.operation.common.exception.ServiceException;
import com.insoundai.childrenplatform.operation.pojo.impt.CoachRecordImport;
import com.insoundai.childrenplatform.operation.service.CoachService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Objects;
/**
* @Author: GZ
* @CreateTime: 2022-09-19 14:52
* @Description: 自定义导入监听类 对模板与字段进行校验
* @Version: 1.0
*/
@Slf4j
public class CustomerCoachImportListener extends AnalysisEventListener<CoachRecordImport> {
public CoachService coachService;
public CustomerCoachImportListener(CoachService coachService) {
this.coachService = coachService;
}
/**
* 在这里进行模板的判断
* @param headMap 存放着导入表格的表头,键是索引,值是名称
* @param context
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
int count = 0;
// 获取数据实体的字段列表
Field[] fields = CoachRecordImport.class.getDeclaredFields();
// 遍历字段进行判断
for (Field field : fields) {
// 获取当前字段上的ExcelProperty注解信息
ExcelProperty fieldAnnotation = field.getAnnotation(ExcelProperty.class);
// 判断当前字段上是否存在ExcelProperty注解
if (fieldAnnotation != null) {
++count;
// 存在ExcelProperty注解则根据注解的index索引到表头中获取对应的表头名
String headName = headMap.get(fieldAnnotation.index());
// 判断表头是否为空或是否和当前字段设置的表头名不相同
if (StringUtils.isEmpty(headName) || !headName.equals(fieldAnnotation.value()[0])) {
// 如果为空或不相同,则抛出异常不再往下执行
throw new RuntimeException("模板错误,请检查导入模板");
}
}
}
// 判断用户导入表格的标题头是否完全符合模板
if (count != headMap.size()) {
throw new RuntimeException("模板错误,请检查导入模板");
}
}
@Override
public void invoke(CoachRecordImport coachRecordImport, AnalysisContext analysisContext) {
System.out.println("CustomerDailyImportListener------->");
log.info("----------CustomerImportListener is start------------");
//校验身份号是否合法
if (Objects.nonNull(coachRecordImport)) {
if (!Objects.isNull(coachRecordImport.getIdCard())) {
boolean validCard = IdcardUtil.isValidCard(coachRecordImport.getIdCard());
if (!validCard) {
throw new ServiceException("导入失败,该身份证:" + coachRecordImport.getIdCard() + "不合法");
}
}
}
}
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
// ExcelDataConvertException:当数据转换异常的时候,会抛出该异常,此处可以得知第几行,第几列的数据
if (exception instanceof ExcelDataConvertException) {
Integer columnIndex = ((ExcelDataConvertException) exception).getColumnIndex() + 1;
Integer rowIndex = ((ExcelDataConvertException) exception).getRowIndex() + 1;
String message = "第" + rowIndex + "行,第" + columnIndex + "列" + "数据格式有误,请核实";
throw new RuntimeException(message);
} else if (exception instanceof RuntimeException) {
throw exception;
} else {
super.onException(exception, context);
}
}
/**
* @param analysisContext
* @description: 解析完成全步回调
* @author: GZ
* @date: 2022/09/19 14:56
* @return: void
**/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
字段转换
public class SexConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return Converter.super.supportJavaTypeKey();
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) throws Exception {
return "男".equals(cellData.getStringValue()) ? "1" : "0";
}
}
3.导出全部(以压缩包形式导出)
public void downloadAllModelExtraction(HttpServletResponse response, Long modelId) {
List<ExcelUtils.ExcelData> excelDataList = Lists.newArrayList();
//查询模型提取数据
List<ModelOperationExtractionDtl> attachmentExtractionList = modelOperationExtractionDtlMapperExt.getAttachmentExtraction(null,modelId);
Assert.isTrue(!CollectionUtils.isEmpty(attachmentExtractionList),"提取数据为空,还末提取完成");
//按照附近id进行分组
Map<Long, List<ModelOperationExtractionDtl>> attachmentMap= attachmentExtractionList.stream()
.collect(Collectors.groupingBy(ModelOperationExtractionDtl::getOperationId));
attachmentMap.forEach((key,value)->{
ExcelUtils.ExcelData excel = new ExcelUtils.ExcelData();
List<ModelExtractionExcel> modelExtractionList = BeanUtil.copyToList(value, ModelExtractionExcel.class);
//设置文件名
excel.setFilename(reFileName(value,attachmentExtractionList,key));
//不设置下载模板
excel.setTemplateFilename(null);
//设置sheet数据
excel.addShellData(new ExcelUtils.ExcelShellData<>(modelExtractionList, "提取数据", ModelExtractionExcel.class));
excelDataList.add(excel);
});
//通过模型id查询模型名称
String name= modelService.getModelName(modelId);
String zipName=name+ DateUtil.now();
ExcelUtils.exportZip(zipName, excelDataList, response);
}
package com.insound.license.recognition.backend.util;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.google.common.base.Charsets;
import com.insound.license.recognition.backend.pojo.constant.Constants;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @Author: GZ
* @CreateTime: 2022-12-29 14:44
* @Description: excel工具类
* @Version: 1.0
*/
public class ExcelUtils {
/**
* 导出多个sheet到多个excel文件,并压缩到一个zip文件 支持模板和直接创建
*/
public static void exportZip(String zipFilename, List<ExcelData> excelDataList, HttpServletResponse response) {
if (zipFilename == null || zipFilename.isEmpty()) {
zipFilename = "export";
} else if (zipFilename.toLowerCase(Locale.ROOT).endsWith(".zip")) {
zipFilename = zipFilename.substring(0, zipFilename.length() - 4);
}
if (excelDataList == null || excelDataList.isEmpty()) {
throw new RuntimeException("导出数据为空!");
}
try {
zipFilename = URLEncoder.encode(zipFilename, Charsets.UTF_8);
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + zipFilename + ".zip");
response.setContentType("application/x-msdownload");
response.setCharacterEncoding("utf-8");
//开始存入
try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
try {
for (ExcelData excelData : excelDataList) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ExcelWriter excelWriter = null;
try {
ExcelWriterBuilder builder = EasyExcel.write(outputStream).autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert());
if (excelData.getTemplateFile() != null) {
builder.withTemplate(excelData.getTemplateFile());
}
excelWriter = builder.build();
zipOut.putNextEntry(new ZipEntry(excelData.getFilename()));
if (excelData.getTemplateFile() != null) {
for (ExcelShellData<?> shellData : excelData.getShellDataList()) {
WriteSheet writeSheet = EasyExcel.writerSheet(shellData.getSheetName()).build();
excelWriter.fill(shellData.getList(), writeSheet);
}
} else {
//开始写入excel
for (ExcelShellData<?> shellData : excelData.getShellDataList()) {
WriteSheet writeSheet = EasyExcel.writerSheet(shellData.getSheetName()).head(shellData.getClazz()).build();
excelWriter.write(shellData.getList(), writeSheet);
}
}
} catch (Exception e) {
throw new RuntimeException("导出Excel异常", e);
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
outputStream.writeTo(zipOut);
zipOut.closeEntry();
}
} catch (Exception e) {
throw new RuntimeException("导出Excel异常", e);
}
}
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* excel数据
*/
public static class ExcelData {
private List<ExcelShellData<?>> shellDataList = new ArrayList<>();
private String filename = "export.xlsx";
private String templateFilename;
public List<ExcelShellData<?>> getShellDataList() {
return shellDataList;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getTemplateFilename() {
return templateFilename;
}
public void setTemplateFilename(String templateFilename) {
this.templateFilename = templateFilename;
}
public File getTemplateFile() {
if (templateFilename == null) {
return null;
}
File templateFile = new File(templateFilename);
if (templateFile.exists() && templateFile.isFile()) {
return templateFile;
}
Resource resource = new ClassPathResource(templateFilename);
if (resource.exists()) {
File file = null;
try {
file = resource.getFile();
} catch (IOException e) {
e.printStackTrace();
}
if (file != null) {
if (file.exists() && file.isFile()) {
return file;
}
return null;
}
}
return null;
}
public void setShellDataList(List<ExcelShellData<?>> shellDataList) {
if (shellDataList != null) {
this.shellDataList = shellDataList;
}
}
public String getFilename() {
if (filename == null || filename.isEmpty()) {
filename = "export.xlsx";
} else {
String fn = filename.toLowerCase(Locale.ROOT);
if (!(fn.endsWith(".xlsx") || fn.endsWith(".xls"))) {
filename = filename + ".xlsx";
}
}
return filename;
}
public void addShellData(ExcelShellData<?> excelShellData) {
this.shellDataList.add(excelShellData);
}
}
/**
* sheet数据
*/
public static class ExcelShellData<T> {
private List<T> list;
private String sheetName;
private Class<T> clazz;
public ExcelShellData(List<T> list, String sheetName, Class<T> clazz) {
this.list = list;
this.sheetName = sheetName;
this.clazz = clazz;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
public String getSheetName() {
return sheetName;
}
public void setSheetName(String sheetName) {
this.sheetName = sheetName;
}
public Class<T> getClazz() {
return clazz;
}
public void setClazz(Class<T> clazz) {
this.clazz = clazz;
}
}
/**
* Excel 数值长度位15位 大于15位的数值转换位字符串
*/
public static class ExcelBigNumberConvert implements Converter<Long> {
@Override
public Class<Long> supportJavaTypeKey() {
return Long.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
Object data = cellData.getData();
if (data == null) {
return null;
}
String s = String.valueOf(data);
if (s.matches("^\\d+$")) {
return Long.parseLong(s);
}
return null;
}
@Override
public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if (object != null) {
String str = object.toString();
if (str.length() > Constants.EXCEL_VALUE_LENGTH) {
return new WriteCellData<>(str);
}
}
WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
cellData.setType(CellDataTypeEnum.NUMBER);
return cellData;
}
}
}