zoukankan      html  css  js  c++  java
  • 【统一规则校验的设计与实现】自定义匹配规则

            业务规则校验作为IT系统中重要组成部分之一,是对数据的有效性的重要保障手段,而作为业务规则校验相关在相关的开源框架中都存在对应部分内容,比如spring与hibernate等是基于jsr-303 validation规范进行数据验证的,考虑到日常业务规则校验有时候对配置灵活性有较大要求(比如动态校验支持),本文自定义一组业务规则(字符串格式),同时相应提供校验工具类来满足业务的需要。

     
            校验过程可以理解为:被匹配字符串(string)匹配规则字符串(rule),以及最终结果(ture/false)      
     
     
    规则设计  
      关键词 格式 值约束 举例
    数据类型 typeof-类型 typeof[*] int-整数 typeof[float]
    float-浮点数  
    string-字符串  
    date-日期  
    datetime-时间  
    关系运算符 eq-等于 eq[*] 无限制 eq[你好]
    lt-小于 lt[*] 无限制 lt[100]
    gt-大于 gt[*] 无限制 gt[200]
    ne-不等于 ne[*] 无限制 ne[100]
    le-小于等于 le[*] 无限制 le[100]
    ge-大于等于 ge[*] 无限制 ge[100]
    in-在之内 in[*] 无限制 in[100,200,300,400]
    ni-不在之内 ni[*] 无限制 ni[100,200,300,400]
    bt-在之间 bt[*] 无限制 bt[100,400]
    nb-不在之间 nb[*] 无限制 nb[100,400]
    逻辑运算符 &&-且 *&&*   ne[NULL] && in[5,4,3,2,1]
    ||-或 *||*   ne[NULL] || in[5,4,3,2,1]
    !-非 !*   !(ne[NULL] || in[5,4,3,2,1])
    长度 length-长度 length[*,*]   length[100,200]
    替代符 NULL-空      
      NOW-当前时间      


      举例(!(ne[NULL] && in[5,4,3,2,1] && true)||eq[NULL]) && (typeof[datetime]&&gt[NOW])&&true

     
     
    校验算法
    假定规则:(!(ne[NULL] && in[5,4,3,2,1] && true)||eq[NULL])||(typeof[datetime]&&rl[NOW])
      算法一,替换计算法(替换原子表达式结果,最后计算只有true/false的逻辑表达式)
    1. ne[NULL] = ture;in[5,4,3,2,1] = false;eq[NULL] =true;typeof[datetime] = true; rl[NOW] = false直接替换进入表达式
    2. 计算替换后(!true && false && true) || true || (true || false )表达式值
    3. 得出结果:true
      算法二,递归计算法 (从左到右开始计算表达式,如果提前确定返回结果,不再计算后面值
    1. (ne[NULL]&&in[5,4,3,2,1]) ||(typeof[datetime]&&rl[NOW])
    2. (false&&in[5,4,3,2,1]) ||(typeof[datetime]&&rl[NOW])
    3. false||(typeof[datetime]&&rl[NOW])
    4. typeof[datetime]&&rl[NOW]
    5. false&&&&rl[NOW]
    6. false
     
    算法二相对于算法一能够减少不必要的计算,因此本文工具类选择算法二进行实现
     
    工具类
            
    public static ValidInfo validate(String data, String rule) {
    
            if (StringUtils.isBlank(rule)) {
                ValidInfo validInfo = new ValidInfo();
                validInfo.setResult(true);
                return validInfo;
            }
    
            rule = rule.replaceAll("[ ]+", "");
    
            ValidInfo validInfo = new ValidInfo();
            validInfo.setData(data);
            boolean result = excuteCascade(data, null, rule, validInfo);
            validInfo.setResult(result);
            validInfo.setResultMessage(ValidationUtil.transferResultMessage(data, validInfo.getRule()));
            return validInfo;
    
        }
    
        private static String getFirstExp(String rule) {
            Pattern pattern = Pattern.compile("[^\\(\\)!(\\|\\|)(\\&\\&)]+");
            Matcher matches = pattern.matcher(rule);
            if(matches.find()) {
                return matches.group();
            }
            return null;
        }
    
        private static boolean excuteCascade(String data, String dataType, String rule, ValidInfo validInfo) {
    
            String headExp = getFirstExp(rule);
            if(dataType == null) {
                dataType = parseDataType(headExp);
            }
            //获得表达式执行结果
            boolean headResult = excuteExp(data, dataType, headExp, validInfo);
            //将表达式替换为执行结果,只可能为true或者false
            rule = rule.replace(headExp,String.valueOf(headResult));
    
            //前后关联将该表达式前后多余的括号与否计算掉,并替换为最终结果
            System.out.print("==> rule : " + rule);
            rule = trimHeadResult(rule, headResult);
            System.out.println(" ==> : " + rule);
            headResult = Boolean.parseBoolean(getFirstExp(rule));
    
            //判断该表达式后面的逻辑运算符,根据逻辑运算符做相应替换
            Pattern groupPattern = Pattern.compile("(true|false)[\\|\\&]*");
            Matcher matches = groupPattern.matcher(rule);
            if(matches.find()) {//一定能匹配
                String headExpExt = matches.group();
    //            System.out.println(headExpExt);
                //后续没有逻辑运算符,说明已是最终结果,直接返回结果
                if(headExpExt.equals(String.valueOf(headResult))) {
                    return headResult;
                }
    
                if(headExpExt.endsWith("&&") && !headResult) {
                    int beginIndex = rule.indexOf(headExpExt);
                    int endIndex = getNextExpEndIndex(rule,headExpExt);
                    rule = rule.substring(0,beginIndex) + false + rule.substring(endIndex);
                    return excuteCascade(data, dataType, rule, validInfo);
    
                }else if(headExpExt.endsWith("||") && headResult) {
                    int beginIndex = rule.indexOf(headExpExt);
                    int endIndex = getNextExpEndIndex(rule,headExpExt);
                    rule = rule.substring(0,beginIndex) + true + rule.substring(endIndex);
                    return excuteCascade(data, dataType, rule, validInfo);
                }else {
                    rule = rule.replace(headExpExt,"");
                    return excuteCascade(data, dataType, rule, validInfo);
                }
            }
            System.out.println("该处原则上不会执行到!");
            return false;
        }
    
        private static String parseDataType(String headExp) {
            if(headExp.startsWith(RelationOperator.TYPEOF)) {
                return headExp.substring(headExp.indexOf("[") + 1, headExp.indexOf("]"));
            }
            return null;
        }
    
        private static String trimHeadResult(String rule, boolean headResult) {
            Pattern groupPattern = Pattern.compile("(true|false)\\)*");
            Matcher matches = groupPattern.matcher(rule);
            if(matches.find()) {
                String temp = matches.group();
                rule = rule.replace(temp,String.valueOf(headResult));
                int count = temp.replace("true", "").replace("false", "").length();
                groupPattern = Pattern.compile("!?(\\(!?){" + count + "}(true|false)");
                matches = groupPattern.matcher(rule);
                if(matches.find()) {
                    temp = matches.group();
                    int length = temp.replace("true", "").replace("false", "").replaceAll("\\(", "").length();
                    if(length % 2 == 1) {
                        headResult = !headResult;
                    }
                    rule = rule.replace(temp, String.valueOf(headResult));
                }
            }
            return rule;
        }
    
        private static int getNextExpEndIndex(String rule, String headExpExt) {
    
            int count = 0 ;
    
            int beginIndex = rule.indexOf(headExpExt);
            int endIndex = rule.length();
            for(int i = beginIndex; i < rule.length(); i ++) {
                if('(' == rule.charAt(i)) {
                    count ++;
                }
                if(')' == rule.charAt(i)) {
                    count --;
                }
                if(count < 0) {
                    endIndex = i;
                    break;
                }
            }
            return endIndex;
        }
    
        @SuppressWarnings("unused")
        private static String trans2RegexText(String curExp) {
            return curExp.replaceAll("\\[","\\\\\\[").replaceAll("\\]", "\\\\\\]");
        }
    
        private static boolean excuteExp(String data, String dataType, String curExp,ValidInfo validInfo) {
            if("true".equals(curExp)) {
                return true;
            }
            if("false".equals(curExp)) {
                return false;
            }
    
            String ruleType = curExp.substring(0,curExp.indexOf("["));
            String paramStr = curExp.substring(curExp.indexOf("[") + 1, curExp.indexOf("]"));
            String[] params = paramStr.split(",");
    
            boolean result =  excuteExp(data, dataType, ruleType, params);
    
            validInfo.setRule(curExp);
            validInfo.setRuleResult(result);
            System.out.println("==> data : " + data + " ; curExp : " + curExp + " ; result : " + result);
            return result;
        }
    
        private static boolean excuteExp(String data, String dataType, String ruleType, String[] params) {
    
            if(TypeOf.DATETIME.equals(dataType) || TypeOf.DATE.equals(dataType)) {
                if(ReplacementCharacter.NOW.equals(params[0])) {
                    params[0] = DateUtil.getCurrentDateString(DateUtil.DATETIME_PATTERN);
                }
            }
    
            switch (ruleType) {
                case RelationOperator.EQ://等于
                    if(ReplacementCharacter.NULL.equals(params[0])) {
                        return data == null;
                    }else {
                        return params[0].equals(data);
                    }
                case RelationOperator.LT: //小于
                    return compareLessThan(data,dataType,params);
                case RelationOperator.GT: //大于
                    return compareGreaterThan(data, dataType, params);
                case RelationOperator.NE://不等于
                    if(ReplacementCharacter.NULL.equals(params[0])) {
                        return !(data == null);
                    }else {
                        return !params[0].equals(data);
                    }
                case RelationOperator.LE://小于等于
                    return !compareGreaterThan(data, dataType, params);
                case RelationOperator.GE://大于等于
                    return !compareLessThan(data, dataType, params);
                case RelationOperator.IN://在之内
                    return Arrays.binarySearch(params,data)> -1;
                case RelationOperator.NI://不在之内
                    return Arrays.binarySearch(params,dataType)< -1;
                case RelationOperator.BT://在之间
                    return compareBetween(data, dataType, params);
                case RelationOperator.NB://不在之间
                    return !compareBetween(data, dataType, params);
                case RelationOperator.LENGTH://长度
                    int param1 = Integer.parseInt(params[0]);
                    int param2 = -1;
                    if(params.length > 1) {
                        param2 = Integer.parseInt(params[1]);
                    }
                    if(TypeOf.FLOAT.equals(data)) {
                        int precision = new BigDecimal(data).precision();
                        int intLength =  new BigDecimal(data).toBigInteger().bitLength();
                        if(param1 > intLength){
                            if(param2 != -1 && param2 < precision) {
                                return false;
                            }
                            return true;
                        }else {
                            return false;
                        }
                    }
                    return data.length() < Integer.valueOf(param1);
    
                case RelationOperator.TYPEOF://类型
                    if(TypeOf.INT.equals(dataType)) {
                        try {
                            Long.parseLong(data);
                            return true;
                        }catch (Exception e) {
                            return false;
                        }
                    }else if(TypeOf.FLOAT.equals(dataType)) {
                        try {
                            new BigDecimal(data);
                            return true;
                        }catch (Exception e) {
                            return false;
                        }
                    }else if(TypeOf.DATE.equals(dataType)) {
                            return DateUtil.stringToDate(data, DateUtil.ISO_EXPANDED_DATE_FORMAT) != null;
                    }else if(TypeOf.DATETIME.equals(dataType)) {
                            return DateUtil.stringToDate(data, DateUtil.DATETIME_PATTERN) != null;
                    }else if(TypeOf.STRING.equals(dataType)) {
                        return true;
                    }else {
                        System.out.println("【ERROR】该类型不支持:" + ruleType);
                        return false;
                    }
                default:
                    System.out.println("不支持类型:" + ruleType);
    
            }
    
    
            return false;
        }
    
        private static boolean compareBetween(String data, String dataType, String[] params) {
            if(TypeOf.INT.equals(data)) {
                return Long.parseLong(data) >= Long.parseLong(params[0])
                        && Long.parseLong(data) <= Long.parseLong(params[1]);
            }else if(TypeOf.FLOAT.equals(data)) {
                return new BigDecimal(data).compareTo(new BigDecimal(params[0])) >= 0
                        &&  new BigDecimal(data).compareTo(new BigDecimal(params[1])) <= 0;
            }else if(TypeOf.DATE.equals(data)) {
                return DateUtil.stringToDate(data, DateUtil.ISO_EXPANDED_DATE_FORMAT)
                        .compareTo(DateUtil.stringToDate(params[0], DateUtil.ISO_EXPANDED_DATE_FORMAT)) >= 0
                        && DateUtil.stringToDate(data, DateUtil.ISO_EXPANDED_DATE_FORMAT)
                        .compareTo(DateUtil.stringToDate(params[1], DateUtil.ISO_EXPANDED_DATE_FORMAT)) <= 0;
            }else if(TypeOf.DATETIME.equals(data)) {
                return DateUtil.stringToDate(data, DateUtil.DATETIME_PATTERN)
                        .compareTo(DateUtil.stringToDate(params[0], DateUtil.DATETIME_PATTERN)) >= 0
                        && DateUtil.stringToDate(data, DateUtil.DATETIME_PATTERN)
                        .compareTo(DateUtil.stringToDate(params[1], DateUtil.DATETIME_PATTERN)) <= 0;
            }else if(TypeOf.STRING.equals(data)) {
                System.out.println("【ERROR】该类型不支持!");
                return false;
            }else {
                System.out.println("【ERROR】该类型不支持!");
                return false;
            }
        }
    
        private static boolean compareGreaterThan(String data, String dataType, String[] params) {
            if(TypeOf.INT.equals(dataType)) {
                return Long.parseLong(data) > Long.parseLong(params[0]);
            }else if(TypeOf.FLOAT.equals(dataType)) {
                return new BigDecimal(data).compareTo(new BigDecimal(params[0])) > 0;
            }else if(TypeOf.DATE.equals(dataType)) {
                return DateUtil.stringToDate(data, DateUtil.ISO_EXPANDED_DATE_FORMAT)
                        .after(DateUtil.stringToDate(params[0], DateUtil.ISO_EXPANDED_DATE_FORMAT));
            }else if(TypeOf.DATETIME.equals(dataType)) {
                return DateUtil.stringToDate(data, DateUtil.DATETIME_PATTERN)
                        .after(DateUtil.stringToDate(params[0], DateUtil.DATETIME_PATTERN));
            }else if(TypeOf.STRING.equals(dataType)) {
                System.out.println("【ERROR】该类型不支持!");
                return false;
            }else {
                System.out.println("【ERROR】该类型不支持!");
                return false;
            }
        }
    
        private static boolean compareLessThan(String data, String dataType, String[] params) {
            if(TypeOf.INT.equals(dataType)) {
                return Long.parseLong(data) < Long.parseLong(params[0]);
            }else if(TypeOf.FLOAT.equals(dataType)) {
                return new BigDecimal(data).compareTo(new BigDecimal(params[0])) < 0;
            }else if(TypeOf.DATE.equals(dataType)) {
                return DateUtil.stringToDate(data, DateUtil.ISO_EXPANDED_DATE_FORMAT)
                        .before(DateUtil.stringToDate(params[0], DateUtil.ISO_EXPANDED_DATE_FORMAT));
            }else if(TypeOf.DATETIME.equals(dataType)) {
                return DateUtil.stringToDate(data, DateUtil.DATETIME_PATTERN)
                        .before(DateUtil.stringToDate(params[0], DateUtil.DATETIME_PATTERN));
            }else if(TypeOf.STRING.equals(dataType)) {
                System.out.println("【ERROR】该类型不支持!");
                return false;
            }else {
                System.out.println("【ERROR】该类型不支持!");
                return false;
            }
        }
    
        public static String transferResultMessage(String data, String rule) {
                String ruleType = rule.substring(0, rule.indexOf("["));
                String paramStr = rule.substring(rule.indexOf("[") + 1, rule.indexOf("]"));
                String[] params = paramStr.split(",");
    
                switch (ruleType) {
                    case RelationOperator.EQ://等于
                        if(ReplacementCharacter.NULL.equals(params[0])) {
                            return "只能为空";
                        }else {
                            return "只能为" + params[0];
                        }
                    case RelationOperator.LT: //小于
                        return "小于" + params[0];
                    case RelationOperator.GT: //大于
                        return "大于" + params[0];
                    case RelationOperator.NE://不等于
                        return "不等于" + params[0];
                    case RelationOperator.LE://小于等于
                        return  "小于等于" + params[0];
                    case RelationOperator.GE://大于等于
                        return  "大于等于" + params[0];
                    case RelationOperator.IN://在之内
                        return  "在" + Arrays.toString(params) + "之内";
                    case RelationOperator.NI://不在之内
                        return  "不在" + Arrays.toString(params) + "之内";
                    case RelationOperator.BT://在之间
                        return  "在" + Arrays.toString(params) + "之间";
                    case RelationOperator.NB://不在之间
                        return  "不在" + Arrays.toString(params) + "之间";
                    case RelationOperator.LENGTH://长度
                        int param1 = Integer.parseInt(params[0]);
                        int param2 = -1;
                        if(params.length > 1) {
                            param2 = Integer.parseInt(params[1]);
                        }
                        if(TypeOf.FLOAT.equals(data)) {
                            String tmp = "小数前最多" + param1 + "位";
                            if(param2 > -1) {
                                tmp += ",小数点后最多" + param2 + "位";
                            }
    
                            return tmp;
                        }
                        return "长度不能大于" + params[0] + "位";
    
                    case RelationOperator.TYPEOF://类型
                        String dataType = params[0];
                        if(TypeOf.INT.equals(dataType)) {
                            return "必须为数字类型";
    
                        }else if(TypeOf.FLOAT.equals(dataType)) {
                            return "必须为浮点类型";
                        }else if(TypeOf.DATE.equals(dataType)) {
                            return "必须为日期类型";
                        }else if(TypeOf.DATETIME.equals(dataType)) {
                            return "必须为时间类型";
                        }else if(TypeOf.STRING.equals(dataType)) {
                            return "必须为字符类型";
                        }
                    default:
                }
            return null;
        }
     
    测试
    1.   public static void main(String[] args) {
              String exp = "(!(ne[NULL] && in[5,4,3,2,1] && true)||eq[NULL]) && (typeof[datetime]&&gt[NOW])&&true";
      //        exp = "ne[NULL]";
      //        exp = "(typeof[date]) && gt[NOW]";
              exp = "(true&&typeof[date])||eq[]";
      
      
              ValidInfo result = ValidationUtil.validate("", exp);
      
              System.out.println(result.isResult() + ":"+ result.getResultMessage());
      
          }
    
    
  • 相关阅读:
    使用AChartEngine画图,项目总结
    Windows系统安装实验报告
    Linux系统安装实验报告
    vm虚拟机详细安装步骤
    L3-010. 是否完全二叉搜索树
    第13届景驰-埃森哲杯广东工业大学ACM程序设计大赛
    L2-021. 点赞狂魔
    L2-020. 功夫传人
    L2-019. 悄悄关注
    L2-017. 人以群分
  • 原文地址:https://www.cnblogs.com/hframe/p/5195415.html
Copyright © 2011-2022 走看看