参数校验 - 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
实现
- 定义规则注解
@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 "";//自定义正则表达式验证
}
- 拦截解析
分两种方式,一种是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);
}
}
}
}
- 使用
@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) {
...
}