参数校验 - 969251639/study GitHub Wiki

背景

spring mvc自带表单验证器做不到同个model类中的某个属性在不同接口中支持不同的校验规则,比如A接口需要Model中的p属性为必填,而B接口需要Model中的p属性为非必填,故造个轮子,自己封装一个

设计思路

当客户端请求时通过Controller增强类,获取接口上的注解来验证规则

每个表单在执行方法前都可以配置参数校验,参数校验分为两种
1:url param,一般为get方式,key value映射
2:body param,一般为post方式,数据类型为json

实现

  1. 定义规则注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Valids {
	ValidArray[] validArrays() default {};//验证Array类型的参数校验
	ValidDouble[] validDouble() default {};//验证Double类型的参数校验
	ValidInt[] validInts() default {};//验证Int类型的参数校验
	ValidLong[] validLongs() default {};//验证Long类型的参数校验
	ValidString[] validStrings() default {};//验证String类型的参数校验
	ValidBoolean[] validBooleans() default {};//验证Boolean类型的参数校验
}

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidArray {//Array
	String name();//属性名
	String message() default "";//验证失败提示信息
	
	boolean notNull() default false;//是否为可为空
	int minSize() default 0;//数组最小长度值
	int maxSize() default Integer.MAX_VALUE;//数组最大长度值
}

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidBoolean {//Boolean
	String name();//属性名
	String message() default "";//验证失败提示信息
	
	boolean notNull() default false;//是否为可为空
	boolean assertFalse() default false;//断言是否为false
	boolean assertTrue() default false;//断言是否为true
}

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidDouble {//Double
	String name();//属性名
	String message() default "";//验证失败提示信息
	
	boolean notNull() default false;//是否为可为空
	double min() default Double.MIN_VALUE;//最小值
	double max() default Double.MAX_VALUE;//最大值
	double[] include() default {};//是否只能是数组中的值
}

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidInt {//Int
	String name();//属性名
	String message() default "";//验证失败提示信息
	
	boolean notNull() default false;//是否为可为空
	int min() default Integer.MIN_VALUE;//最小值
	int max() default Integer.MAX_VALUE;//最大值
	int[] include() default {};//是否只能是数组中的值
}

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidLong {//Long
	String name();//属性名
	String message() default "";//验证失败提示信息
	
	boolean notNull() default false;//是否为可为空
	long min() default Long.MIN_VALUE;//最小值
	long max() default Long.MAX_VALUE;//最大值
	long[] include() default {};//是否只能是数组中的值
}

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidString {//String
	String name();//属性名
	String message() default "";//验证失败提示信息
	
	boolean notNull() default false;//是否为可为空
	int minLength() default 0;//字符串最小长度
	int maxLength() default Integer.MAX_VALUE; //字符串最大长度
	String[] include() default {};//是否只能是数组中的值
	
	boolean email() default false;//是否是email
	boolean mobile() default false;//是否是mobile
	String pattern() default "";//自定义正则表达式验证
}

  1. 拦截解析
    分两种方式,一种是url param,一种是body(项目中规定body只能穿json),另外之所以没有用拦截器实现是因为解析body的时候需要读取request中的stream,而request的stream只能被读取一次,导致异常

url param:

@Aspect
@Component
public class CommonRequestParameterAdvice extends PointcutDefinition {
	@Before("executeRequestMapping()")
	public void doControllerBefore(JoinPoint joinPoint) throws Throwable {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            if (HttpMethod.GET.matches(request.getMethod())) {
        	Signature signature = joinPoint.getSignature();    
        	MethodSignature methodSignature = (MethodSignature)signature;    
        	Method targetMethod = methodSignature.getMethod(); 
        	Valids valids = targetMethod.getAnnotation(Valids.class);//获取方法上的注解
        	ValidationUtils.valids(valids, request);
            }
	}
}

body:

@ControllerAdvice
public class CommonRequestBodyAdvice implements RequestBodyAdvice {
	
	@Override
	public boolean supports(MethodParameter methodParameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) {
		return true;
	}

	@Override
	public Object handleEmptyBody(Object body, HttpInputMessage inputMessage,
			MethodParameter parameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) {
		return body;
	}

	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage,
			MethodParameter parameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType)
			throws IOException {
		return inputMessage;
	}

	@Override
	public Object afterBodyRead(Object body, HttpInputMessage inputMessage,
			MethodParameter parameter, Type targetType,
			Class<? extends HttpMessageConverter<?>> converterType) {
		Valids valids = parameter.getMethodAnnotation(Valids.class);
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
                ServletRequestAttributes sra = (ServletRequestAttributes) ra;
                javax.servlet.http.HttpServletRequest request = sra.getRequest();
                ValidationUtils.valids(valids, request, body);
		return body;
	}
}

这两种方式最终都会调用ValidationUtils工具类中的valids的重载方法

public class ValidationUtils {
	
	public static void valids(Valids valids, HttpServletRequest request) {
		if (null == valids) {
			return ;
		}
		BaseCheck baseCheck = null;
		
		ValidArray[] validArrays = valids.validArrays();
		for (ValidArray validArray : validArrays) {//array校验
			baseCheck = new ValidArrayCheck();
			baseCheck.valid(validArray, request.getParameter(validArray.name()));
		}
		
		ValidDouble[] validDoubles = valids.validDouble();
		for (ValidDouble validDouble : validDoubles) {//double校验
			baseCheck = new ValidDoubleCheck();
			baseCheck.valid(validDouble, request.getParameter(validDouble.name()));
		}
		
		ValidInt[] validInts = valids.validInts();
		for (ValidInt validInt : validInts) {//int校验
			baseCheck = new ValidIntCheck();
			baseCheck.valid(validInt, request.getParameter(validInt.name()));
		}
		
		ValidLong[] ValidLongs = valids.validLongs();
		for (ValidLong validLong : ValidLongs) {//long校验
			baseCheck = new ValidLongCheck();
			baseCheck.valid(validLong, request.getParameter(validLong.name()));
		}
		
		ValidString[] ValidStrings = valids.validStrings();
		for (ValidString validString : ValidStrings) {//string校验
			baseCheck = new ValidStringCheck();
			baseCheck.valid(validString, request.getParameter(validString.name()));
		}
		
		ValidBoolean[] validBooleans = valids.validBooleans();
		for (ValidBoolean validBoolean : validBooleans) {//boolean校验
			baseCheck = new ValidStringCheck();
			baseCheck.valid(validBoolean, request.getParameter(validBoolean.name()));
		}
	}
	
	public static void valids(Valids valids, HttpServletRequest request, Object body) {
		if (null == valids) {
			return ;
		}
		
		String queryString = request.getQueryString();//如果body方式也有url param
		
		JsonObject jsonObject = new JsonObject();//解析json
		if (null != body && StringUtils.isNotEmpty(JsonUtils.toJson(body))) {
			jsonObject = JsonUtils.toJsonObj(JsonUtils.toJson(body));
			if (null == jsonObject) {
				jsonObject = new JsonObject();
			}
		}
		
		BaseCheck baseCheck = null;
		
		ValidArray[] validArrays = valids.validArrays();
		for (ValidArray validArray : validArrays) {//array校验
			baseCheck = new ValidArrayCheck();
			if (fromRequest(validArray.name(), queryString)) {//如果参数在url param上
				baseCheck.valid(validArray, request.getParameter(validArray.name()));
			} else {
				baseCheck.valid(validArray, jsonObject);
			}
		}
		
		ValidDouble[] validDoubles = valids.validDouble();
		for (ValidDouble validDouble : validDoubles) {//double校验
			baseCheck = new ValidDoubleCheck();
			if (fromRequest(validDouble.name(), queryString)) {//如果参数在url param上
				baseCheck.valid(validDouble, request.getParameter(validDouble.name()));
			} else {
				baseCheck.valid(validDouble, jsonObject);
			}
		}
		
		ValidInt[] validInts = valids.validInts();
		for (ValidInt validInt : validInts) {//int校验
			baseCheck = new ValidIntCheck();
			if (fromRequest(validInt.name(), queryString)) {//如果参数在url param上
				baseCheck.valid(validInt, request.getParameter(validInt.name()));
			} else {
				baseCheck.valid(validInt, jsonObject);
			}
		}
		
		ValidLong[] ValidLongs = valids.validLongs();
		for (ValidLong validLong : ValidLongs) {//long校验
			baseCheck = new ValidLongCheck();
			if (fromRequest(validLong.name(), queryString)) {//如果参数在url param上
				baseCheck.valid(validLong, request.getParameter(validLong.name()));
			} else {
				baseCheck.valid(validLong, jsonObject);
			}
		}
		
		ValidString[] ValidStrings = valids.validStrings();
		for (ValidString validString : ValidStrings) {//string校验
			baseCheck = new ValidStringCheck();
			if (fromRequest(validString.name(), queryString)) {//如果参数在url param上
				baseCheck.valid(validString, request.getParameter(validString.name()));
			} else {
				baseCheck.valid(validString, jsonObject);
			}
		}
		
		ValidBoolean[] validBooleans = valids.validBooleans();
		for (ValidBoolean validBoolean : validBooleans) {//boolean校验
			baseCheck = new ValidStringCheck();
			if (fromRequest(validBoolean.name(), queryString)) {//如果参数在url param上
				baseCheck.valid(validBoolean, request.getParameter(validBoolean.name()));
			} else {
				baseCheck.valid(validBoolean, jsonObject);
			}
		}
	}
	
