编写Controller:
@GetMapping("/exportExcel")
public void exportExcel() throws IOException {
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("demo", "UTF-8"); //.replaceAll("\\+", "%20")
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
List<Students> students = new ArrayList<>();
students.add(new Students("java05","zkq"));
students.add(new Students("java05","yue"));
students.add(new Students("java05","xxx"));
students.add(new Students("java06","xxx"));
students.add(new Students("java06","xxx"));
students.add(new Students("java04","xxx"));
students.add(new Students("java04","xxx"));
students.add(new Students("java03","xxx"));
students.add(new Students("java05","xxx"));
EasyExcel.write(response.getOutputStream(), Students.class)
.excelType(ExcelTypeEnum.XLSX)
.autoCloseStream(Boolean.TRUE)
// 添加自定义处理程序,相当于Spring的AOP切面
// new HashSet(Arrays.asList(0))) 接受一个Set 表示要合并的列 第一列为0 ,例子:0,1 (表示合并第一列和第二列其他列忽略,编程下标一般是从0开始,所以0就是第一列)
.registerWriteHandler(new ExcelFillCellMergeStrategy(new HashSet<Integer>(Arrays.asList(0))))
.sheet("工作表").doWrite(students);
}
编写只定义处理程序:该备注的我都备注了(这里借鉴的谁的忘记地址了,太久了,原著看到回复必加链接)
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.List;
import java.util.Set;
/**
* @Description EasyExcel 导出合并单元格,这里只有纵向合并,如果想横向合并,可扩展
* @Author 张凯强
* @Date Created in 2021/9/28
* @E-mail [email protected]
*/
@Slf4j
public class ExcelFillCellMergeStrategy implements CellWriteHandler {
/*
* 要合并的列 (下表也是从0开始)
*/
private Set<Integer> mergeColumnIndex;
/*
* 用第几行开始合并 ,默认为1,因为第0行是标题,EasyExcel 的默认也是
*/
private int mergeBeginRowIndex = 1;
public ExcelFillCellMergeStrategy(Set<Integer> mergeColumnIndex) {
this.mergeColumnIndex = mergeColumnIndex;
}
/*
* 在创建单元格之前调用
*/
@Override
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {
}
/*
* 在创建单元格之后调用
*/
@Override
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
}
/*
* 在单元格数据转换后调用
*/
@Override
public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {
}
/*
* 在对单元格的所有操作完成后调用
*/
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
//当前行
int curRowIndex = cell.getRowIndex();
//当前列
int curColIndex = cell.getColumnIndex();
if (curRowIndex > mergeBeginRowIndex) {
if (mergeColumnIndex.contains(curColIndex)) {
mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
}
}
}
/**
* 当前单元格向上合并
*
* @param writeSheetHolder
* @param cell 当前单元格
* @param curRowIndex 当前行
* @param curColIndex 当前列
*/
private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
//获取当前行的当前列的数据和上一行的当前列列数据,通过上一行数据是否相同进行合并
Object curData = cell.getCellTypeEnum() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);
Object preData = preCell.getCellTypeEnum() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue();
// 比较当前行的第一列的单元格与上一行是否相同,相同合并当前单元格与上一行
if (curData.equals(preData)) {
Sheet sheet = writeSheetHolder.getSheet();
// 获取合并信息
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
int size = mergeRegions.size();
CellRangeAddress cellRangeAddr;
if(size > 0){
cellRangeAddr = mergeRegions.get(size-1);
// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) {
// 移除当前合并信息
sheet.removeMergedRegion(size-1);
// 重新设置当前结束行
cellRangeAddr.setLastRow(curRowIndex);
}else {
// 若上一个单元格未被合并,则新增合并单元
cellRangeAddr = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
}
}else {
// 若上一个单元格未被合并,则新增合并单元
cellRangeAddr = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
}
// 添加新的合并信息
sheet.addMergedRegion(cellRangeAddr);
}
}
}
我用的Swagger2 不会的可看:SpringBoot集成swagger2教程
导出成功:http://localhost:8080/exportExcel
编写Controller:
@PostMapping("/importExcel")
public String importExcel(@RequestPart("file") MultipartFile file) throws IOException {
List<Students> students = new ArrayList<>();
EasyExcel.read(file.getInputStream(),Students.class,new EasyExcelUtils.EasyEventListener(students))
// 特别注意 .extraRead(CellExtraTypeEnum.MERGE)
.extraRead(CellExtraTypeEnum.MERGE).excelType(ExcelTypeEnum.XLSX).headRowNumber(1).sheet(0).doRead();
students.forEach(s ->{
System.out.println(s.toString());
});
return "读取成功!";
}
编写导入Util类:
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.alibaba.excel.metadata.CellExtra;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.List;
/**
* @Description 导入表格程序 使用EasyExcel
* @Author 张凯强
* @Date Created in 2021/9/27
* @E-mail [email protected]
*/
@Slf4j
public class EasyExcelUtils {
// 使用静态内部类 (这里使用泛型为例支持各种数据的导入)
public static class EasyEventListener<T> extends AnalysisEventListener<T> {
private List<T> excels;
// 从第几行读取 默认为空,如果又合并的单元格就去获取EasyExcel 内置ReadSheetHolder 然后获取读取的开始行。
// 没有合并的单元格,则用不上该属性
private Integer headRowNumber = null;
public EasyEventListener(List<T> cityExcels) {
this.excels = cityExcels;
}
// 这个是每行的数据(每一行都会执行这个)
@Override
public void invoke(T data, AnalysisContext context) {
// System.out.println(JSONObject.toJSON(data));
excels.add(data);
}
// 这个是全部读取完成后的回调
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("地址读取完毕!");
}
// 这个是读取异常是的回调
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
System.err.println("解析失败,但是继续解析下一行: " + exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
System.err.println("第{}行,第{}列解析异常" + excelDataConvertException.getRowIndex() +
excelDataConvertException.getColumnIndex());
}
// super.onException(exception, context);
}
// 这个是读取单元格和并时的信息
@SneakyThrows
@Override
public void extra(CellExtra extra, AnalysisContext context) {
// 解析合并单元格的信息 利用反射给合并的单元格读不到值时,进行赋值
if(headRowNumber==null){
headRowNumber = context.readSheetHolder().getHeadRowNumber();
}
// 获取合并后的第一个索引
Integer index = extra.getFirstRowIndex() - headRowNumber;
// 获取合并后的最后一个索引
Integer lastRowIndex = extra.getLastRowIndex() - headRowNumber;
// 获取合并后的第一个值
T t = excels.get(index);
// 利用反射获取所有私有属性
Field[] fields = t.getClass().getDeclaredFields();
for (Field field:fields) {
// 让该属性可操作
field.setAccessible(true);
// 获取EasyExcel 原有注解
ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
// 循环遍历空缺的值
for (int i = index+1; i <= lastRowIndex; i++) {
// 当读取的列索引等于注解的索引时,表示就是该属性赋值
if (extra.getFirstColumnIndex().equals(annotation.index())){
// 利用反射赋值
field.set(excels.get(i),field.get(t));
}
}
}
}
}
}
Swagger2 API :
导入成功:http://localhost:8080/importExcel
Students(grade=java05, name=zkq)
Students(grade=java05, name=yue)
Students(grade=java05, name=xxx)
Students(grade=java06, name=xxx)
Students(grade=java06, name=xxx)
Students(grade=java04, name=xxx)
Students(grade=java04, name=xxx)
Students(grade=java03, name=xxx)
Students(grade=java05, name=xxx)
poi和easyexcel兼容pom
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-baseartifactId>
<version>4.4.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>3.0.5version>
dependency>
@ExcelProperty: 核心注解,value属性可用来设置表头名称,converter属性可以用来设置类型转换器;
@ColumnWidth: 用于设置表格列的宽度;
@DateTimeFormat: 用于设置日期转换格式;
@NumberFormat: 用于设置数字转换格式。