com.iceolive.util.ExcelUtil Maven / Gradle / Ivy
The newest version!
package com.iceolive.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iceolive.util.annotation.ExcelColumn;
import com.iceolive.util.constants.ValidationConsts;
import com.iceolive.util.enums.ColumnType;
import com.iceolive.util.enums.RuleType;
import com.iceolive.util.exception.ImageOutOfBoundsException;
import com.iceolive.util.model.*;
import com.iceolive.xpathmapper.XPathMapper;
import com.monitorjbl.xlsx.StreamingReader;
import com.networknt.schema.JsonSchema;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SpecVersionDetector;
import com.networknt.schema.ValidationMessage;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.*;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.sql.Time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author wangmianzhe
*/
@SuppressWarnings("unchecked")
public class ExcelUtil {
private static Validator validator = null;
private static Validator getValidatorInstance() {
if (validator == null) {
createValidatorInstance();
}
return validator;
}
private static synchronized Validator createValidatorInstance() {
if (validator == null) {
validator = Validation.buildDefaultValidatorFactory()
.getValidator();
}
return validator;
}
/**
* 导入excel
*
* @param filepath excel文件路径
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param
* @return
*/
public static ImportResult importExcel(
String filepath, Class clazz,
boolean faultTolerant) {
return importExcel(filepath, clazz, faultTolerant, 1, null, null);
}
/**
* 导入excel
*
* @param filepath excel文件路径
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param startRow 开始行数,从1开始,当第一行是标题,则传1,当第二行是标题则传2。
* @param
* @return
*/
public static ImportResult importExcel(
String filepath, Class clazz,
boolean faultTolerant, int startRow) {
return importExcel(filepath, clazz, faultTolerant, startRow, null, null);
}
/**
* 导入excel
*
* @param filepath excel文件路径
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param
* @return
*/
public static ImportResult importExcel(
String filepath, Class clazz,
boolean faultTolerant,
Function importFunc) {
return importExcel(filepath, clazz, faultTolerant, 1, null, importFunc);
}
/**
* 导入excel
*
* @param filepath excel文件路径
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param startRow 开始行数,从1开始,当第一行是标题,则传1,当第二行是标题则传2。
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param
* @return
*/
public static ImportResult importExcel(
String filepath, Class clazz,
boolean faultTolerant, int startRow,
Function importFunc) {
return importExcel(filepath, clazz, faultTolerant, startRow, null, importFunc);
}
/**
* 导入excel
*
* @param filepath excel文件路径
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param customValidateFunc {@code 自定义验证的方法,一般简单验证写在字段注解中,这里处理复杂验证,如身份证格式等,不需要请传null。如果验证错误,则返回List,由于一行数据可能有多个错误,所以用List。如果验证通过返回null或空list即可}
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param
* @return
*/
public static ImportResult importExcel(
String filepath, Class clazz,
boolean faultTolerant,
Function> customValidateFunc,
Function importFunc) {
return importExcel(filepath, clazz, faultTolerant, 1, customValidateFunc, importFunc);
}
/**
* 导入excel
*
* @param filepath excel文件路径
* @param clazz 中间类类型
* @param startRow 开始行数,从1开始,当第一行是标题,则传1,当第二行是标题则传2。
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param customValidateFunc {@code 自定义验证的方法,一般简单验证写在字段注解中,这里处理复杂验证,如身份证格式等,不需要请传null。如果验证错误,则返回List,由于一行数据可能有多个错误,所以用List。如果验证通过返回null或空list即可}
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param 中间类
* @return 返回导入结果
*/
public static ImportResult importExcel(
String filepath, Class clazz,
boolean faultTolerant,
int startRow,
Function> customValidateFunc,
Function importFunc) {
FileInputStream inputStream;
try {
inputStream = new FileInputStream(filepath);
} catch (Exception e) {
throw new RuntimeException(e);
}
return importExcel(inputStream, clazz, faultTolerant, startRow, customValidateFunc, importFunc);
}
/**
* 导入excel
*
* @param inputStream excel文件的字节数组
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param
* @return
*/
public static ImportResult importExcel(
InputStream inputStream, Class clazz,
boolean faultTolerant) {
return importExcel(inputStream, clazz, faultTolerant, 0, null, null);
}
/**
* 导入excel
*
* @param inputStream excel文件的字节数组
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param startRow 开始行数,从1开始,当第一行是标题,则传1,当第二行是标题则传2。
* @param
* @return
*/
public static ImportResult importExcel(
InputStream inputStream, Class clazz,
boolean faultTolerant, int startRow) {
return importExcel(inputStream, clazz, faultTolerant, startRow, null, null);
}
/**
* 导入excel
*
* @param inputStream excel文件的字节数组
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param
* @return
*/
public static ImportResult importExcel(
InputStream inputStream, Class clazz,
boolean faultTolerant,
Function importFunc) {
return importExcel(inputStream, clazz, faultTolerant, 0, null, importFunc);
}
/**
* 导入excel
*
* @param inputStream excel文件的字节数组
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param startRow 开始行数,从1开始,当第一行是标题,则传1,当第二行是标题则传2。
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param
* @return
*/
public static ImportResult importExcel(
InputStream inputStream, Class clazz,
boolean faultTolerant, int startRow,
Function importFunc) {
return importExcel(inputStream, clazz, faultTolerant, startRow, null, importFunc);
}
/**
* 导入excel
*
* @param inputStream excel文件的字节数组
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param customValidateFunc {@code 自定义验证的方法,一般简单验证写在字段注解中,这里处理复杂验证,如身份证格式等,不需要请传null。如果验证错误,则返回List,由于一行数据可能有多个错误,所以用List。如果验证通过返回null或空list即可}
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param
* @return
*/
public static ImportResult importExcel(
InputStream inputStream, Class clazz,
boolean faultTolerant,
Function> customValidateFunc,
Function importFunc) {
return importExcel(inputStream, clazz, faultTolerant, 0, customValidateFunc, importFunc);
}
/**
* 导入excel
*
* @param inputStream excel文件的字节数组
* @param clazz 中间类类型
* @param faultTolerant 是否容错,验证是所有数据先验证后在一条条导入。true表示不需要全部数据都符合验证,false则表示必须全部数据符合验证才执行导入。
* @param startRow 开始行数,从1开始,当第一行是标题,则传1,当第二行是标题则传2。
* @param customValidateFunc {@code 自定义验证的方法,一般简单验证写在字段注解中,这里处理复杂验证,如身份证格式等,不需要请传null。如果验证错误,则返回List,由于一行数据可能有多个错误,所以用List。如果验证通过返回null或空list即可}
* @param importFunc 一条条入库的方法,只有验证通过的数据才会进入此方法。如果你是批量入库,请自行获取结果的成功列表,此参数传null。返回true表示入库成功,入库失败提示请抛一个带message的Exception。
* @param 中间类
* @return 返回导入结果
*/
public static ImportResult importExcel(
InputStream inputStream, Class clazz,
boolean faultTolerant,
int startRow,
Function> customValidateFunc,
Function importFunc) {
ImportResult result = new ImportResult();
result.setErrors(new ArrayList<>());
Workbook workbook = null;
try {
if (hasCellImageField(clazz)) {
//如果有图片字段,则不使用StreamingWorkbook
workbook = new XSSFWorkbook(inputStream);
} else {
workbook = StreamingReader.builder()
//缓存到内存中的行数,默认是10
.rowCacheSize(100)
//读取资源时,缓存到内存的字节大小,默认是1024
.bufferSize(4096)
//打开资源,必须,可以是InputStream或者是File,注意:只能打开XLSX格式的文件
.open(inputStream);
}
} catch (Exception e1) {
try {
workbook = new HSSFWorkbook(inputStream);
} catch (Exception e2) {
throw new RuntimeException(e2);
}
}
Sheet sheet = workbook.getSheetAt(0);
//列序号和字段的map
Map> headMap = null;
Map list = new LinkedHashMap<>();
int totalCount = 0;
int titleRowNum = startRow - 1;
for (Row row : sheet) {
if (row.getRowNum() < titleRowNum) {
//小于标题行的抛弃
} else if (row.getRowNum() == titleRowNum) {
headMap = getHeadMap(clazz, row);
} else {
totalCount++;
if (null != row) {
T obj = null;
try {
obj = clazz.newInstance();
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
boolean validate = true;
for (Integer c : headMap.keySet()) {
Cell cell = row.getCell(c);
List fields = headMap.get(c);
//是否日期单元格
boolean isDateCell = SheetUtil.isDateCell(cell);
String dateFormat = "yyyy-MM-dd HH:mm:ss";
try {
if (null != cell) {
String str = SheetUtil.getCellStringValue(cell);
for (Field field : fields) {
Object value = null;
if (isDateCell && (field.getType().isAssignableFrom(Date.class) || field.getType().isAssignableFrom(LocalDateTime.class) || field.getType().isAssignableFrom(LocalDate.class))) {
//特殊处理日期格式
if (!StringUtil.isBlank(str)) {
value = StringUtil.parse(str, dateFormat, field.getType());
}
} else if (isDateCell && (field.getType().isAssignableFrom(LocalTime.class) || field.getType().isAssignableFrom(Time.class))) {
//特殊处理日期格式
if (!StringUtil.isBlank(str)) {
value = StringUtil.parse(str, dateFormat, field.getType());
}
} else if (field.getType().isAssignableFrom(boolean.class) || field.getType().isAssignableFrom(Boolean.class)) {
ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
value = StringUtil.parseBoolean(str, excelColumn.trueString(), excelColumn.falseString(), field.getType());
} else if (field.getType().isArray() && field.getType().getComponentType().equals(byte.class)) {
value = SheetUtil.getCellImageBytes((XSSFWorkbook) workbook, cell);
} else if (field.getType().isAssignableFrom(BufferedImage.class)) {
value = ImageUtil.Bytes2Image(SheetUtil.getCellImageBytes((XSSFWorkbook) workbook, cell));
} else {
value = StringUtil.parse(str, field.getType());
}
field.setAccessible(true);
field.set(obj, value);
}
} else {
//单元格为null,处理图片
for (Field field : fields) {
Object value = null;
if (field.getType().isArray() && field.getType().getComponentType().equals(byte.class)) {
List floatImages = getFloatImagesBytes(sheet, row.getRowNum(), c);
if (!CollectionUtils.isEmpty(floatImages)) {
value = floatImages.get(0);
}
} else if (field.getType().isAssignableFrom(BufferedImage.class)) {
List floatImages = getFloatImagesBytes(sheet, row.getRowNum(), c);
if (!CollectionUtils.isEmpty(floatImages)) {
value = ImageUtil.Bytes2Image(floatImages.get(0));
}
} else if (field.getType().isAssignableFrom(List.class)) {
ParameterizedType genericType = (ParameterizedType) field.getGenericType();
if (genericType.getActualTypeArguments()[0] == BufferedImage.class) {
List floatImages = getFloatImagesBytes(sheet, row.getRowNum(), c);
value = new ArrayList<>();
for (byte[] floatImage : floatImages) {
((List) value).add(ImageUtil.Bytes2Image(floatImage));
}
} else if (genericType.getActualTypeArguments()[0] == byte[].class) {
List floatImages = getFloatImagesBytes(sheet, row.getRowNum(), c);
value = floatImages;
}
}
field.setAccessible(true);
field.set(obj, value);
}
}
} catch (Exception e) {
validate = false;
ImportResult.ErrorMessage errorMessage = new ImportResult.ErrorMessage();
errorMessage.setRow(row.getRowNum());
errorMessage.setCol(CellReference.convertNumToColString(c));
errorMessage.setCell(new CellAddress(row.getRowNum(), c).formatAsString());
if (e instanceof ImageOutOfBoundsException) {
errorMessage.setMessage(e.getMessage());
} else {
errorMessage.setMessage("类型转换错误");
}
result.getErrors().add(errorMessage);
}
}
List validateResults = validate(obj);
validate = isValidate(result, headMap, row, validate, validateResults, clazz);
if (customValidateFunc != null) {
List customValidateResults = customValidateFunc.apply(obj);
validate = isValidate(result, headMap, row, validate, customValidateResults, clazz);
}
if (validate) {
list.put(row.getRowNum(), obj);
}
}
}
}
//设置总记录数
result.setTotalCount(totalCount);
if (list.size() > 0) {
if (faultTolerant || result.getErrors().size() == 0) {
//如果容错模式或是验证全部通过
if (importFunc != null) {
//如果有导入函数
for (Map.Entry m : list.entrySet()) {
try {
if (Boolean.TRUE.equals(importFunc.apply(m.getValue()))) {
result.getSuccesses().put(m.getKey(), m.getValue());
} else {
ImportResult.ErrorMessage errorMessage = new ImportResult.ErrorMessage();
errorMessage.setRow(m.getKey());
errorMessage.setMessage("未抛异常的错误");
result.getErrors().add(errorMessage);
//非容错模式,退出循环
if (!faultTolerant) {
break;
}
}
} catch (Exception e) {
ImportResult.ErrorMessage errorMessage = new ImportResult.ErrorMessage();
errorMessage.setRow(m.getKey());
errorMessage.setMessage(e.getMessage());
result.getErrors().add(errorMessage);
//非容错模式,退出循环
if (!faultTolerant) {
break;
}
}
}
} else {
//没有导入函数
result.setSuccesses(list);
}
}
}
return result;
}
/**
* 获取列序号和字段的对应关系
*
* @param clazz
* @param row 标题行
* @param
* @return
*/
private static Map> getHeadMap(Class clazz, Row row) {
//列序号和字段的map
Map> headMap = new HashMap<>();
for (int c = 0; c < row.getLastCellNum(); c++) {
Cell cell = row.getCell(c);
if (null != cell) {
String title = cell.getStringCellValue();
for (Field field : clazz.getDeclaredFields()) {
ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
if (excelColumn != null) {
if (StringUtil.isNotEmpty(excelColumn.value())) {
if (excelColumn.value().equals(title)) {
if (headMap.containsKey(c)) {
headMap.get(c).add(field);
} else {
List fieldList = new ArrayList<>();
fieldList.add(field);
headMap.put(c, fieldList);
}
}
} else {
//如果ExcelColumn不指定名称,则使用字段名匹配
if (field.getName().equals(title)) {
if (headMap.containsKey(c)) {
headMap.get(c).add(field);
} else {
List fieldList = new ArrayList<>();
fieldList.add(field);
headMap.put(c, fieldList);
}
}
}
}
}
}
}
return headMap;
}
/**
* 根据验证结果填充错误信息
*
* @param result
* @param headMap
* @param row
* @param validate
* @param validateResults
* @return
*/
private static boolean isValidate(ImportResult result, Map> headMap, Row row, boolean validate, List validateResults, Class> clazz) {
if (validateResults != null && !validateResults.isEmpty()) {
validate = false;
for (ValidateResult v : validateResults) {
//错误是否在单元格内
boolean errorInCell = false;
for (Map.Entry> m : headMap.entrySet()) {
List fields = m.getValue();
boolean stop = false;
for (Field field : fields) {
if (field.getName().equals(v.getFieldName())) {
ImportResult.ErrorMessage errorMessage = new ImportResult.ErrorMessage();
errorMessage.setRow(row.getRowNum());
errorMessage.setCol(CellReference.convertNumToColString(m.getKey()));
errorMessage.setCell(new CellAddress(row.getRowNum(), m.getKey()).toString());
errorMessage.setMessage(v.getMessage());
result.getErrors().add(errorMessage);
stop = true;
errorInCell = true;
break;
}
}
if (stop) {
break;
}
}
if (!errorInCell) {
String fieldName = v.getFieldName();
String columnName = fieldName;
Field field = Arrays.stream(clazz.getDeclaredFields()).filter(m -> m.getName().equals(fieldName)).findFirst().orElse(null);
if (field != null) {
String title = field.getAnnotation(ExcelColumn.class).value();
if (StringUtil.isNotEmpty(title)) {
columnName = title;
}
}
//如果错误不在单元格内,不
ImportResult.ErrorMessage errorMessage = new ImportResult.ErrorMessage();
errorMessage.setRow(row.getRowNum());
errorMessage.setMessage(v.getMessage() + "\n请检查[" + columnName + "]列是否存在");
result.getErrors().add(errorMessage);
}
}
}
return validate;
}
/**
* 根据注解验证对象
*
* @param obj 验证的对象
* @return 返回验证列表
*/
public static List validate(@Valid Object obj) {
List result = new ArrayList<>();
Set> validateSet = getValidatorInstance()
.validate(obj, new Class[0]);
if (!CollectionUtils.isEmpty(validateSet)) {
validateSet.stream().forEach((v) -> {
String msg = v.getMessage();
if (StringUtil.isEmpty(msg)) {
msg = "参数输入有误";
}
result.add(new ValidateResult(v.getPropertyPath().toString(), msg));
});
}
return result;
}
/**
* json-schema验证
*
* @param schemaJson
* @param obj
* @return
*/
public static List jsonSchemaValidate(String schemaJson, Object obj) {
List result = new ArrayList<>();
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode schemaNode = mapper.readTree(schemaJson);
JsonSchema schema = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(schemaNode)).getSchema(schemaNode);
JsonNode node = mapper.readTree(mapper.writeValueAsString(obj));
Set errors = schema.validate(node);
for (ValidationMessage error : errors) {
String msg = error.getMessage();
String fieldName = error.getPath();
int split = msg.indexOf(":");
if (split > -1) {
fieldName = msg.substring(2, split);
msg = msg.substring(split + 1);
}
result.add(new ValidateResult(fieldName, msg));
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
return result;
}
/**
* 简单的excel转list
*
* @param filepath
* @return
*/
public static List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy