Skip to content

Commit

Permalink
perf: 优化测试工具代码,便于后续直接对内部验证类进行处理
Browse files Browse the repository at this point in the history
  • Loading branch information
stick-i committed Oct 29, 2024
1 parent 6305ac6 commit eca6c16
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
public class ObjectValidResult {

private final ArrayList<FieldError> errors = new ArrayList<>();
private final List<FieldError> errors = new ArrayList<>();

public boolean hasError() {
return !errors.isEmpty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cn.sticki.spel.validator.javax.util;

import javax.validation.ConstraintViolation;
import java.util.*;
import java.util.stream.Collectors;

/**
* 约束违反集合
* <p>
* 用于存储校验结果,根据字段名和期望的错误信息来获取字段约束结果
*
* @author 阿杆
* @since 2024/10/29
*/
public class ConstraintViolationSet {

private static final ConstraintViolationSet EMPTY = new ConstraintViolationSet(Collections.emptyList());

private final Map<String, List<VerifyFailedField>> verifyMap;

public ConstraintViolationSet(Collection<VerifyFailedField> failedFields) {
if (failedFields == null || failedFields.isEmpty()) {
verifyMap = Collections.emptyMap();
return;
}

this.verifyMap = failedFields.stream().collect(Collectors.groupingBy(VerifyFailedField::getName));
}

public static ConstraintViolationSet of(Set<ConstraintViolation<Object>> validate) {
if (validate == null || validate.isEmpty()) {
return EMPTY;
}
List<VerifyFailedField> list = validate.stream().map(ConstraintViolationSet::convert).collect(Collectors.toList());
return new ConstraintViolationSet(list);
}

public static ConstraintViolationSet of(List<VerifyFailedField> validate) {
return new ConstraintViolationSet(validate);
}

private static VerifyFailedField convert(ConstraintViolation<Object> violation) {
return VerifyFailedField.of(violation.getPropertyPath().toString(), violation.getMessage());
}

/**
* 根据字段和期望的错误信息来获取字段约束结果
*
* @param fieldName 字段名
* @param expectMessage 期望的错误信息
* @return 字段约束结果,当 expectMessage 不为null时,会优先匹配具有相同message的数据
*/
public VerifyFailedField getAndRemove(String fieldName, String expectMessage) {
List<VerifyFailedField> violationList = verifyMap.get(fieldName);
if (violationList == null || violationList.isEmpty()) {
return null;
}
if (violationList.size() == 1 || expectMessage == null) {
VerifyFailedField violation = violationList.get(0);
verifyMap.remove(fieldName);
return violation;
}
// 当存在多个约束时,优先匹配具有相同message的数据。
// 否则当一个字段有多个约束条件时,无法匹配到期望的约束。
for (VerifyFailedField violation : violationList) {
if (expectMessage.equals(violation.getMessage())) {
violationList.remove(violation);
return violation;
}
}

return violationList.remove(0);
}

/**
* 获取所有的约束违反字段
*/
public Set<VerifyFailedField> getAll() {
return verifyMap.values().stream().flatMap(List::stream).collect(Collectors.toSet());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cn.sticki.spel.validator.javax.util;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

/**
* 日志上下文
*
* @author 阿杆
* @since 2024/10/29
*/
@Slf4j
public class LogContext {

/**
* 设置验证对象日志上下文
*/
public static void setValidateObject(Object object) {
String className = object.getClass().getSimpleName();
Class<?> enclosingClass = object.getClass().getEnclosingClass();
if (enclosingClass != null) {
className = enclosingClass.getSimpleName() + "." + className;
}

MDC.put("className", className);
MDC.put("fullClassName", abbreviate(object.getClass().getName()));
if (object instanceof ID) {
MDC.put("id", String.valueOf(((ID) object).getId()));
}
}

/**
* 清除验证对象日志上下文
*/
public static void clearValidateObject() {
MDC.remove("id");
MDC.remove("className");
MDC.remove("fieldName");
MDC.remove("fullClassName");
}

public static void set(String key, String value) {
MDC.put(key, value);
}

public static void remove(String key) {
MDC.remove(key);
}

/**
* 缩写类名
*/
private static String abbreviate(String className) {
String[] parts = className.split("\\.");
StringBuilder abbreviated = new StringBuilder();
for (int i = 0; i < parts.length - 1; i++) {
abbreviated.append(parts[i].charAt(0)).append(".");
}
abbreviated.append(parts[parts.length - 1]);
return abbreviated.toString();
}

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package cn.sticki.spel.validator.javax.util;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
* 测试验证工具类
Expand Down Expand Up @@ -55,25 +55,14 @@ public static boolean checkConstraintResult(VerifyObject verifyObject) {
boolean expectException = verifyObject.isExpectException();

// 设置日志上下文
String className = object.getClass().getSimpleName();
Class<?> enclosingClass = object.getClass().getEnclosingClass();
if (enclosingClass != null) {
className = enclosingClass.getSimpleName() + "." + className;
}

MDC.put("className", className);
MDC.put("fullClassName", abbreviate(object.getClass().getName()));
if (object instanceof ID) {
MDC.put("id", String.valueOf(((ID) object).getId()));
}

LogContext.setValidateObject(object);
log.info("Start checking object: {}", object);

int failCount = 0;
try {
// 执行约束校验
Set<ConstraintViolation<Object>> validate = ValidateUtil.validate(object);
failCount += calcFailCount(verifyFailedFields, ViolationSet.of(validate));
failCount += processVerifyResult(verifyFailedFields, ConstraintViolationSet.of(validate));
} catch (Exception e) {
if (expectException) {
log.info("Passed, Capture exception {}, message: {}", e.getClass(), e.getMessage());
Expand All @@ -95,30 +84,32 @@ public static boolean checkConstraintResult(VerifyObject verifyObject) {
log.error("Verification end, number of failures: {}", failCount);
}
log.info("------------------------------------------------------------------------");
MDC.clear();
LogContext.clearValidateObject();

return failCount == 0;
}

/**
* 计算验证字段约束的失败次数
* 处理验证结果
*
* @param verifyFailedFields 预期失败字段
* @param violationSet 验证结果
* @return 验证失败的次数
*/
private static int calcFailCount(Collection<VerifyFailedField> verifyFailedFields, ViolationSet violationSet) {
private static int processVerifyResult(Collection<VerifyFailedField> verifyFailedFields, ConstraintViolationSet violationSet) {
final String fieldNameLogKey = "fieldName";
int failCount = 0;
// 检查结果是否符合预期
for (VerifyFailedField verifyFailedField : verifyFailedFields) {
String fieldName = verifyFailedField.getName();
MDC.put("fieldName", fieldName);
LogContext.set(fieldNameLogKey, fieldName);
String message = verifyFailedField.getMessage();

log.info("Expected exception information: {}", message == null ? "ignore" : message);

boolean fieldMatch = false, find = false;

ConstraintViolation<Object> violation = violationSet.getAndRemove(fieldName, message);
VerifyFailedField violation = violationSet.getAndRemove(fieldName, message);
if (violation != null) {
find = true;
log.info("Real exception information: {}", violation.getMessage());
Expand All @@ -141,75 +132,14 @@ private static int calcFailCount(Collection<VerifyFailedField> verifyFailedField
}
}

MDC.remove("fieldName");
LogContext.remove(fieldNameLogKey);
// 被忽略的字段
for (ConstraintViolation<Object> violation : violationSet.getAll()) {
log.error("Field [{}] is ignored", violation.getPropertyPath().toString());
for (VerifyFailedField violation : violationSet.getAll()) {
log.error("Field [{}] is ignored", violation.getName());
failCount++;
}
return failCount;
}

public static String abbreviate(String className) {
String[] parts = className.split("\\.");
StringBuilder abbreviated = new StringBuilder();
for (int i = 0; i < parts.length - 1; i++) {
abbreviated.append(parts[i].charAt(0)).append(".");
}
abbreviated.append(parts[parts.length - 1]);
return abbreviated.toString();
}

static class ViolationSet {

private final Map<String, List<ConstraintViolation<Object>>> violationMap;

public ViolationSet(Set<ConstraintViolation<Object>> validate) {
if (validate == null || validate.isEmpty()) {
violationMap = Collections.emptyMap();
return;
}
violationMap = validate.stream().collect(
Collectors.groupingBy(violation -> violation.getPropertyPath().toString())
);
}

public static ViolationSet of(Set<ConstraintViolation<Object>> validate) {
return new ViolationSet(validate);
}

/**
* 根据字段和期望的错误信息来获取字段约束结果
*
* @param fieldName 字段名
* @param expectMessage 期望的错误信息
* @return 字段约束结果,当 expectMessage 不为null时,会优先匹配具有相同message的数据
*/
public ConstraintViolation<Object> getAndRemove(String fieldName, String expectMessage) {
List<ConstraintViolation<Object>> violationList = violationMap.get(fieldName);
if (violationList == null || violationList.isEmpty()) {
return null;
}
if (violationList.size() == 1 || expectMessage == null) {
ConstraintViolation<Object> violation = violationList.get(0);
violationMap.remove(fieldName);
return violation;
}
// 当存在多个约束时,优先匹配具有相同message的数据
for (ConstraintViolation<Object> violation : violationList) {
if (expectMessage.equals(violation.getMessage())) {
violationList.remove(violation);
return violation;
}
}

return violationList.remove(0);
}

public Set<ConstraintViolation<Object>> getAll() {
return violationMap.values().stream().flatMap(List::stream).collect(Collectors.toSet());
}

}

}

0 comments on commit eca6c16

Please sign in to comment.