前言
在项目开发交互过程中,难免会遇到一些数据校验。以校验客户端发送数据的合法性,对于一些非空校验,我们也许可以使用@NonNull,@NotNull 等注解,可是对于一些常规的,如手机号,身份证等等的校验,我们就还要判断处理每个请求的参数的合法性。
但是合法性的判断是难以避免的,我们是否可以精简工作量、提高工作效率呢。
思考
我们或许应该从@NonNull @NotNull等其他注解那里受到些启发。
我们或许可以结合正则表达式及注解对某些通用数据进行验证。
注解可以设置参数,我们可以设置参数为校验规则,通过枚举列举出来,同时也应该允许用户自定义正则等校验。
我们知道,注解有三种类型
RetentionPolicy.SOURCE
RetentionPolicy.CLASS
RetentionPolicy.RUNTIME。
SOURCE主要用于编译之前,编译过程中会被丢弃如@Override注解。
CLASS主要用于编译,运行时会被丢弃。
RUNTIME在源码,编译,运行时始终会存在。
可以利用反射,拿到具有特定注解的bean,并处理。所以我们定义的注解应该是RUNTIME类型。同时声明注作用范围为FIELD及PARAMETER。
实践
定义注解
/**
* 数据验证注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface DataValid {
//是否可以为空
boolean nullable() default false;
//提供几种常用的正则验证
RegexType regexType() default RegexType.NONE;
//自定义正则验证
String regexExpression() default "";
//参数或者字段描述
String description() default "";
}
定义如上注解,nullable用来校验参数是否可空,默认不可以为空,false。
同时提供几种通用的正则校验,用枚举列出,如手机号码校验,身份证信息校验等等。
同时如果没有规定的正则表达式,可以让用户自定义自己的正则表达式。
另增加描述字段,用来说明这个paramer的用途。
定义常用正则枚举
/**
* 正则类型枚举
*/
public enum RegexType {
NONE,
SPECIALCHAR,
CHINESE,
EMAIL,
IP,
NUMBER,
NUMBERORNIL,
PHONENUMBER,
ID;
}
列出几种常用枚举。非空,特殊字符,中文,邮箱,IP,数字等等
枚举规则
定义了枚举,要定义它们的具体对应的方法,以便后续调用。
/**
* 常用正则表达式
*/
public class RegexUtils {
/**
* 判断是否是正确的IP地址
*
* @param ip
* @return boolean true,通过,false,没通过
*/
public static boolean isIp(String ip) {
if (null == ip || "".equals(ip))
return false;
String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."
+ "(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";
return ip.matches(regex);
}
/**
* 判断是否是正确的邮箱地址
*
* @param email
* @return boolean true,通过,false,没通过
*/
public static boolean isEmail(String email) {
if (null == email || "".equals(email))
return false;
String regex = "\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*";
return email.matches(regex);
}
/**
* 判断是否含有中文,仅适合中国汉字,不包括标点
* @param text
* @return boolean true,通过,false,没通过
*/
public static boolean isChinese(String text) {
if (null == text || "".equals(text))
return false;
Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
Matcher m = p.matcher(text);
return m.find();
}
/**
* 判断是否正整数
*
* @param number
* 数字
* @return boolean true,通过,false,没通过
*/
public static boolean isNumber(String number) {
if (null == number || "".equals(number))
return false;
String regex = "[0-9]*";
return number.matches(regex);
}
/**
* 判断是否正整数(可以为空)
*
* @param number
* 数字
* @return boolean true,通过,false,没通过
*/
public static boolean isNumberOrNil(String number) {
if(null == number) return true;
if ("".equals(number.trim())) return true;
String regex = "[0-9]*";
return number.matches(regex);
}
/**
* 判断几位小数(正数)
*
* @param decimal
* 数字
* @param count
* 小数位数
* @return boolean true,通过,false,没通过
*/
public static boolean isDecimal(String decimal, int count) {
if (null == decimal || "".equals(decimal))
return false;
String regex = "^(-)?(([1-9]{1}\\d*)|([0]{1}))(\\.(\\d){" + count
+ "})?$";
return decimal.matches(regex);
}
/**
* 判断是否是手机号码
*
* @param phoneNumber
* 手机号码
* @return boolean true,通过,false,没通过
*/
public static boolean isPhoneNumber(String phoneNumber) {
if (null == phoneNumber || "".equals(phoneNumber))
return false;
String regex = "^1[3|4|5|6|7|8|9][0-9]\\d{8}$";
return phoneNumber.matches(regex);
}
/**
* 判断身份证号格式正确性
*
* @param ID
* 身份证号
* @return boolean true,通过,false,没通过
*/
public static boolean isID(String ID) {
if (null == ID || "".equals(ID))
return false;
String regex = "^(\\d{14}[0-9a-zA-Z])|(\\d{17}[0-9a-zA-Z])$";
return ID.matches(regex);
}
/**
* 判断是否含有特殊字符
*
* @param text
* @return boolean true,通过,false,没通过
*/
public static boolean hasSpecialChar(String text) {
if (null == text || "".equals(text))
return false;
if (text.replaceAll("[a-z]*[A-Z]*\\d*-*_*\\s*", "").length() == 0) {
// 如果不包含特殊字符
return true;
}
return false;
}
/**
* 适应CJK(中日韩)字符集,部分中日韩的字是一样的
*/
public static boolean isChinese2(String strName) {
char[] ch = strName.toCharArray();
for (int i = 0; i < ch.length; i++) {
char c = ch[i];
if (isChinese(c)) {
return true;
}
}
return false;
}
private static boolean isChinese(char c) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
return true;
}
return false;
}
}
实现及调用
基本数据都定义及处理好了,我们应该建立注解与方法之间的关联,RUNTIME类型的注解在程序运行时也会被保留,我们可以利用反射,拿到具体注解参数信息,进行相关处理。
/**
* 注解解析Service
*/
public class ValidateService {
/*private static DataValid dataValid;*/
public ValidateService() {
super();
}
/**
* 解析入口
* @param object
* @throws Exception
*/
public static void valid(Object object) throws Exception{
//获取object的类型
Class<? extends Object> clazz=object.getClass();
//获取该类型声明的成员
Field[] fields=clazz.getDeclaredFields();
//遍历属性
for(Field field:fields){
//对于private私有化的成员变量,通过setAccessible来修改器访问权限
field.setAccessible(true);
validate(field,object);
//重新设置会私有权限
field.setAccessible(false);
}
}
public static void validate(Field field,Object object) throws Exception{
String description = null;
Object value = null;
DataValid dataValid = null;
//获取对象的成员的注解信息
dataValid=field.getAnnotation(DataValid.class);
value=field.get(object);
if(dataValid==null)return;
description=dataValid.description().equals("")?field.getName():dataValid.description();
/*************注解解析工作开始******************/
if(!dataValid.nullable() && dataValid.regexType() != RegexType.NUMBERORNIL){
if(value==null|| StringUtils.isBlank(value.toString())){
throw new Exception(description+"不能为空");
}
}
if(dataValid.regexType()!=RegexType.NONE){
switch (dataValid.regexType()) {
case NONE:
break;
case SPECIALCHAR:
if(RegexUtils.hasSpecialChar(value.toString())){
throw new Exception(description+"不能含有特殊字符");
}
break;
case CHINESE:
if(RegexUtils.isChinese2(value.toString())){
throw new Exception(description+"不能含有中文字符");
}
break;
case EMAIL:
if(!RegexUtils.isEmail(value.toString())){
throw new Exception(description+"邮箱地址格式不正确");
}
break;
case IP:
if(!RegexUtils.isIp(value.toString())){
throw new Exception(description+"IP地址格式不正确");
}
break;
case NUMBER:
if(!RegexUtils.isNumber(value.toString())){
throw new Exception(description+"不是数字");
}
break;
case NUMBERORNIL:
if(value == null){
break;
}
if(!RegexUtils.isNumberOrNil(value.toString())){
throw new Exception(description+"格式不正确");
}
break;
case PHONENUMBER:
if(!RegexUtils.isPhoneNumber(value.toString())){
throw new Exception("手机号格式不正确");
}
break;
case ID:
if(!RegexUtils.isID(value.toString())){
throw new Exception("身份证号格式不正确");
}
break;
default:
break;
}
}
if(!dataValid.regexExpression().equals("")){
if(value.toString().matches(dataValid.regexExpression())){
throw new Exception(description+"格式不正确");
}
}
/*************注解解析工作结束******************/
}
}
如上代码。
当然,到具体业务层,应该调用这个Service的valid方法去校验参数。
结论
可以看到,经过这样,我们可以把一些常用的校验通过这种方式封装,大大简化代码量,使业务层更注重业务。
这种也可以添加自己的通用类型,灵活性很强。
这个小小的简单工具最主要的就是利用了Java的反射机制。
以上。
今天就到这里啦,中秋节快乐~~