validation 为我们提供了这么多特性,几乎可以满足日常开发中大多数参数校验场景了,如@NotNull,@NotBlank等。 但是在实际开发过程中如字段A满足条件,才对字段B进行校验这种场景也应用的比较多。然validation 库并没有提供这样的校验注解,一般需要在业务逻辑里面做判断。validation 也提供了扩展,允许用户自定义校验。接下来实现参数自定义条件校验注解。
1 场景
场景: 请求参数对象中,当属性A= x时, 才校验属性B
示例: 当type= 1 时, username必填, 否则username可不填
当type = 2 时, status必填且允许填值1或者2,不能填值3和4
@ApiModelProperty("类型")
@NotNull(message = "类型不能为空")
private Integer type;
@ApiModelProperty("用户名")
@ConditionBy(column = "type",columnValue = "1",message = "用户名不能为空")
private String username;
/**
* allowedValues --条件触发时,只允许填的值(被注解属性不为对象) (选填)
* unAllowedValues --条件触发时,不允许填的值(被注解属性不为对象) (选填)
*/
@ApiModelProperty("状态")
@ConditionBy(column = "type",columnValue = "2", allowedValues = {"1","2"},
unAllowedValues = {"3","4"},message = "状态不能为空")
private String status;
2 自定义注解
/**
* 该注解用于参数的关联校验
* 型如: 属性A=1时,对属性B进行校验
* @Author: zhangxilin
* @Date: 2023/6/27
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ConditionBy.List.class)
@Constraint(validatedBy = {ConditionByValidator.class})
public @interface ConditionBy {
/**
* 决定该字段需要关联的字段名
* @return
*/
String column();
/**
* column的会触发该校验校的条件值类型
* @return
*/
ConditionType columnValueType() default ConditionType.FIXED;
/**
* column的会触发该校验的条件值(ConditionType.FIXED是必填)
* @return
*/
String[] columnValue() default {};
/**
* 条件触发时,只允许填的值(被注解属性不为对象)
*/
String[] allowedValues() default { };
/**
* 条件触发时,不允许填的值(被注解属性不为对象)
*/
String[] unAllowedValues() default { };
/**
* 自定义错误信息
*/
String message() default "不合法的参数";
/**
* 忽略错误消息详情,为false时,返回错误消息详情
* @return
*/
boolean ignoreMsgDetail() default true;
/**
* 是否校验属性,开启则对其校验,不开启只判空。(一般需要校验对象或是对象列表时使用,默认关闭)
* @return
*/
boolean isCheck() default false;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface List {
ConditionBy[] value();
}
enum ConditionType{
NULL, //column对应字段值为空时触发校验
UNNULL, //column对应字段值不为空时触发校验
FIXED //column对应字段值为某个固定值(columnValue的值)时触发校验
}
3 校验逻辑
@Slf4j
public class ConditionByValidator implements ConstraintValidator<ConditionBy,Object> {
private String column;
private String[] columnValue;
private ConditionBy.ConditionType columnValueType ;
private String[] allowedValues;
private String[] unAllowedValues;
private boolean isCheck;
private String message;
private boolean ignoreMsgDetail;
private static Validator validator;
private static final String NULL_STR = "null";
@Override
public void initialize(ConditionBy constraintAnnotation) {
log.info("IsAllowByValidator>>>>>>>>>>>>>>>initialize");
column = constraintAnnotation.column();
columnValueType = constraintAnnotation.columnValueType();
columnValue = constraintAnnotation.columnValue();
allowedValues = constraintAnnotation.allowedValues();
unAllowedValues = constraintAnnotation.unAllowedValues();
isCheck = constraintAnnotation.isCheck();
message = constraintAnnotation.message();
ignoreMsgDetail = constraintAnnotation.ignoreMsgDetail();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
factory.close();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
// 获取column的请求值,为空的话校验失败
String columnRequestValue = getColumnRequestValue();
if(columnValueType == ConditionBy.ConditionType.NULL){
if(StringUtils.isNotBlank(columnRequestValue)){
//column字段值为空时校验
return true;
}
}else if(columnValueType == ConditionBy.ConditionType.UNNULL){
if(StringUtils.isBlank(columnRequestValue)){
//column字段值不为空时校验
return true;
}
}else {
if(StringUtils.isBlank(columnRequestValue)){
return ValidatorUtils.getResult(StrUtil.format("请求值{}不能为空",column),context);
}
if(columnValue ==null || columnValue.length ==0){
return ValidatorUtils.getResult("请填写条件校验触发值columnValue",context);
}
if(!Arrays.asList(columnValue).contains(columnRequestValue)){
// 如果column的值不存在于columnValue中 不校验
return true;
}
}
if(ObjectUtil.isEmpty(value)){
// 被注解字段的值为空直接校验不通过
return false;
}
// notAllowedValues、allowedValues存在情况
boolean b = (unAllowedValues != null && unAllowedValues.length > 0) || (allowedValues != null && allowedValues.length > 0);
if(b){
String validResult = ValidatorUtils.validateValues(allowedValues,unAllowedValues,value);
if(StringUtils.isNotBlank(validResult)){
String msg = ignoreMsgDetail?message:message+ "且"+validResult;
return ValidatorUtils.getResult(msg, context);
}
}
// 如果开启校验
if(isCheck){
return validObject(value,context);
}
return true;
}
private String getColumnRequestValue(){
String paramValue = null;
try {
paramValue = getParameter(column);
// 如果从param获取不到,再找body
if(StringUtils.isBlank(paramValue)){
String body = new RequestWrapper(getRequest()).getBody();
if(StringUtils.isNotBlank(body)){
JSONObject jsonObject = JSONUtil.parseObj(body);
Object paramValueObject= jsonObject.get(column);
if(ObjectUtil.isEmpty(paramValueObject)){
return null;
}
if(NULL_STR.equalsIgnoreCase(paramValueObject+"")){
return null;
}
paramValue = String.valueOf(paramValueObject);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return paramValue;
}
/**
* 校验传入对象
*
* @param o 当前字段的值
* @param context ConstraintValidatorContext校验上下文
* @return boolean
* @date 2023/9/20
**/
private boolean validObject(Object o, ConstraintValidatorContext context){
// 定义错误信息
StringBuffer errorMsg = new StringBuffer();
boolean result = true;
// 当被注解属性为列表时
if(o instanceof ArrayList<?>){
List<Set<ConstraintViolation<Object>>> collect = new ArrayList<>();
for (Object oItem : (List<?>) o){
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(oItem);
collect.add(constraintViolations);
}
result = ValidatorUtils.getListResult(collect);
ValidatorUtils.getListInfo(errorMsg, collect);
}else {
// 当被注解属性为单个对象时
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(o);
result = constraintViolations.isEmpty();
ValidatorUtils.getOneInfo(errorMsg, constraintViolations);
}
// 把自定义返回错误信息写入上下文
if(!result){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(errorMsg.toString())
.addConstraintViolation();
}
return result;
}
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
}