SpringEL:SpEL表达式文本转译

目录

1.效果展示

2.实现方法


在项目开发中,后端研发定义的规则表达式,由于掺杂字段定义、操作符、具体数值等,对业务运营人员比较晦涩难懂,不易理解,解释成本也比较高,为了更好将规则表达式的含义触达业务运营人员,将规则表达式转译为业务术语是一种比较好的方法,下面针对SpringEL编写的规则表达式如何进行转译提供了一种思路:

1.效果展示

    @Test
    public void filterConvertComplex() {
        String srcContent =
                "#data['bizRecordSource'] eq 1 and #data['paymentType'] eq 2 and ((#data['documentType'] eq 1 and #data['tradeItemType'] eq 1) or (#data['documentType'] matches '21|24' and #data['tradeItemType'] eq 6))";
        String resStr = bizItemManager.convertDisplayContent(srcContent, "");
        Assert.assertThat(resStr, is(
                "业务来源 = 交易履约 并且 付款方式 = 在线支付 并且 ((单据类型 = 普通销售 并且 商品行类型 = 普通商品) 或者 (单据类型 满足 (退货退款 或 仅退款) 并且 商品行类型 = 售后赠品未退回))"));

    }

2.实现方法

@Slf4j
public abstract class AbstractConfigConvert {

    @Resource
    private ConfigTranslateDisplayService configTranslateDisplayService;

    private Map mccFieldsMap = MccConfigUtils.getTransformDisplayOperatorFields();
    private static final Pattern MATCHES_PATTERN1 = Pattern.compile("(-?\\d{1,5})((\\|)(\\-?\\d{1,5}))+");
    private static final Pattern MATCHES_PATTERN2 = Pattern.compile("(\\-?\\d{1,5})(\\-)(\\-?\\d{1,5})");
    private static final Pattern NUM_PATTERN = Pattern.compile("(\\-?\\d{1,5})");

