Administrator
发布于 2025-05-29 / 4 阅读
0

导入

package com.ininin.quote.common.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.ininin.quote.common.domain.BaseImportDTO;
import com.ininin.quote.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.poi.EncryptedDocumentException;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;


/**
 * Excel工具类
 *
 */
@Slf4j
public abstract class ExcelUtils {

    public static final int MAX_ROW = 1000;

    public static final int START_ROW = 1;

    /**
     * 获取导入excel的数据list,默认从第一行开始读取
     *
     * @param clazz   接收excel数据的对象
     * @param fileUrl excel文件url
     * @param <T>     接收数据泛型
     * @return 解析后的数据list
     */
    public static <T extends BaseImportDTO> List<T> getData(Class<T> clazz, String fileUrl) {
        return getData(clazz, fileUrl, START_ROW, MAX_ROW, null, null);
    }

    /**
     * 获取导入excel的数据list
     *
     * @param clazz    接收excel数据的对象
     * @param fileUrl  excel文件url
     * @param startRow 开始读取行数
     * @param <T>      接收数据泛型
     * @return 解析后的数据list
     */
    public static <T extends BaseImportDTO> List<T> getData(Class<T> clazz, String fileUrl, int startRow) {
        return getData(clazz, fileUrl, startRow, MAX_ROW, null, null);
    }

    /**
     * 获取导入excel的数据list
     *
     * @param clazz    接收excel数据的对象
     * @param fileUrl  excel文件url
     * @param startRow 开始读取行数
     * @param max      最大写入行数
     * @param <T>      接收数据泛型
     * @return 解析后的数据list
     */
    public static <T extends BaseImportDTO> List<T> getData(Class<T> clazz, String fileUrl, int startRow, int max) {
        return getData(clazz, fileUrl, startRow, max, null, null);
    }

    /**
     * 获取导入excel的数据list
     *
     * @param clazz    接收数据对象
     * @param fileUrl  文件路径, 网络地址路径需要自己带上http,本地路径不带http
     * @param startRow 从第几行开始读取数据
     * @param sheet    指定sheet名字读取数据,null为默认读取第一个sheet
     * @param max      指定一次性最大读取数据量,防止内存溢出
     * @return 读取出来的数据集合
     */
    public static <T extends BaseImportDTO> List<T> getData(Class<T> clazz, String fileUrl, int startRow, int max, String sheet) {
        return getData(clazz, fileUrl, startRow, max, sheet, null);
    }

    /**
     * 获取导入excel的数据list
     *
     * @param clazz    接收数据对象
     * @param fileUrl  文件路径, 网络地址路径需要自己带上http,本地路径不带http
     * @param sheetMax 支持最大sheet数
     * @param listener 监听器
     * @return 读取出来的数据集合
     */
    public static <T> List<T> getMultipleSheetData(Class<T> clazz, String fileUrl, int sheetMax, EasyExcelListener<T> listener) {
        return getMultipleSheetData(clazz, fileUrl, sheetMax, listener, null);
    }

