• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
职业熬夜选手
博客园    首页    新随笔    联系   管理    订阅  订阅

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<Integer>(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 862166318@qq.com
 */
@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教程
在这里插入图片描述

导出成功:http://localhost:8080/exportExcel
在这里插入图片描述

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 862166318@qq.com
 */
@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 :
在这里插入图片描述
导入成功: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.afterturn</groupId>
    <artifactId>easypoi-base</artifactId>
    <version>4.4.0</version>
</dependency>
 <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.0.5</version>
</dependency>
posted @ 2021-09-29 18:15  职业熬夜选手  阅读(995)  评论(0)    收藏  举报  来源
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3