    // 转译引擎
    public  List convertDisplayEngine(String templateCode, List objects) {
        log.info("开始进行字符串转译,templateCode = {},items = {}", templateCode, objects);
        try {
            // 寻找转译字段并翻译
            if (!CollectionUtils.isEmpty(objects)) {
                Class clazz = objects.get(0).getClass();
                List fields = Arrays.stream(clazz.getDeclaredFields())
                        .filter(field -> !field.isSynthetic() && field.getAnnotation(SwitchDisplay.class) != null)
                        .collect(Collectors.toList());

                objects = objects.stream().peek(o -> {
                    for (Field field : fields) {
                        field.setAccessible(true);
                        SwitchDisplay switchDisplay = field.getAnnotation(SwitchDisplay.class);
                        Field srcField = ReflectionUtils.findField(clazz, switchDisplay.srcFieldName());
                        srcField.setAccessible(true);
                        String srcContent = (String) ReflectionUtils.getField(srcField, o);
                        if (StringUtils.isNotBlank(srcContent)) {
                            String resContent = convertDisplayContent(srcContent, templateCode);
                            log.info("转译结果:{}", resContent);
                            ReflectionUtils.setField(field, o, resContent);
                            // 如果不需要可以删掉
                            ReflectionUtils.setField(srcField, o, resContent);
                        }
                    }
                }).collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.warn("过滤器转译失败,templateCode:{},objects:{}", templateCode, objects);
        }
        return objects;
    }

    /**
     * 生成转译后的展示内容
     *
     * @param srcContent 原始内容
     * @param templateCode 模板
     * @return 转移后的内容
     */
    public String convertDisplayContent(String srcContent, String templateCode) {
        ExpressionParser parser = new SpelExpressionParser();
        SpelExpression spelExpression = (SpelExpression) parser.parseExpression(srcContent);
        SpelNodeImpl root = (SpelNodeImpl) spelExpression.getAST();
        return travelAndTranslate(root, templateCode, srcContent);

    }

    // MCC字段转译
    private String mccConfigTranslate(String mccField) {
        mccField = StringUtils.isNoneBlank(mccFieldsMap.get(mccField)) ? mccFieldsMap.get(mccField) : mccField;
        return mccField;
    }

    /** 遍历EL表达式语义树,生成转译后的字符串 */
    private String travelAndTranslate(SpelNodeImpl root, String templateCode, String srcContent) {
        if (root instanceof Operator && root.getChildCount() > 1) {
            Operator operator = (Operator) root;
            SpelNodeImpl left = operator.getLeftOperand();
            SpelNodeImpl right = operator.getRightOperand();
            boolean isLeftLeaf = left instanceof CompoundExpression;
            boolean isRightLeaf = right instanceof Literal || right instanceof OpMinus;
            if (isLeftLeaf && isRightLeaf) {
                // 到达叶子节点
                String leftValue = (String) ((Literal) left.getChild(1).getChild(0)).getLiteralValue().getValue();
                String rightValue =
                        right instanceof Literal ? String.valueOf(((Literal) right).getLiteralValue().getValue())
                                : right.toStringAST();
                // mcc转译操作符
                String operatorValue = mccConfigTranslate(operator.getOperatorName());
                String res = translateValue(leftValue, rightValue, templateCode, operatorValue);
                return replenishBrackets(res, left, right, srcContent);
            }
            String leftValue = travelAndTranslate((SpelNodeImpl) root.getChild(0), templateCode, srcContent);
            String rightValue = travelAndTranslate((SpelNodeImpl) root.getChild(1), templateCode, srcContent);
            return leftValue + " " + mccConfigTranslate(operator.getOperatorName()) + " " + rightValue;
        }
        return root.toStringAST();
    }

    /** 转译表达式值 */
    private String translateValue(String leftValue, String rightValue, String templateCode, String operatorValue) {
        // 获取右边表达式集合
        Map rightStringMap = regularRightExpr(rightValue);
        List rightValues = (List) rightStringMap.get("rightValues");
        String rightOperator = (String) rightStringMap.get("operator");
        rightOperator = mccConfigTranslate(rightOperator);

        List displays;
        if (CollectionUtils.isEmpty(rightValues)) {
            displays = configTranslateDisplayService.queryDisplayListByFieldName(leftValue);
        } else {
            displays = configTranslateDisplayService.queryDisplayListByFieldNameFieldVal(leftValue, rightValues);
        }
        if (!CollectionUtils.isEmpty(displays)) {
            List displayList = displays.stream()
                    .filter(config -> config.getTemplateCode().equals(templateCode)).collect(Collectors.toList());
            // 模版过滤后结果list为空,则过滤默认模版
            if (CollectionUtils.isEmpty(displayList)) {
                displays =
                        displays.stream()
                                .filter(configCenterTranslateDisplay -> StringUtils
                                        .isEmpty(configCenterTranslateDisplay.getTemplateCode()))
                                .collect(Collectors.toList());
            } else {
                displays = displayList;
            }
            if (displays.isEmpty()) {
                log.info("未查询到相关转译信息");
                return leftValue + " " + operatorValue + " " + rightValue;
            }
            leftValue = displays.get(0).getFieldDesc();

            if (!CollectionUtils.isEmpty(rightValues)) {
                if (rightValues.size() == 1) {
                    rightValue = displays.get(0).getFieldValDesc();
                } else if (displays.size() < rightValues.size()) {
                    // 查到转译结果数量小于要转译的数量
                    rightValue = processNonCompleteFound(rightValues, displays, rightOperator);
                } else {
                    rightValue = processCompleteFound(displays, rightOperator);
                }
            }
        }
        return leftValue + " " + operatorValue + " " + rightValue;
    }

    /** 补充表达式原有括号 */
    private String replenishBrackets(String rawRes, SpelNodeImpl left, SpelNodeImpl right, String srcContent) {
        StringBuilder res = new StringBuilder(rawRes);
        // 添加左括号
        for (int i = left.getStartPosition() - 1; i > -1; i--) {
            if (srcContent.charAt(i) != '(') {
                break;
            }
            res.insert(0, '(');
        }
        // 添加右括号
        for (int i = right.getEndPosition(); i < srcContent.length(); i++) {
            if (srcContent.charAt(i) != ')') {
                break;
            }
            res.append(')');
        }
        return res.toString();
    }

    /** 处理未全部查询到转译数据的情况 */
    private String processNonCompleteFound(List values, List displays,
            String operator) {
        operator = mccConfigTranslate(operator);
        if (CollectionUtils.isEmpty(displays)) {
            return StringUtils.join(values, " " + operator + " ");
        }
        List displayValues =
                displays.stream().map(ConfigCenterTranslateDisplay::getFieldVal).collect(Collectors.toList());
        values.removeAll(displayValues);
        String res = joinValue(displays, operator);
        return "(" + res + " " + operator + " " + StringUtils.join(values, " " + operator + " ") + ")";

    }

    /** 处理全部查询到转译数据的情况 */
    private String processCompleteFound(List displays, String operator) {
        return "(" + joinValue(displays, operator) + ")";
    }

    /** 统一转换右侧表达式 */
    private Map regularRightExpr(String rightValue) {
        List rightValues = new ArrayList<>();
        String operator = "|";

        Matcher m1 = MATCHES_PATTERN1.matcher(rightValue);
        Matcher m2 = MATCHES_PATTERN2.matcher(rightValue);
        if (m1.find()) {
            // 匹配x1|x2|x3格式
            Matcher numMatcher = NUM_PATTERN.matcher(rightValue);
            while (numMatcher.find()) {
                rightValues.add(numMatcher.group());
            }
        } else if (m2.find()) {
            // 匹配[a-b]格式
            int leftBoundaryValue = Integer.parseInt(m2.group(1));
            int rightBoundaryValue = Integer.parseInt(m2.group(3));
            for (int i = leftBoundaryValue; i <= rightBoundaryValue; i++) {
                rightValues.add(String.valueOf(i));
            }
        } else {
            rightValues.add(rightValue);
        }
        Map map = new HashMap<>();
        map.put("rightValues", rightValues);
        map.put("operator", operator);
        return map;
    }

    /** 拼接转译值及操作符 返回值示例: 货到付款 或 在线支付 或 帐期支付 */
    private String joinValue(List displays, String operator) {
        return StringUtils.join(
                displays.stream().map(ConfigCenterTranslateDisplay::getFieldValDesc).collect(Collectors.toList()),
                " " + operator + " ");
    }
}

关注2点:

1.操作符转译名称映射配置在配置中心上;

2.字段转译映射配置在Mysql数据库中(包括字段名称描述以及关联的字段枚举值描述)

由上,通过对SpringEL表达式抽象语法树的遍历,完成对规则表达式的转译;

你可能感兴趣的:(spring,java,后端)