使用EasyExcel的AnalysisEventListener读取EXCEL导入数据

本文介绍了一个使用Spring Boot、EasyExcel和Apache POI实现的奖品发货记录导入服务,包括数据校验、接口定义、服务实现和Excel文件处理。重点展示了如何解析Excel,验证快递信息并批量导入数据库。

1、实体对象VO

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

@Data
public class PrizeLogImportExcelVO {
    @ExcelProperty("订单编号")
    private String prizeSn;
    @ExcelProperty("快递公司")
    private String expressName;
    @ExcelProperty("快递单号")
    private String expressSn;
    @ExcelProperty("快递编码")
    private String expressCode;
    @ExcelProperty("快递ID")
    private Integer expressId;
}

 2、接口类

public interface IJmPrizeLogService {
    /**
     * 导入
     *
     * @param list
     */
    void importExcel(List<PrizeLogImportExcelVO> list);
}

3、接口实现类

@Service
public class JmPrizeLogServiceImpl extends ServiceImpl<JmPrizeLogMapper, JmPrizeLog> implements IJmPrizeLogService {
    public static List<String> errorPrizeSns = new ArrayList<>();
    @Resource
    private JmPrizeLogMapper prizeLogMapper;
    @Resource
    private JmExpressMapper expressMapper;
    private static final ExecutorService POOL = Executors.newCachedThreadPool();

    @Override
    public void importExcel(List<PrizeLogImportExcelVO> list) {
        errorPrizeSns.clear();
        if (CollectionUtil.isEmpty(list)) {
            errorPrizeSns.add("excel中无数据,无法导入发货!");
            return;
        }
        List<PrizeLogImportExcelVO> result = new ArrayList<>();
        for (PrizeLogImportExcelVO importExcelVO : list) {
            String prizeSn = importExcelVO.getPrizeSn();
            if (StringUtils.isEmpty(prizeSn)) {
                continue;
            }
            if (StringUtils.isBlank(importExcelVO.getExpressName())) {
                errorPrizeSns.add(prizeSn + " 快递公司不能为空!");
                continue;
            }
            if (StringUtils.isBlank(importExcelVO.getExpressSn())) {
                errorPrizeSns.add(prizeSn + " 快递单号不能为空!");
                continue;
            }
            QueryWrapper<JmExpress> expressQueryWrapper = new QueryWrapper<>();
            expressQueryWrapper.eq("name", importExcelVO.getExpressName());
            JmExpress jmExpress = expressMapper.selectOne(expressQueryWrapper);
            if (!ObjectUtil.isNotEmpty(jmExpress)) {
                errorPrizeSns.add(prizeSn + " 快递公司有误!");
                continue;
            } else {
                importExcelVO.setExpressCode(jmExpress.getCode());
                importExcelVO.setExpressId(jmExpress.getId().intValue());
            }
            result.add(importExcelVO);
        }
        if (CollectionUtil.isEmpty(errorPrizeSns)) {
            POOL.execute(() -> {
                prizeLogMapper.batchExpressInfo(result);
            });
        }
    }
}

4、 Mapper接口

public interface JmPrizeLogMapper extends BaseMapper<JmPrizeLog> {
    /**
     * 批量导入快递信息
     *
     * @param list
     */
    @Update("<script><foreach collection='list' item='o' index='index' separator=';' >UPDATE `jm_prize_log` SET express_name =#{o.expressName}, express_sn = #{o.expressSn}, express_code = #{o.expressCode}, express_id = #{o.expressId} WHERE prize_sn = #{o.prizeSn}</foreach></script>")
    void batchExpressInfo(@Param("list") List<PrizeLogImportExcelVO> list);
}

5、定义一个ExcelHander工具类继承AnalysisEventListener 最重要的是重写invoke方法,去执行读EXCEL逻辑。

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.jumi.microservice.sale.entity.vo.PrizeLogImportExcelVO;
import com.jumi.microservice.sale.service.IJmPrizeLogService;

import java.util.ArrayList;
import java.util.List;


public class PrizeLogImportListener extends AnalysisEventListener<PrizeLogImportExcelVO> {
    private IJmPrizeLogService prizeLogService;
    private List<PrizeLogImportExcelVO> list = new ArrayList<>();

    @Override
    public void invoke(PrizeLogImportExcelVO data, AnalysisContext context) {
        list.add(data);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        prizeLogService.importExcel(list);
    }

    public PrizeLogImportListener(IJmPrizeLogService prizeLogService) {
        this.prizeLogService = prizeLogService;
    }

    public List<PrizeLogImportExcelVO> getList() {
        return list;
    }
}

6、调用逻辑

    @Resource
    private IJmPrizeLogService prizeLogService;
    
//    @PostMapping("/import/excel")
//    @ApiOperation("导入excel")
//    public ResponseResult<List<String>> importExcel(@RequestParam(value = "multipartFile") MultipartFile request) throws IOException {
//        PrizeLogImportListener listener = new PrizeLogImportListener(prizeLogService);
//        EasyExcel.read(request.getInputStream(), PrizeLogImportExcelVO.class, listener).sheet().doRead();
//        return ResponseResult.success(JmPrizeLogServiceImpl.errorPrizeSns);
//    }

    @PostMapping("/import/excel")
    @ApiOperation("导入excel")
    public ResponseResult<List<String>> importExcel(@RequestParam(value = "multipartFile") MultipartFile file) throws IOException {
        if (file == null || file.isEmpty() || ObjectUtils.isEmpty(file.getOriginalFilename())) {
            throw new BaseException(500, "文件不能为空");
        }
        String fileName = file.getOriginalFilename();
        if (!(fileName.endsWith(".xlsx") || fileName.endsWith(".xls") || fileName.endsWith(".csv"))) {
            throw new BaseException(500, "文件类型错误,只支持:xlsx、xls、csv");
        }
        try {
            InputStream inputStream = file.getInputStream();
            if (fileName.endsWith(".csv")) {
                inputStream = CsvToXlsxUtil.csvStream2xlsxStream(file.getInputStream(), fileName);
            }
            PrizeLogImportListener listener = new PrizeLogImportListener(prizeLogService);
            EasyExcel.read(inputStream, PrizeLogImportExcelVO.class, listener).sheet().doRead();
        } catch (ExcelAnalysisException | ExcelCommonException | IOException e) {
            System.out.println(e);
            throw new BaseException(500, "文件异常,请检查确认");
        }
        return ResponseResult.success(JmPrizeLogServiceImpl.errorPrizeSns);
    }

7、CsvToXlsxUtil工具类

import cn.hutool.core.util.ObjectUtil;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jumpmind.symmetric.csv.CsvReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
 
/**
 * Csv 转 Xlsx 工具类
 */
public class CsvToXlsxUtil {
    private static final Logger log = LoggerFactory.getLogger(CsvToXlsxUtil.class);
    /**
     * CSV常用分隔符,如需动态扩展设置成配置项
     */
    private static final char[] DELIMITERS = {
            ',',
            ';',
            '\001',
            ' ',
            '\t',
            '|',
            '#',
            '&'
    };
 
    /**
     * 读取CSV文件并写入到XLSX文件中,默认编码
     *
     * @param csvFileAddress 文件地址
     */
    public static String csvToXlsx(String csvFileAddress) {
        return csvToXlsx(csvFileAddress, "UTF-8");
    }
 
    /**
     * @param inputStream 输入流
     */
    public static InputStream csv2xlsx(InputStream inputStream, String fileName) {
        return csvStream2xlsxStream(inputStream, fileName);
    }
 
    /**
     * 读取CSV文件并写入到XLSX文件中,指定CSV文件编码
     *
     * @param csvFileAddress 文件地址
     * @param charset        编码
     */
    public static String csvToXlsx(String csvFileAddress, String charset) {
        String xlsxFileAddress = "";
        FileOutputStream fileOutputStream = null;
        try {
            char delimiter = getDelimiter(csvFileAddress);
            //xlsx file address
            xlsxFileAddress = csvFileAddress.replace("csv", "xlsx");
            XSSFWorkbook workBook = new XSSFWorkbook();
            XSSFSheet sheet = workBook.createSheet(getSheetName(csvFileAddress));
            int rowNum = -1;
            CsvReader csvReader = new CsvReader(csvFileAddress, delimiter, Charset.forName(charset));
            while (csvReader.readRecord()) {
                rowNum++;
                XSSFRow currentRow = sheet.createRow(rowNum);
                for (int i = 0; i < csvReader.getColumnCount(); i++) {
                    currentRow.createCell(i).setCellValue(csvReader.get(i));
                }
            }
            fileOutputStream = new FileOutputStream(xlsxFileAddress);
            workBook.write(fileOutputStream);
            return getFileName(xlsxFileAddress);
        } catch (Exception e) {
            log.error("CsvToXlsxUtil exception :", e);
        } finally {
            try {
                assert fileOutputStream != null;
                fileOutputStream.close();
            } catch (IOException e) {
                log.error("CsvToXlsxUtil close FileOutputStream exception :", e);
            }
        }
        return getFileName(xlsxFileAddress);
    }
 
