Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cn.idev.excel.test.core.validate;

import cn.idev.excel.annotation.ExcelProperty;
import lombok.Data;

import java.math.BigDecimal;

/**
* @author wangmeng
* @since 2025/3/22
*/
@Data
public class ValidateDemoData {

@ExcelProperty(value = "订单号", notNull = true)
private String orderNo;
@ExcelProperty(value = "用户名", notNull = true)
private String username;
@ExcelProperty(value = "金额")
private BigDecimal amount;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package cn.idev.excel.test.core.validate;

import cn.idev.excel.FastExcel;
import cn.idev.excel.exception.ExcelDataConvertException;
import cn.idev.excel.read.builder.ExcelReaderBuilder;
import cn.idev.excel.read.listener.PageReadListener;
import cn.idev.excel.read.metadata.holder.ValidateErrorHolder;
import cn.idev.excel.read.processor.FileErrorHandler;
import cn.idev.excel.test.util.TestFileUtil;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.util.List;

/**
* Test cases for reading validation
*
* @author wangmeng
*/
@Slf4j
public class ValidateTest {


@Test
public void test() {
// 不存在转换失败情况
String fileName = TestFileUtil.getPath() + "validate" + File.separator + "checkRead" + ".xlsx";
testCheckRead(fileName);
testCheckOutFile(fileName);
existConvertException(fileName);
// 存在转换失败
String fileName2 = TestFileUtil.getPath() + "validate" + File.separator + "checkRead2" + ".xlsx";
existConvertException(fileName2);
existConvertException(fileName);
// 手动添加业务校验
testAddError(fileName);
testAddError(fileName2);
}


public void testCheckRead(String fileName) {
ExcelReaderBuilder readerBuilder = FastExcel.read(fileName).head(ValidateDemoData.class);
List<ValidateDemoData> list = readerBuilder.registerReadListener(new PageReadListener<>(l -> {
System.out.println("读取内容:" + JSON.toJSONString(l));
})).validate().sheet().doReadSync();

ValidateErrorHolder errorHolder = readerBuilder.getErrorHolder();
System.out.println(JSON.toJSONString(errorHolder));
String errorText = readerBuilder.handleError();
System.out.println(errorText);
}

public void testCheckOutFile(String fileName) {
ExcelReaderBuilder readerBuilder = FastExcel.read(fileName).head(ValidateDemoData.class);
List<ValidateDemoData> list = readerBuilder.registerReadListener(new PageReadListener<>(l -> {
System.out.println("读取内容:" + JSON.toJSONString(l));
})).validate().setErrorHandler(FileErrorHandler.INSTANCE).sheet().doReadSync();

ValidateErrorHolder errorHolder = readerBuilder.getErrorHolder();
System.out.println(JSON.toJSONString(errorHolder));
File errorFile = readerBuilder.handleError();
System.out.println("错误信息输出到" + errorFile.getAbsolutePath());
}


public void existConvertException(String fileName) {
ExcelReaderBuilder readerBuilder = FastExcel.read(fileName).head(ValidateDemoData.class);
try {
List<ValidateDemoData> list = readerBuilder.registerReadListener(new PageReadListener<>(l -> {
System.out.println("读取内容:" + JSON.toJSONString(l));
})).validate().setErrorHandler(FileErrorHandler.INSTANCE).sheet().doReadSync();
} catch (ExcelDataConvertException e) {
log.info("存在类型转换失败异常");
}
ValidateErrorHolder errorHolder = readerBuilder.getErrorHolder();
System.out.println(JSON.toJSONString(errorHolder));
File errorFile = readerBuilder.handleError();
System.out.println("错误信息输出到" + errorFile.getAbsolutePath());
}


public void testAddError(String fileName) {
ExcelReaderBuilder readerBuilder = FastExcel.read(fileName).head(ValidateDemoData.class);
List<ValidateDemoData> list = null;
try {
list = readerBuilder.registerReadListener(new PageReadListener<>(l -> {
System.out.println("读取内容:" + JSON.toJSONString(l));
})).validate().setErrorHandler(FileErrorHandler.INSTANCE).sheet().doReadSync();
} catch (ExcelDataConvertException e) {
log.info("存在类型转换失败异常");
}
ValidateErrorHolder errorHolder = readerBuilder.getErrorHolder();
System.out.println(JSON.toJSONString(errorHolder));

// 手动业务校验
if(CollectionUtils.isEmpty(list)){
return;
}

for (int i = 0; i < list.size(); i++) {
if(true){
errorHolder.addError(i+1,"订单号已存在");
}
if(true){
errorHolder.addError(i+1,"用户不存在");
}
}


File errorFile = readerBuilder.handleError();
System.out.println("错误信息输出到" + errorFile.getAbsolutePath());
}


}
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,10 @@
*/
@Deprecated
String format() default "";

/**
* use with {@link .ValidateReadListener} to verify whether a field is empty
* @return whether the field can be null
*/
boolean notNull() default false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
import cn.idev.excel.event.AnalysisEventListener;
import cn.idev.excel.event.SyncReadListener;
import cn.idev.excel.read.listener.ModelBuildEventListener;
import cn.idev.excel.read.listener.ValidateReadListener;
import cn.idev.excel.read.metadata.ReadWorkbook;
import cn.idev.excel.read.metadata.holder.ValidateErrorHolder;
import cn.idev.excel.read.processor.TextErrorHandler;
import cn.idev.excel.read.processor.ValidateErrorHandler;
import cn.idev.excel.support.ExcelTypeEnum;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.List;
import javax.xml.parsers.SAXParserFactory;
import lombok.Getter;

/**
* Build ExcelReader
Expand All @@ -30,6 +35,12 @@ public class ExcelReaderBuilder extends AbstractExcelReaderParameterBuilder<Exce
*/
private final ReadWorkbook readWorkbook;

@Getter
private ValidateErrorHandler<?> errorHandler;

@Getter
private ValidateErrorHolder errorHolder;

public ExcelReaderBuilder() {
this.readWorkbook = new ReadWorkbook();
}
Expand Down Expand Up @@ -282,4 +293,28 @@ public ExcelReaderBuilder ignoreHiddenSheet(Boolean ignoreHiddenSheet) {
readWorkbook.setIgnoreHiddenSheet(ignoreHiddenSheet);
return this;
}

public ExcelReaderBuilder setErrorHandler(ValidateErrorHandler<?> validateErrorHandler) {
this.errorHandler = validateErrorHandler;
return this;
}

/**
* enable validate,
* Note: The default {@link TextErrorHandler} is used here.
* If a replacement is needed, call {@link ExcelReaderBuilder#setErrorHandler} after this method.
*
* @return
*/
public ExcelReaderBuilder validate() {
ValidateReadListener<?> validateReadListener = new ValidateReadListener<>();
registerReadListener(validateReadListener);
errorHolder = validateReadListener;
setErrorHandler(TextErrorHandler.INSTANCE);
return this;
}

public <T> T handleError() {
return (T) getErrorHandler().handleError(getErrorHolder());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package cn.idev.excel.read.listener;

import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.exception.ExcelDataConvertException;
import cn.idev.excel.metadata.Head;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.read.metadata.ValidateError;
import cn.idev.excel.read.metadata.holder.ValidateErrorHolder;
import cn.idev.excel.read.metadata.property.ExcelReadHeadProperty;
import java.io.File;
import java.lang.reflect.Field;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* validate read listener
*
*/
public class ValidateReadListener<T> implements ReadListener<T>, ValidateErrorHolder {

private static final Logger LOGGER = LoggerFactory.getLogger(ValidateReadListener.class);

private boolean existValidate = true;

private final Map<Field, String> fieldMap = new HashMap<>();

private final Map<Integer, List<ValidateError>> errorMap = new TreeMap<>();

private static final String CHECK_EMPTY_TEXT = "%s cannot be null or empty";
private static final String CONVERSION_FAIL_TEXT =
"the '%s' field type conversion failed, please enter the correct content";

private File sourceFile = null;
private Integer sheetNo;

/**
* Record {@link ExcelDataConvertException}
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException convertException = ((ExcelDataConvertException) exception);
Field field = convertException.getExcelContentProperty().getField();
String headName = fieldMap.get(field);
ValidateError error = new ValidateError(
context.readRowHolder().getRowIndex(), headName, String.format(CONVERSION_FAIL_TEXT, headName));
// mark conversion failure
error.setConvertError(true);
addError(error);
}
}

/**
* Initialize all fields that require validation
*
* @param headMap
* @param context
*/
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
ExcelReadHeadProperty excelReadHeadProperty =
context.currentReadHolder().excelReadHeadProperty();
for (Head head : excelReadHeadProperty.getHeadMap().values()) {
Field field = head.getField();
String headName = String.join("-", head.getHeadNameList());
field.setAccessible(true);
fieldMap.put(field, headName);

ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if (excelProperty != null && excelProperty.notNull()) {}
}
if (fieldMap.isEmpty()) {
existValidate = false;
}
if (sourceFile == null) {
sourceFile = context.readWorkbookHolder().getFile();
}
sheetNo = context.readSheetHolder().getSheetNo();
}

@Override
public void invoke(T data, AnalysisContext context) {
if (existValidate) {
checkNotNull(data, context);
}
}

private void checkNotNull(T data, AnalysisContext context) {
Integer rowIndex = context.readRowHolder().getRowIndex();
fieldMap.forEach((field, headName) -> {
try {
Object attribute = field.get(data);
if (attribute == null || (field.getType().equals(String.class) && ((String) attribute).isEmpty())) {
addError(rowIndex, String.format(CHECK_EMPTY_TEXT, headName));
}
} catch (IllegalAccessException e) {
LOGGER.warn("failed to retrieve field properties through reflection");
}
});
}

@Override
public void doAfterAllAnalysed(AnalysisContext context) {}

@Override
public Map<Integer, List<ValidateError>> getError() {
return errorMap;
}

@Override
public void addError(Integer rowNum, String message) {
ValidateError error = new ValidateError(rowNum, message);
errorMap.computeIfAbsent(rowNum, key -> new ArrayList<>());
errorMap.get(rowNum).add(error);
}

public void addError(ValidateError error) {
errorMap.computeIfAbsent(error.getRowNum(), key -> new ArrayList<>());
errorMap.get(error.getRowNum()).add(error);
}

@Override
public File getSourceFile() {
return sourceFile;
}

@Override
public Integer getSheetNo() {
return sheetNo;
}
}
Loading