一、前言
开发中经常会设计到excel的处理,如导出Excel,导入Excel到数据库中!
首先我们先简单了解一下Excel:
Excel xls和xlsx有什么区别:
1、文件格式不同。xls 是一个特有的二进制格式,其核心结构是复合文档类型的结构,而 xlsx 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小。xlsx 中最后一个 x 的意义就在于此。
2、版本不同。xls是excel2003及以前版本生成的文件格式,而xlsx是excel2007及以后版本生成的文件格式。
3、兼容性不同。xlsx格式是向下兼容的,可兼容xls格式。
二、关于 Apache POI 和EsayExcel 的选择:
Apache POI 是通过内存一次性全部读取 然后在输出, ,EasyExcel 能大大减少占用内存的主要原因是在解析 Excel 时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。阿里的大佬们在设计EsayExcel时 也是基于POI开发 不过使用起来比较轻便,通过注解和一行代码就可以解决Excel导入和导出。
下面我们来看看大佬们总结的两种方式实现Excel的解析过程图:
三、 我设计的方便的工具类 来完成写出 大家可以直接CV大法 就能使用 亲测有效
<!--相关依赖:阿里Excel文件处理-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
1、EasyExcelUtils:
import com.alibaba.excel.EasyExcel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
/**
* @author zhu
* @date 2021/4/27 14:10
* EasyExcel 工具 传入Excel必要的参数
*/
@Data
@Slf4j
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class EasyExcelUtil {
/**
* Excel下载文件名
*/
private String excelName;
/**
* 工作表 ps:如果需要支持多表 请自定义添加对应的sheetName
*/
private String sheetName;
/**
* Excel数据
*/
private List<?> resultList;
/**
* EasyExcel 写出
*
* @param easyExcelUtil 自定义参数对象
* @param clazz 指定返回表格对象 ps:用注解定义行和列
* @throws UnsupportedEncodingException 编码异常 解决字符编码问题。
*/
public static void create(EasyExcelUtil easyExcelUtil, Class<?> clazz) throws UnsupportedEncodingException {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
//避免response报错空指针异常
assert requestAttributes != null;
HttpServletResponse httpServletResponse = requestAttributes.getResponse();
//下载Excel名称
String fileName = new String(
(easyExcelUtil.getExcelName() + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".xlsx")
.getBytes(), StandardCharsets.UTF_8);
//处理中文乱码
fileName = new String(fileName.getBytes(), "ISO_8859_1");
//导出Excel 设置两个头 响应内容格式
//避免response报错空指针异常
assert httpServletResponse != null;
httpServletResponse.setContentType("application/vnd.ms-excel;charset=UTF-8");
//设置前端下载文件名
httpServletResponse.setHeader("Content-disposition", "attachment;filename=" + fileName);
try {
//向前端写入文件流流
EasyExcel.write(httpServletResponse.getOutputStream(), clazz)
.sheet(easyExcelUtil.getSheetName())
//此步骤是开启EasyEXCEL自适应列宽
.registerWriteHandler(new CustomCellWriteHandler())
.doWrite(easyExcelUtil.getResultList());
} catch (IOException e) {
log.info("[EasyExcelUtil-create] Excel下载出错");
}
}
}
二、EasyEXCEL自适应列宽:
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author zhu
* @date 2021/4/25 17:14
* EasyEXCEL自适应列宽
* 在导出时注册registerWriteHandler(new CustomCellWriteHandler())
*/
public class CustomCellWriteHandler extends AbstractColumnWidthStyleStrategy {
private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>();
@Override
protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) {
boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
if (needSetWidth) {
Map<Integer, Integer> maxColumnWidthMap = CACHE.get(writeSheetHolder.getSheetNo());
if (maxColumnWidthMap == null) {
maxColumnWidthMap = new HashMap<>();
CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
}
Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
if (columnWidth >= 0) {
if (columnWidth > 255) {
columnWidth = 255;
}
Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
}
}
}
}
private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {
if (isHead) {
return cell.getStringCellValue().getBytes().length;
} else {
CellData cellData = cellDataList.get(0);
CellDataTypeEnum type = cellData.getType();
if (type == null) {
return -1;
} else {
switch (type) {
case STRING:
return cellData.getStringValue().getBytes().length;
case BOOLEAN:
return cellData.getBooleanValue().toString().getBytes().length;
case NUMBER:
return cellData.getNumberValue().toString().getBytes().length;
default:
return -1;
}
}
}
}
}
三、自定义类型转换器: ps:一个类型转换器只支持一个类型 不能在一个转换器里写多个方法实现
因为 implements Converter 我们自定义的都是 CellData 方法
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
/**
* @author zhu
* @date 2021/4/25 14:01
* EasyExcel 自定义转换器
*/
public class TypeConverter implements Converter<Integer> {
/**
* 类型(1-新增 2-扩科 3-续费 4-新增连报 5-新增体验)
*/
private static final String NEW = "新增";
private static final String EXTENSION = "扩科";
private static final String RENEWAL = "续费";
private static final String NEW_EVEN = "新增连报";
private static final String NEW_EXPERIENCE = "新增体验";
private static final String NULL_CHARACTER = " ";
@Override
public Class supportJavaTypeKey() {
return Integer.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty,
GlobalConfiguration globalConfiguration) throws Exception {
return null;
}
@Override
public CellData convertToExcelData(Integer num, ExcelContentProperty excelContentProperty,
GlobalConfiguration globalConfiguration) throws Exception {
switch (num) {
case 1:
return new CellData(NEW);
case 2:
return new CellData(EXTENSION);
case 3:
return new CellData(RENEWAL);
case 4:
return new CellData(NEW_EVEN);
case 5:
return new CellData(NEW_EXPERIENCE);
default:
return new CellData(NULL_CHARACTER);
}
}
}
四、测试:
1、返回Excel数据的实体类对象
import cn.njcool.backend.edu.common.utils.TypeConverter;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Builder;
import lombok.Data;
/**
* @author zhu
* @date 2021/4/27 16:24
*/
@Data
@Builder
@ExcelIgnoreUnannotated
/**
* @ExcelIgnoreUnannotated
* 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与
* 所以我们在某些场景下不需要ID 用Excel的自动排序 就开启该注解
*/
public class StudentDO {
/**
* 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
* 但是在类上需要开启@ExcelIgnoreUnannotated
*/
@ExcelIgnore
private Integer id;
//第一列序号为0
@ExcelProperty(value = "学员名称", index = 0)
private String name;
/**
* @ExcelProperty(value = "学员名称", index = 0)
* 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,
* 以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。
*/
@ExcelProperty(value = "学员爱好", index = 1)
private String hobby;
/**
* 类型(1-新增 2-扩科 3-续费 4-新增连报 5-新增体验
* 转换器,默认加载了很多转换器。也可以自定义 指向你自定义的转换器
*/
@ExcelProperty(value = "类型", index = 2, converter = TypeConverter.class)
private Integer type;
/**
* DateTimeFormat 日期转换,
* 用String去接收excel日期格式的数据会调用这个注解。
* 里面的value参照java.text.SimpleDateFormat
*/
// @ExcelProperty(value = "时间", index = 3)
// @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
// @DateTimeFormat(pattern = "yyyy-MM-dd")
// private String date;
}
2、控制层方法调用:
具体的业务逻辑可以和之前一样查询 如果rsulstList为空则返回空表数据
@ApiOperation(value = "Excel下载测试")
@GetMapping("test")
public void Test() throws UnsupportedEncodingException {
//就不查数据库了 一层撸到底 大家一下就看懂了
//准备数据
List<StudentDO> resultList = new ArrayList<>();
StudentDO StudentDO1 = StudentDO.builder().id(1).name("哈拉少").hobby("洗澡的干活").type(1).build();
StudentDO StudentDO2 = StudentDO.builder().id(2).name("龚大人").hobby("摸鱼").type(2).build();
StudentDO StudentDO3 = StudentDO.builder().id(3).name("秋桑").hobby("钓鱼").type(3).build();
StudentDO StudentDO4 = StudentDO.builder().id(4).name("文总").hobby("分析一波").type(4).build();
StudentDO StudentDO5 = StudentDO.builder().id(5).name("为老总").hobby("18禁").type(5).build();
resultList.add(StudentDO1);
resultList.add(StudentDO2);
resultList.add(StudentDO3);
resultList.add(StudentDO4);
resultList.add(StudentDO5);
//构建生成Excel对象
EasyExcelUtil excel = EasyExcelUtil.builder()
.excelName("应天打工人")
.sheetName("个人介绍")
.resultList(resultList)
.build();
//Run
EasyExcelUtil.create(excel,StudentDO.class);
}
Ps: 小结用psotman调用 httpRespon 并不会被解析 我们需要用浏览器进行调用解析
下载完成之后的表: 已经执行了我们的自定义转换器
五、总结
目前还总结到导出 写入还没有了解释 不足之处请大家多批评指正 欢迎留言!!!