    /**
     * @param inputStream 输入流
     */
    public static InputStream csvStream2xlsxStream(InputStream inputStream, String fileName) {
        FileOutputStream fileOutputStream = null;
        try {
            fileName = fileName.replace(".csv", ".xlsx");
            XSSFWorkbook workBook = new XSSFWorkbook();
            XSSFSheet sheet = workBook.createSheet("sheet1");
            int rowNum = -1;
            CsvReader csvReader = new CsvReader(inputStream, StandardCharsets.UTF_8);
            while (csvReader.readRecord()) {
                rowNum++;
                XSSFRow currentRow = sheet.createRow(rowNum);
                for (int i = 0; i < csvReader.getColumnCount(); i++) {
                    currentRow.createCell(i).setCellValue(csvReader.get(i));
                }
            }
            File file = new File("/" + fileName);
            fileOutputStream = new FileOutputStream(file);
            workBook.write(fileOutputStream);
            InputStream input = new FileInputStream(file);
            file.delete();
            return input;
        } catch (Exception e) {
            log.error("CsvToXlsxUtil exception :", e);
        } finally {
            try {
                if (ObjectUtil.isNotNull(fileOutputStream)) {
                    assert fileOutputStream != null;
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                log.error("CsvToXlsxUtil close FileOutputStream exception :", e);
            }
        }
        return null;
    }
 
    /**
     * 设置excel文件的sheet名称
     * 获取CSV文件名作为Excel文件的sheet名称
     *
     * @param path 资源路径
     */
    private static String getSheetName(String path) {
        try {
            String[] file = getFileName(path).split("\\.");
            return file[0];
        } catch (Exception e) {
            log.error("CsvToXlsxUtil get sheet name exception : ", e);
            return "Sheet";
        }
    }
 
    /**
     * 根据资源路径切割获取文件名
     *
     * @param path 资源路径
     */
    private static String getFileName(String path) {
        String[] paths = path.contains("\\") ? path.split("\\\\") : path.split("/");
        return paths[paths.length - 1];
    }
 
    /**
     * 常用CSV分隔符数组遍历资源第一行,分隔的字段数多的为资源分隔符
     * 异常情况下默认用’,‘作为分隔符
     *
     * @param path 资源路径
     */
    private static char getDelimiter(String path) {
        BufferedReader br = null;
        char delimiter = ',';
        try {
            br = new BufferedReader(new FileReader(path));
            String line = br.readLine();
            CsvReader csvReader;
            int columCount = 0;
            for (char delimiterTest : DELIMITERS) {
                csvReader = new CsvReader(getStringStream(line), delimiterTest, StandardCharsets.UTF_8);
                if (csvReader.readRecord()) {
                    int newColumnCount = csvReader.getColumnCount();
                    if (newColumnCount > columCount) {
                        columCount = newColumnCount;
                        delimiter = delimiterTest;
                    }
                }
            }
        } catch (Exception e) {
            log.error("CsvToXlsxUtil get delimiter exception :", e);
        } finally {
            try {
                assert br != null;
                br.close();
            } catch (IOException e) {
                log.error("CsvToXlsxUtil get delimiter close BufferedReader exception :", e);
            }
        }
        return delimiter;
    }
 
    /**
     * 字符串转输入流
     * 把CSV文件第一行数据转成输入流
     *
     * @param sInputString 字符串
     */
    private static InputStream getStringStream(String sInputString) {
        if (null != sInputString && !"".equals(sInputString)) {
            try {
                return new ByteArrayInputStream(sInputString.getBytes());
            } catch (Exception e) {
                log.error("CsvToXlsxUtil get StringStream exception :", e);
            }
        }
        return null;
    }
}
<think>好的,我需要帮助用户解决如何结合使用EasyExcelAnalysisEventListener和CellWriteHandler来实现指定列的数据导出到Excel文件。首先,我需要回忆这两个组件在EasyExcel中的作用。AnalysisEventListener通常用于处理读取Excel时的监听事件,比如逐行解析数据。而CellWriteHandler则是在写入Excel时处理单元格的写入操作,可以自定义单元格的样式、内容等。用户的需求是将指定列的数据导出,可能需要先读取数据,然后处理特定列的写入。接下来,我需要考虑如何将这两个组件结合起来。可能的流程是:使用AnalysisEventListener读取数据时收集需要导出的特定列的数据,然后通过CellWriteHandler在写入Excel时对这些列进行处理,比如设置样式或只写入这些列的数据。需要确认是否正确理解用户的需求。用户是否希望从导入数据中筛选出指定列,然后导出到新的Excel文件中?如果是这样,可能需要先读取数据,过滤出需要的列,再使用CellWriteHandler在导出时处理这些列。然后,查阅EasyExcel的文档或常见用法。AnalysisEventListener读取时可以通过重写invoke方法获取每一行的数据,这里可以收集所需的列数据。而CellWriteHandler可以在写入阶段拦截单元格的创建,例如通过beforeCellCreate方法判断当前列是否为目标列,进行相应的处理。 可能需要分步骤实现:1.读取Excel并收集指定列的数据;2.导出数据到新Excel使用CellWriteHandler控制列的写入。或者,在导出时动态决定哪些列需要写入,这时可能需要结合自定义的CellWriteHandler来跳过不需要的列,或者调整列的顺序。需要注意的是,CellWriteHandler主要用于在写入单元格时进行操作,比如修改样式、值等。如果要动态排除某些列,可能需要结合其他方式,比如在导出时只包含需要的字段,或者在数据对象中忽略不需要的字段。另外,用户可能需要示例代码来展示如何具体实现。需要确保代码正确使用这两个组件,并且语法正确。例如,在读取时注册AnalysisEventListener,在导出时注册CellWriteHandler,并在处理器中判断列索引,只处理目标列。可能遇到的问题包括:如何确定目标列的索引,处理合并单元格的情况,或者在读取时如何保留需要的列数据。需要确保示例代码能够清晰地展示这些步骤,并处理可能的异常情况。最后,生成相关问题,帮助用户深入理解相关技术,比如性能优化、处理复杂结构等。确保回答结构清晰,步骤详细,符合用户的需求。</think>以下是结合使用EasyExcel的`AnalysisEventListener`和`CellWriteHandler`实现指定列数据导出的分步方案: ### 一、实现原理 1. **AnalysisEventListener**:用于数据读取时监听并过滤目标列数据[^1] 2. **CellWriteHandler**:在写入Excel时动态控制单元格的渲染逻辑[^2] 3. **组合逻辑**:读取时收集目标列数据 -> 导出时通过处理器筛选指定列 ### 二、完整实现代码 ```java // 1. 定义数据实体类 @Data public class ExportData { @ExcelProperty("姓名") private String name; @ExcelProperty("年龄") private Integer age; @ExcelProperty("地址") private String address; } // 2. 自定义读取监听器(过滤目标列) public class ColumnFilterListener extends AnalysisEventListener<ExportData> { private List<ExportData> filteredData = new ArrayList<>(); private Set<Integer> targetColumns = new HashSet<>(Arrays.asList(0, 2)); // 指定导出列索引 @Override public void invoke(ExportData data, AnalysisContext context) { // 模拟列过滤逻辑 ExportData filtered = new ExportData(); if(targetColumns.contains(0)) filtered.setName(data.getName()); if(targetColumns.contains(2)) filtered.setAddress(data.getAddress()); filteredData.add(filtered); } @Override public void doAfterAllAnalysed(AnalysisContext context) { // 触发导出操作 writeFilteredData(); } private void writeFilteredData() { String fileName = "filtered_data_" + System.currentTimeMillis() + ".xlsx"; EasyExcel.write(fileName, ExportData.class) .registerWriteHandler(new ColumnRenderHandler()) .sheet("筛选数据") .doWrite(filteredData); } } // 3. 自定义单元格渲染处理器 public class ColumnRenderHandler implements CellWriteHandler { @Override public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) { // 隐藏非目标列(0和2列可见) if(!isHead && columnIndex != 0 && columnIndex != 2) { row.getSheet().setColumnHidden(columnIndex, true); } } } // 4. 使用示例 public class ExcelProcessor { public static void main(String[] args) { String sourceFile = "source_data.xlsx"; EasyExcel.read(sourceFile, ExportData.class, new ColumnFilterListener()) .sheet() .doRead(); } } ``` ### 三、关键配置说明 1. **列索引控制**:通过`targetColumns`集合定义需要导出的列索引(0-based) 2. **动态列隐藏**:在`ColumnRenderHandler`中设置`setColumnHidden`实现列过滤 3. **数据完整性**:读取阶段保留原始数据,导出阶段进行列筛选 ### 四、效果说明 1. 最终生成的Excel文件仅包含第0列(姓名)和第2列(地址) 2. 隐藏列在Excel中不可见但实际数据仍然存在 3. 支持动态调整目标列索引集合
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值