EasyExcel 实现动态表头导入

EasyExcel 实现动态表头导入

需求:

​ 实现数据导入,模板的表头可以有固定列和非固定列(即用户可以自定义表头,前端直接进行数据展示)模板如图所示,自定义列用户可以输入自定义的表头,代码实现中固定表头为姓名、身份证号、手机号。

在这里插入图片描述

依赖导入

导入easyexcel 依赖、和阿里巴巴fastjson

 <!-- excel导出 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>
<!-- fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.26</version>
</dependency>
定义导入实体类

定义一个导入实体类,其中固定表头直接定义属性,非固定表头,定义为json字符串

package com.focus.zxowntest.entity;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

/**
 * @author zx
 * @date 2023/9/7 16:03
 */
@Data
public class Salary {
    /**
     * 姓名
     */
    @ExcelProperty(value = "姓名",index = 0)
    private String name;
    /**
     * 手机号
     */
    @ExcelProperty(value = "手机号",index = 1)
    private String phone;
    /**
     * 身份证号
     */
    @ExcelProperty(value = "身份证号",index = 2)
    private String card;
    /**
     * 自定义数据
     */
    @ExcelIgnore
    private String json;

}
定义监听器
import com.alibaba.excel.context.AnalysisContext;

import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson2.JSONObject;
import com.focus.zxowntest.entity.Salary;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    private List<Salary> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    private List<String> headers = new ArrayList<>();
    // 存储自定义表头
    Map<Integer, String> customHeaders = new HashMap<>();

    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {

        for (int i = 3; i < headMap.size(); i++) {
            String header = headMap.get(i).getStringValue();
            if(!"null".equals(header) && !isFixedHeader(header) ){
                customHeaders.put(i,header);
            }
        }
        // 如果想转成成 Map<Integer,String>
        // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener

        // 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换
    }

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        // 处理读取到的数据逻辑,根据 ExcelRow 中的数据构建对象
        // 根据 ExcelRow 中的表头信息构建对应的对象,可以根据实际情况进行修改
        // 这里仅作示例,只输出每个人的姓名和身份证号以及其他自定义的属性
        Salary salary = new Salary();
        StringBuilder sb = new StringBuilder();
        salary.setName(data.get(0));
        salary.setPhone(data.get(1));
        salary.setCard(data.get(2));
        JSONObject jsonObject = new JSONObject();
        for (int i = 3; i <= data.size()-1; i++) {
            jsonObject.put(customHeaders.get(i),data.get(i));
        }
        salary.setJson(jsonObject.toJSONString());

        //将读取到的数据加入缓存集合
        cachedDataList.add(salary);

        //如果缓存集合数据大于等于设定值,存储数据库
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            //清空集合
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }

    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        log.info("所有数据解析完成!");
    }
    //判断是否是固定表头
    private boolean isFixedHeader(String header) {
        //定义固定表头集合
        List<String> list = Arrays.asList("姓名","手机号","身份证号");
        return list.contains(header);
    }
    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
    }


    /**
     * 加上存储数据库
     */
    private void saveData() {
        List<Salary> newList = cachedDataList.stream().collect(Collectors.collectingAndThen(
                Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Salary::getCard))), ArrayList::new));
        if(newList.size()<cachedDataList.size()){
            log.error("表中存在{}条重复数据,请检查数据后重新导入!",cachedDataList.size()-newList.size());
        }
        log.info("{}条数据,开始存储数据库!", newList.size());
        log.info("存储数据库成功!");
    }
}
数据文件解析导入
/**
 * 不创建对象的读
 */
@Test
public void noModelRead() {
    String fileName = "D:\\doc\\zxowntest\\src\\main\\resources\\模板数据测试.xlsx";
    // 这里 只要,然后读取第一个sheet 同步读取会自动finish
    EasyExcel.read(fileName, new NoModelDataListener())
            .sheet()//默认读取第一个sheet
            .headRowNumber(2)//定义表头行
            .doReadSync();
}
  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用easyexcel进行表格导入时,如果需要动态地插入列头和数据,可以使用以下步骤: 1. 定义表头 首先需要定义表头,包括固定列和动态列。对于动态列,可以使用占位符“#{columnIndex}”来表示其所在列的位置,例如: ```java List<List<String>> head = new ArrayList<List<String>>(); List<String> fixedHeaders = Arrays.asList("姓名", "年龄", "性别"); List<String> dynamicHeaders = Arrays.asList("科目1", "科目2", "科目3"); head.add(fixedHeaders); for (int i = 0; i < dynamicHeaders.size(); i++) { head.add(Arrays.asList("成绩#{i}")); } ``` 2. 定义数据处理器 接下来需要定义一个数据处理器,用于将动态列的数据写入对应的列。在数据处理器中,可以使用“columnIndex”参数来获取当前列的位置,从而确定需要写入数据的列。例如: ```java public class MyDataListener extends AnalysisEventListener<Map<Integer, String>> { // ... @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { // ... } @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { // 获取当前行的数据 String name = data.get(0); String age = data.get(1); String gender = data.get(2); // 遍历动态列,将数据写入对应的列 for (int i = 0; i < dynamicHeaders.size(); i++) { String score = data.get(i + 3); int columnIndex = fixedHeaders.size() + i; writeCell(columnIndex, Arrays.asList(name, age, gender, score)); } } // ... } ``` 其中,“writeCell”方法用于将数据写入指定的单元格。 3. 执行导入操作 最后执行导入操作,例如: ```java File file = new File("data.xlsx"); ExcelReader excelReader = EasyExcel.read(file, new MyDataListener()).build(); excelReader.read(); ``` 在导入过程中,数据处理器会根据表头中的占位符动态地插入列头,同时根据实际数据动态地写入数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值