本文共 11092 字,大约阅读时间需要 36 分钟。
原文作者:弥诺R
原文地址: 转载声明:转载请注明原文地址,注意版权维护,谢谢!
之前项目都是使用hibernate-validator来校验参数,但是实际上会出现一些小问题,就是校验规则都是通过注解的方式来完成,这样如果项目上线了,这个参数校验规则就没办法修改,如果出现校验规则问题,就必须修改后重新紧急上线(之前因为手机号码格式校验就出现过这个问题,因为新的号段不支持)。为了适应动态配置校验规则,在新起的项目我们就不再使用hibernate-validator校验规则,而是自己写个小功能来实现。
1、实现这种动态配置,就要能随时修改规则,并应用到实际业务逻辑中,直接在代码中写是不行的,因此这里采用数据库记录的方式是一个不错的选择;
2、需要对所有controller进入的参数校验,不能每个方法中加调用逻辑,这个必须写一个公共的方法,使用Spring AOP做切面切入所有的controller方法;3、服务的请求方式,使用这种方式,最方便的就是使用post请求,入参后,参数都在一个类中封装,拿到类,使用反射,拿出参数的参数名和参数值。基本都是以上思路,切面切入controller类中所有方法,拿到请求Dto类,利用反射技术拿出所有的参数名和参数值,从数据库中获取当前Dto类下所有参数的校验规则,依次对参数进行校验。数据库需要建三张表,校验模板表(t_template_info),校验模板规则表(t_template_rule_info),实体规则关联表(t_bean_rule_info),只说表的基本字段,需要SQL可以到码云或者git上现在原代码,项目中有datasql.sql文件中很详细,还包含初始数据。
template_id
varchar(16) NOT NULL COMMENT '模板编号',
template_desc
varchar(64) DEFAULT NULL COMMENT '模板描述',template_status
tinyint(4) NOT NULL DEFAULT '1' COMMENT '模板状态(0:不使用,1:使用)',check_level
int(11) NOT NULL COMMENT '检查优先级' rule_id
varchar(16) NOT NULL COMMENT '规则编号',
template_id
varchar(16) NOT NULL COMMENT '模板编号',rule_express
varchar(128) NOT NULL COMMENT '规则表达式',toast_msg
varchar(128) NOT NULL COMMENT '提示信息',rule_status
tinyint(4) NOT NULL DEFAULT '1' COMMENT '规则状态' bean_id
varchar(32) NOT NULL COMMENT '实体类编号',
rule_id
varchar(16) NOT NULL COMMENT '规则编号',field_name
varchar(32) NOT NULL COMMENT '字段名',field_desc
varchar(128) DEFAULT NULL COMMENT '字段描述',check_status
tinyint(4) DEFAULT '1' COMMENT '是否校验' #####pom.xml配置
org.springframework.boot spring-boot-starter-parent 1.5.6.RELEASE 1.8 1.16.10 1.1.0 1.3.0 5.1.35 3.5 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-logging com.alibaba druid-spring-boot-starter ${druid.version} org.springframework.boot spring-boot-starter-aop org.mybatis.spring.boot mybatis-spring-boot-starter ${mybatis.version} mysql mysql-connector-java ${mysql.version} org.projectlombok lombok ${lombok.version} provided org.apache.commons commons-lang3 ${commons-lang3.version} org.apache.maven.plugins maven-compiler-plugin 3.1
@Component@Slf4j@Aspectpublic class DynamicCheckAspect { @Autowired private DynamicCheckRuleService dynamicCheckRuleService; @Autowired private DynamicCheckEngine paramCheckEngine; /** * 定义切点 */ @Pointcut("execution(* com.minuor.dynamic.check.controller.*.*(..))") public void pointcut() { } /** * 定义环切 */ @Around("pointcut()") public void check(ProceedingJoinPoint joinPoint) { try { // 查询获取请求参数封装类(dto)的类名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Class [] parameterTypes = signature.getMethod().getParameterTypes(); String beanName = null; if (parameterTypes != null && parameterTypes.length > 0) { beanName = parameterTypes[0].getSimpleName(); } //查询当前beanName下字段的所有校验规则 ListmodelList = null; if (StringUtils.isNotBlank(beanName)) { modelList = dynamicCheckRuleService.queryRuleByBeanName(beanName); } if (modelList != null && !modelList.isEmpty()) { //规则分类(根据字段名分类) Map > ruleMap = new HashMap<>(); for (DynamicCheckRuleModel ruleModel : modelList) { List fieldRules = ruleMap.get(ruleModel.getFieldName()); if (fieldRules == null) fieldRules = new ArrayList<>(); fieldRules.add(ruleModel); ruleMap.put(ruleModel.getFieldName(), fieldRules); } //获取请求参数 Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0) { Object reqDto = args[0]; Field[] fields = reqDto.getClass().getDeclaredFields(); if (fields != null && fields.length > 0) { for (Field field : fields) { String fieldName = field.getName(); boolean isCheck = ruleMap.containsKey(fieldName); if (!isCheck) continue; field.setAccessible(true); List paramRules = ruleMap.get(fieldName); for (DynamicCheckRuleModel ruleModel : ruleMap.get(fieldName)) { ruleModel.setFieldValue(field.get(reqDto)); } //校验 paramCheckEngine.checkParamter(paramRules); } } } } joinPoint.proceed(); } catch (Exception e) { throw new DynamicCheckException(e.getMessage()); } catch (Throwable throwable) { throw new DynamicCheckException(throwable.getMessage()); } }}
这里首先是获取Dto的名称,然后到数据库中查询校验规则列表,如果没有,就不需要校验,中间的校验逻辑就无需再走。
@Slf4j@Componentpublic class DynamicCheckEngine { /** * 综合校验分发器 * * @param paramRules */ public void checkParamter(ListparamRules) throws Exception { paramRules.sort(Comparator.comparing(DynamicCheckRuleModel::getCheckLevel)); for (DynamicCheckRuleModel ruleModel : paramRules) { Method method = this.getClass().getMethod(ruleModel.getTemplateId(), DynamicCheckRuleModel.class); Object result = method.invoke(this, ruleModel); if (result != null) { throw new DynamicCheckException((String) result); } } } /** * 检查非空 * 模板编号:notBlank */ public String notBlank(DynamicCheckRuleModel roleModel) throws DynamicCheckException { Object fieldValue = roleModel.getFieldValue(); if (fieldValue == null) { return generateToastMsg(roleModel); } else { if ((fieldValue instanceof String) && StringUtils.isBlank((String) fieldValue)) { return generateToastMsg(roleModel); } } return null; } /** * 检查非空 * 模板编号:notNull */ public String notNull(DynamicCheckRuleModel roleModel) throws DynamicCheckException { if (roleModel.getFieldValue() == null) return generateToastMsg(roleModel); return null; } /** * 检查长度最大值 * 模板编号:lengthMax */ public String lengthMax(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String fieldValue = (String) roleModel.getFieldValue(); if (fieldValue.length() > Integer.valueOf(roleModel.getRuleExpress().trim())) { return generateToastMsg(roleModel); } return null; } /** * 检查长度最小值 * 模板编号:lengthMin */ public String lengthMin(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String fieldValue = (String) roleModel.getFieldValue(); if (fieldValue.length() < Integer.valueOf(roleModel.getRuleExpress().trim())) { return generateToastMsg(roleModel); } return null; } /** * 检查值最大值 * 模板编号:valueMax */ public String valueMax(DynamicCheckRuleModel roleModel) throws DynamicCheckException { Double fieldValue = Double.valueOf(roleModel.getFieldValue().toString()); if (fieldValue > Double.valueOf(roleModel.getRuleExpress())) { return generateToastMsg(roleModel); } return null; } /** * 检查值最小值 * 模板编号:valueMin */ public String valueMin(DynamicCheckRuleModel roleModel) throws DynamicCheckException { Double fieldValue = Double.valueOf(roleModel.getFieldValue().toString()); if (fieldValue < Double.valueOf(roleModel.getRuleExpress())) { return generateToastMsg(roleModel); } return null; } /** * 正则格式校验 * 模板编号:regex */ public String regex(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String value = (String) roleModel.getFieldValue(); if (!Pattern.matches(roleModel.getRuleExpress(), value)) { return generateToastMsg(roleModel); } return null; } /** * 构建结果信息 */ private String generateToastMsg(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String[] element = new String[]{StringUtils.isNotBlank(roleModel.getFieldDesc()) ? roleModel.getFieldDesc() : roleModel.getFieldName(), roleModel.getRuleExpress()}; String toast = roleModel.getToastMsg(); int index = 0; while (index < element.length) { String replace = toast.replace("{" + index + "}", element[index] + ""); if (toast.equals(replace)) break; toast = replace; index++; } return toast; }}
在校验方法checkParameter中,并不是去if else取判断校验模板名称,而是使用反射的方式执行方法,当然这里执行的校验的方法名要和模板名称相同,如校验非空,模板名是notBlank,那么对应的检验方法名就是notBlank。
1、这里没有列出项目中的所有代码,感觉没有必要,太冗余,主要思路和核心代码足矣,其他的代码下面会提供git和码云上的下载链接地址;
2、这里校验及基于post请求,如果你所在的项目中必须有get请求,那么就需要重新筹划一下这个校验规则如何定义,如get采用方法名,post采用Dto名称;3、这里代码作为demo展示,记得使用根据自己项目做优化;4、这里面校验的异常都是往外抛出的,实际是不会把异常抛给用户,可以在controller中做异常的统一过滤封装。码云:
gitHub:转载于:https://blog.51cto.com/simplelife/2107253