	private static boolean fromRequest(String name, String queryString) {//判断url param是否有该参数
		if (StringUtils.isEmpty(queryString)) {
			return false;
		}
		if (queryString.indexOf(name + "=") == -1) {
			return false;
		}
		return true;
	}
}

最后都统一的调用了baseCheck的valid方法,而baseCheck是抽象类,具体的valid都交由具体的子类,也就说不同类型的校验器去校验

public abstract class BaseCheck {

	public abstract void valid(Annotation annotation, String value);
	
	public abstract void valid(Annotation annotation, JsonObject jsonObject);
	
}

public class ValidArrayCheck extends BaseCheck {

	@Override
	public void valid(Annotation annotation, String value) {
		ValidArray validArray = (ValidArray) annotation;
                String message = StringUtils.isNotBlank(validArray.message()) ? validArray.message() : validArray.name() + " param error";
		if (validArray.notNull()) {//校验是否为空
			if (StringUtils.isEmpty(value)) {
				throw new ValidateException(message);
			}
		}
		String[] valueArray = StringUtils.split(value, ",");
		int len = valueArray.length;
		if (len < validArray.minSize()) {//校验最小值
			throw new ValidateException(message);
		}
		if (len > validArray.maxSize()) {//校验最大值
			throw new ValidateException(message);
		}
	}

	@Override
	public void valid(Annotation annotation, JsonObject jsonObject) {
		ValidArray validArray = (ValidArray) annotation;
                String message = StringUtils.isNotBlank(validArray.message()) ? validArray.message() : validArray.name() + " param error";
		JsonArray valueArray = null;
		if (validArray.notNull()) {
			if (null == jsonObject.get(validArray.name())) {//校验是否为空
				throw new ValidateException(message);
			}
			try {
				valueArray = jsonObject.get(validArray.name()).getAsJsonArray();
			} catch (Exception e) {
				throw new ValidateException(message);
			}
		}
		int len = valueArray.size();
		if (len < validArray.minSize()) {//校验最小值
			throw new ValidateException(message);
		}
		if (len > validArray.maxSize()) {//校验最大值
			throw new ValidateException(message);
		}
	}
}

public class ValidBooleanCheck extends BaseCheck {

	@Override
	public void valid(Annotation annotation, String value) {
		ValidBoolean validBoolean = (ValidBoolean) annotation;
                String message = StringUtils.isNotBlank(validBoolean.message()) ? validBoolean.message() : validBoolean.name() + " param error";
		if (validBoolean.notNull()) {//校验是否为空
			if (StringUtils.isEmpty(value)) {
				throw new ValidateException(message);
			}
		}
		Boolean bValue = null;
		try {//解析boolean
			bValue = Boolean.parseBoolean(value);
		} catch (Exception e) {
			throw new ValidateException(message);
		}
		if (null == bValue) {//解析boolean失败
			throw new ValidateException(message);
		}
                if (validBoolean.assertFalse()) {//断言为false
			if (bValue) {
				throw new ValidateException(message);
			}
		}
		if (validBoolean.assertTrue()) {//断言为true
			if (!bValue) {
				throw new ValidateException(message);
			}
		}
	}

	@Override
	public void valid(Annotation annotation, JsonObject jsonObject) {
		ValidBoolean validBoolean = (ValidBoolean) annotation;
                String message = StringUtils.isNotBlank(validBoolean.message()) ? validBoolean.message() : validBoolean.name() + " param error";
		if (validBoolean.notNull()) {//校验是否为空
			if (null == jsonObject.get(validBoolean.name())) {
				throw new ValidateException(message);
			}
		}
		Boolean bValue = null;
		try {//解析boolean
			bValue = jsonObject.get(validBoolean.name()).getAsBoolean();
		} catch (Exception e) {
			throw new ValidateException(message);
		}
		if (null == bValue) {//解析boolean失败
			throw new ValidateException(message);
		}
                if (validBoolean.assertFalse()) {//断言为false
			if (bValue) {
				throw new ValidateException(message);
			}
		}
		if (validBoolean.assertTrue()) {//断言为true
			if (!bValue) {
				throw new ValidateException(message);
			}
		}
	}
}


public class ValidDoubleCheck extends BaseCheck {

