【统一规则校验的设计与实现】-自定义匹配规则

        业务规则校验作为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());
    
        }

     


 

posted @ 2016-02-17 15:12  hframe  阅读(1045)  评论(0编辑  收藏  举报