easyexcel 导出导入合并单元格的表格

easyexcel 导入、导出合并单元格的表格

  1. 现在经常遇到导入导出表格,又有列重复的数据,想要合并,手动有太慢的(所以直接导入或导入和并的表格)

1. 引入pom

  1. 引入pom 参考 java导入Excel(使用阿里巴巴的easyexcel)

2. 导出合并的表格

编写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);
        }
    }
}

3. 导出测试

我用的Swagger2 不会的可看:SpringBoot集成swagger2教程
easyexcel 导出导入合并单元格的表格_第1张图片

导出成功:http://localhost:8080/exportExcel
easyexcel 导出导入合并单元格的表格_第2张图片

4. 导入合并的表格

编写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));
                    }
                }
            }
        }
    }
}

5. 导入测试

Swagger2 API :
easyexcel 导出导入合并单元格的表格_第3张图片
导入成功: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)

easyexcel 导出导入合并单元格的表格_第4张图片
导出结束!

扩展内容

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>

EasyExcel核心注解

@ExcelProperty: 核心注解,value属性可用来设置表头名称,converter属性可以用来设置类型转换器;

@ColumnWidth: 用于设置表格列的宽度;

@DateTimeFormat: 用于设置日期转换格式;

@NumberFormat: 用于设置数字转换格式。

你可能感兴趣的:(Java,教程,记录,spring,java)