	@Override
	public void valid(Annotation annotation, String value) {
		ValidDouble validDouble = (ValidDouble) annotation;
                String message = StringUtils.isNotBlank(validDouble.message()) ? validDouble.message() : validDouble.name() + " param error";
		if (validDouble.notNull()) {//是否为空
			if (StringUtils.isEmpty(value)) {
				throw new ValidateException(validDouble.message());
			}
		}
		Double dValue = null;
		try {//解析double
			dValue = Double.parseDouble(value);
		} catch (NumberFormatException e) {
			throw new ValidateException(validDouble.message());
		}
		otherValid(validDouble, dValue, message);
	}

	@Override
	public void valid(Annotation annotation, JsonObject jsonObject) {
		ValidDouble validDouble = (ValidDouble) annotation;
                String message = StringUtils.isNotBlank(validDouble.message()) ? validDouble.message() : validDouble.name() + " param error";
		Double dValue = null;
		if (validDouble.notNull()) {//是否为空
			if (null == jsonObject.get(validDouble.name())) {
				throw new ValidateException(validDouble.message());
			}
		}
		try {//解析double
			dValue = jsonObject.get(validDouble.name()).getAsDouble();
		} catch (Exception e) {
			throw new ValidateException(validDouble.message());
		}
		if (null == dValue) {
			throw new ValidateException(validDouble.message());
		}
		otherValid(validDouble, dValue, message);
	}
	
	private void otherValid(ValidDouble validDouble, Double dValue, String message) {
		if (dValue.doubleValue() < validDouble.min()) {//校验最小值
			throw new ValidateException(message);
		}
		if (dValue.doubleValue() > validDouble.max()) {//校验最大值
			throw new ValidateException(message);
		}
		if (null != validDouble.include() && validDouble.include().length > 0) {//是否是数组中的值
			boolean isContains = false;
			for (double include : validDouble.include()) {
				if (include == dValue.doubleValue()) {
					isContains = true;
					break;
				}
			}
			if (!isContains) {
				throw new ValidateException(message);
			}
		}
	}
}

public class ValidIntCheck extends BaseCheck {

	@Override
	public void valid(Annotation annotation, String value) {
		ValidInt validInt = (ValidInt) annotation;
                String message = StringUtils.isNotBlank(validInt.message()) ? validInt.message() : validInt.name() + " param error";
		if (validInt.notNull()) {//校验是否为空
			if (StringUtils.isEmpty(value)) {
				throw new ValidateException(message);
			}
		}
		Integer iValue = null;
		try {//解析int
			iValue = Integer.parseInt(value);
		} catch (NumberFormatException e) {
			throw new ValidateException(message);
		}
		if (null == iValue) {//解析失败
			throw new ValidateException(message);
		}
		validOther(validInt, iValue, message);
	}

	@Override
	public void valid(Annotation annotation, JsonObject jsonObject) {
		ValidInt validInt = (ValidInt) annotation;
                String message = StringUtils.isNotBlank(validInt.message()) ? validInt.message() : validInt.name() + " param error";
		if (validInt.notNull()) {//校验是否为空
			if (null == jsonObject.get(validInt.name())) {
				throw new ValidateException(message);
			}
		}
		Integer iValue = null;
		try {//解析int
			iValue = jsonObject.get(validInt.name()).getAsInt();
		} catch (Exception e) {
			throw new ValidateException(message);
		}
		if (null == iValue) {//解析失败
			throw new ValidateException(message);
		}
		validOther(validInt, iValue, message);
	}
	
	private void validOther(ValidInt validInt, Integer iValue, String message) {
		if (iValue.intValue() < validInt.min()) {//校验最小值
			throw new ValidateException(message);
		}
		if (iValue.intValue() > validInt.max()) {//校验最大值
			throw new ValidateException(message);
		}
		if (null != validInt.include() && validInt.include().length > 0) {//是否是数组中的值
			boolean isContains = false;
			for (int include : validInt.include()) {
				if (include == iValue.intValue()) {
					isContains = true;
					break;
				}
			}
			if (!isContains) {
				throw new ValidateException(message);
			}
		}
	}
}

public class ValidLongCheck extends BaseCheck {

	@Override
	public void valid(Annotation annotation, String value) {
		ValidLong validLong = (ValidLong) annotation;
                String message = StringUtils.isNotBlank(validLong.message()) ? validLong.message() : validLong.name() + " param error";
		if (validLong.notNull()) {//校验是否为空
			if (StringUtils.isEmpty(value)) {
				throw new ValidateException(message);
			}
		}
		Long lValue = null;
		try {//解析long
			lValue = Long.parseLong(value);
		} catch (NumberFormatException e) {
			throw new ValidateException(message);
		}
		if (null == lValue) {//解析失败
			throw new ValidateException(message);
		}
		validOther(validLong, lValue, message);
	}

