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;
}
}
}