    /**
     * 获取导入excel的数据list
     *
     * @param clazz       接收数据对象
     * @param fileUrl     文件路径, 网络地址路径需要自己带上http,本地路径不带http
     * @param startRow    从第几行开始读取数据
     * @param sheet       指定sheet名字读取数据,null为默认读取第一个sheet
     * @param max         指定一次性最大读取数据量,防止内存溢出
     * @param mobilePhone 手机号码
     * @return 读取出来的数据集合
     */
    public static <T extends BaseImportDTO> List<T> getData(Class<T> clazz, String fileUrl, int startRow, int max, String sheet, String mobilePhone) {
        log.info(fileUrl);

        // 因为EasyExcel会自动关闭输入流,所以此处需要获得两次
        try (InputStream in = FileUtils.getInputStreamOfUrl(fileUrl);
             InputStream in2 = FileUtils.getInputStreamOfUrl(fileUrl)) {
            AtomicInteger serialNum = new AtomicInteger(0);
            EasyExcelListener<T> listener = new EasyExcelListener<>((t, analysisContext) -> {
                t.setSerialNum(serialNum.incrementAndGet());
                if (analysisContext.readSheetHolder().getRowIndex() > max) {
                    throw new BusinessException(String.format("一次性导入数据最多%d条", max));
                }
            });

            if (!StringUtils.isEmpty(mobilePhone) && hasPassword(clazz, sheet, in, listener)) {
                EasyExcel.read(in2, clazz, listener).password(defaultPassword(mobilePhone)).sheet(sheet).headRowNumber(startRow).doReadSync();
            } else {
                EasyExcel.read(in2, clazz, listener).sheet(sheet).headRowNumber(startRow).doReadSync();
            }

            return listener.getList();
        } catch (Exception e) {
            Throwable throwable = ExceptionUtils.getRootCause(e);
            if (throwable instanceof BusinessException) {
                throw (BusinessException) throwable;
            }
            if (throwable instanceof EncryptedDocumentException) {
                throw new BusinessException("导入模板已加密,请使用未加密的模板");
            }
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取导入excel的数据list
     *
     * @param clazz       接收数据对象
     * @param inputStream 文件流
     * @param startRow    从第几行开始读取数据
     * @param sheet       指定sheet名字读取数据,null为默认读取第一个sheet
     * @param max         指定一次性最大读取数据量,防止内存溢出
     * @return 读取出来的数据集合
     */
    public static <T extends BaseImportDTO> List<T> getData(Class<T> clazz, InputStream inputStream, int startRow, int max, String sheet) {
        try {
            AtomicInteger serialNum = new AtomicInteger(0);
            EasyExcelListener<T> listener = new EasyExcelListener<>((t, analysisContext) -> {
                t.setSerialNum(serialNum.incrementAndGet());
                if (analysisContext.readSheetHolder().getRowIndex() > max) {
                    throw new BusinessException(String.format("一次性导入数据最多%d条", max));
                }
            });

            EasyExcel.read(inputStream, clazz, listener).sheet(sheet).headRowNumber(startRow).doReadSync();
            return listener.getList();
        } catch (Exception e) {
            log.error("Excel解析异常:", e);
            throw new BusinessException("Excel解析异常");
        }
    }

    /**
     * 获取导入excel的数据list(获取全部sheet页)
     *
     * @param clazz         接收数据对象
     * @param fileUrl       文件路径, 网络地址路径需要自己带上http,本地路径不带http
     * @param sheetMax      支持最大sheet数
     * @param listener      监听器
     * @param readSheetList 需要读取的sheet页
     * @return 读取出来的数据集合
     */
    private static <T> List<T> getMultipleSheetData(Class<T> clazz, String fileUrl, int sheetMax, EasyExcelListener<T> listener, List<ReadSheet> readSheetList) {
        log.info("批量导入数据解析入参 --> fileUrl:{} startRow:{} sheetMax:{}", fileUrl, ExcelUtils.START_ROW, sheetMax);
        try (InputStream inputStream = FileUtils.getInputStreamOfUrl(fileUrl)) {
            ExcelReader excelReader = EasyExcel.read(inputStream, clazz, listener).headRowNumber(ExcelUtils.START_ROW).build();
            List<ReadSheet> sheetList = excelReader.excelExecutor().sheetList();
            if (CollectionUtils.isEmpty(sheetList)) {
                return Collections.emptyList();
            }
            if (sheetList.size() > sheetMax) {
                throw new BusinessException(String.format("sheet不能超过%s个", sheetMax));
            }
            if (CollectionUtils.isEmpty(readSheetList)) {
                excelReader.readAll();
            } else {
                excelReader.read(readSheetList);
            }
            excelReader.finish();
            return listener.getList();
        } catch (BusinessException e) {
            throw e;
        } catch (Throwable e) {
            log.error("Excel解析异常:", e);
            throw new BusinessException("Excel解析异常");
        }
    }

    /**
     * 判断Excel文件是否加密
     */
    private static <T extends BaseImportDTO> boolean hasPassword(Class<T> clazz, String sheet, InputStream inputStream, EasyExcelListener<T> listener) {
        try {
            EasyExcel.read(inputStream, clazz, listener).sheet(sheet);
            return false;
        } catch (EncryptedDocumentException e) {
            log.warn("Excel文件存在加密", e);
            return true;
        }
    }

    /**
     * Excel加密默认密码
     *
     * @param mobilePhone 电话号码
     * @return 默认密码
     */
    public static String defaultPassword(String mobilePhone) {
        if (StringUtils.isEmpty(mobilePhone)) {
            return "";
        }
        return DateUtils.format(new Date(), "yyyyMMdd")
                + mobilePhone.substring(mobilePhone.length() - 4);
    }

    /**
     * excel监听器
     *
     * @param <T> 返回数据类型
     */
    public static class EasyExcelListener<T> extends AnalysisEventListener<T> {

        /**
         * 最终返回参数集合
         */
        private final List<T> list = new ArrayList<>();

        private final BiConsumer<T, AnalysisContext> consumer;

        public EasyExcelListener(BiConsumer<T, AnalysisContext> consumer) {
            this.consumer = consumer;
        }

        /**
         * 拦截器调用方法
         *
         * @param t               每行对应的参数对象
         * @param analysisContext excel解析上下文
         */
        @Override
        public void invoke(T t, AnalysisContext analysisContext) {
            // 参数处理
            consumer.accept(t, analysisContext);
            list.add(t);
        }

        /**
         * 解析完成之后对应监听处理
         *
         * @param analysisContext excel解析上下文
         */
        @Override
        public void doAfterAllAnalysed(AnalysisContext analysisContext) {

        }

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