	@Override
	public void valid(Annotation annotation, JsonObject jsonObject) {
		ValidLong validLong = (ValidLong) annotation;
                String message = StringUtils.isNotBlank(validLong.message()) ? validLong.message() : validLong.name() + " param error";
		if (validLong.notNull()) {//校验是否为空
			if (null == jsonObject.get(validLong.name())) {
				throw new ValidateException(message);
			}
		}
		Long lValue = null;
		try {//解析long
			lValue = jsonObject.get(validLong.name()).getAsLong();
		} catch (Exception e) {
			throw new ValidateException(message);
		}
		if (null == lValue) {//解析失败
			throw new ValidateException(message);
		}
		validOther(validLong, lValue, message);
	}
	
	private void validOther(ValidLong validLong, Long lValue, String message) {
		if (lValue.longValue() < validLong.min()) {//校验最小值
			throw new ValidateException(message);
		}
		if (lValue.longValue() > validLong.max()) {//校验最大值
			throw new ValidateException(message);
		}
		if (null != validLong.include() && validLong.include().length > 0) {//是否是数组中的值
			boolean isContains = false;
			for (long include : validLong.include()) {
				if (include == lValue.longValue()) {
					isContains = true;
					break;
				}
			}
			if (!isContains) {
				throw new ValidateException(message);
			}
		}
	}
}

public class ValidStringCheck extends BaseCheck {
	
	private static final String DEFAULT_MOBILE_PATTERN = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$";
	private static final String DEFAULT_EMAIL_PATTERN = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";

	@Override
	public void valid(Annotation annotation, String value) {
		ValidString validString = (ValidString) annotation;
                String message = StringUtils.isNotBlank(validString.message()) ? validString.message() : validString.name() + " param error";
		if (validString.notNull()) {//校验是否为空
			if (StringUtils.isEmpty(value)) {
				throw new ValidateException(message);
			}
		}
		validOther(validString, value, message);
	}

	@Override
	public void valid(Annotation annotation, JsonObject jsonObject) {
		ValidString validString = (ValidString) annotation;
                String message = StringUtils.isNotBlank(validString.message()) ? validString.message() : validString.name() + " param error";
		if (validString.notNull()) {//校验是否为空
			if (null == jsonObject.get(validString.name())) {
				throw new ValidateException(message);
			}
		}
		String value = null;
		try {//解析string
			value = jsonObject.get(validString.name()).getAsString();
		} catch (Exception e) {
			throw new ValidateException(validString.message());
		}
		if (StringUtils.isEmpty(value)) {//解析失败
			throw new ValidateException(validString.message());
		}
		validOther(validString, value, message);
	}
	
	private void validOther(ValidString validString, String value, String message) {
		if (value.length() < validString.minLength()) {//校验最小值
			throw new ValidateException(message);
		}
		if (value.length() > validString.maxLength()) {//校验最大值
			throw new ValidateException(message);
		}
		if (null != validString.include() && validString.include().length > 0) {//是否是数组中的值
			boolean isContains = false;
			for (String include : validString.include()) {
				if (include.equals(value)) {
					isContains = true;
					break;
				}
			}
			if (!isContains) {
				throw new ValidateException(message);
			}
		}
		if (validString.email()) {//email校验
			String pattern = ValidConstants.EMAIL_PATTERN == null ? DEFAULT_EMAIL_PATTERN : ValidConstants.EMAIL_PATTERN;
			if (!Pattern.matches(pattern, value)) {
				throw new ValidateException(message);
			}
		}

		if (validString.mobile()) {//mobile校验
			String pattern = ValidConstants.MOBILE_PATTERN == null ? DEFAULT_MOBILE_PATTERN : ValidConstants.MOBILE_PATTERN;
			if (!Pattern.matches(pattern, value)) {
				throw new ValidateException(message);
			}
		}

		if (StringUtils.isNotEmpty(validString.pattern())) {//正则校验
			if (!Pattern.matches(validString.pattern(), value)) {
				throw new ValidateException(message);
			}
		}
	}

}

  1. 使用
    @RequestMapping(value = "/add", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
    @org.springframework.web.bind.annotation.ResponseBody
    @Valids(validInts = {
            @ValidInt(name="xxx", notNull = true, message = "参数xxx不能为空")
    }, validStrings = {
            @ValidString(name="xxx", notNull = true, message = "参数xxx不能为空"),
    })
    public ResponseBody add(@RequestBody BloodPressureVo bloodPressureVo) {
         ...
    }