Jexl动态代码执行逻辑引擎

2021-03-10  本文已影响0人  一击必中

一、前情提要

二、要做什么?

我们要实现的功能是,场景自动化功能。

三、怎么做?

1、难点
2、表结构
CREATE TABLE `scene_automation` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `scene_name` varchar(200) DEFAULT NULL COMMENT '场景名称',
  `store_code` varchar(10) DEFAULT NULL COMMENT '门店编号',
  `match_type` varchar(10) DEFAULT NULL COMMENT '条件匹配模式(ANY 任意满足 ALL 全部满足)',
  `background` varchar(200) DEFAULT NULL COMMENT '背景',
  `scene_state` varchar(10) DEFAULT NULL COMMENT '场景状态(ON 开启 OFF 关闭)',
  `conditions` text COMMENT '条件',
  `actions` text COMMENT '条件',
  `preconditions` text COMMENT '前置条件',
  `create_no` varchar(50) DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_no` varchar(50) DEFAULT NULL COMMENT '更新人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='场景自动化表';
3、数据结构
{
        "sceneName": "场景自动化测试",
        "storeCode": "0009",
        "matchType": "ALL",
        "background": "#ffffff",
        "sceneState": "ON",
        "conditionMessages": [
            {
                "entityId": "6cb6a428adede6b580jqqb",
                "entityType": "DEVICE",
                "orderNum": 1,
                "display": {
                    "code": "temperature",
                    "operator": "MORE",
                    "value": 25
                }
            },
            {
                "entityId": "6cb6a428adede6b580jqqb",
                "entityType": "DEVICE",
                "orderNum": 2,
                "display": {
                    "code": "humidity",
                    "operator": "MORE",
                    "value": 30
                }
            }
        ],
        "preconditionMessages": [
            {
                "display": {
                    "start": "00:00",
                    "end": "23:59",
                    "loops": "1111110"
                },
                "type": "TIME_CHECK"
            }
        ],
        "actionMessages": [
            {
                "executor": "socket",
                "entityId": "6c30fb6e4c962c96b2asgo",
                "property": {
                    "switchSocket": true
                }
            },
            {
                "executor": "sendDing",
                "property": {
                    "message": "钉钉消息发送测试"
                }
            }
        ]
    }
4、引入jexl逻辑引擎
    <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-jexl</artifactId>
            <version>2.1.1</version>
        </dependency>

Java表达式语言--Java Expression Language(JEXL),这是Apache开源的一个jar包,旨在促进在用Java编写的应用程序和框架中,实现动态和脚本功能,JEXL基于JSTL表达式语言的一些扩展实现了表达式语言,支持shell脚本或ECMAScript(js)中的大多数构造.

5、条件-核心逻辑封装(关系运算符)
    /**
     * 关系运算符
     *
     * @param conditionOperationMessage
     * @return boolean
     */
    private static boolean relationalCal(ConditionOperationMessage conditionOperationMessage) {
        JexlEngine engine = new JexlEngine();
        JexlContext context = new MapContext();
        // 构建表达式
        String command = " if ( a " +
                conditionOperationMessage.getOperation() +
                conditionOperationMessage.getValue() +
                " ) { return true } else { return false } ";
        // 设置变量值
        context.set("a", conditionOperationMessage.getParam());
        // 执行计算结果
        boolean result = (boolean) engine.createExpression(command).evaluate(context);
        log.info("关系运算结果 [ {} {} {} ] --> [{}] ", conditionOperationMessage.getParam(), conditionOperationMessage.getOperation(), conditionOperationMessage.getValue(), result);
        return result;
    }
6、条件-逻辑运算表达式
 /**
     * 逻辑或运算核心方法
     *
     * @param conditionOperationMessageList
     * @return boolean
     */
    private static boolean logicOrCoreCal(List<ConditionOperationMessage> conditionOperationMessageList) {
        log.info("========开始进入逻辑或运算=====");
        for (ConditionOperationMessage conditionOperationMessage : conditionOperationMessageList) {
            if (relationalCal(conditionOperationMessage)) {
                log.info("========满足逻辑或运算=====");
                return true;
            }
        }
        log.info("========不满足逻辑或运算=====");
        return false;
    }

    /**
     * 逻辑或运算核心方法
     *
     * @param conditionOperationMessageList
     * @return boolean
     */
    private static boolean logicAndCoreCal(List<ConditionOperationMessage> conditionOperationMessageList) {
        log.info("========开始进入逻辑且运算=====");
        for (ConditionOperationMessage conditionOperationMessage : conditionOperationMessageList) {
            if (!relationalCal(conditionOperationMessage)) {
                log.info("========不满足逻辑且运算=====");
                return false;
            }
        }
        log.info("========满足逻辑且运算=====");
        // 校验是否全部为true
        return true;
    }

7、动作-动态方法执行

    /**
     * 执行动作
     *
     * @param actionMessageList
     */
    public static void action(List<ActionMessage> actionMessageList) {
        JexlEngine engine = new JexlEngine();
        JexlContext context;
        if (!CollectionUtils.isEmpty(actionMessageList)) {
            for (ActionMessage actionMessage : actionMessageList) {
                context = new MapContext();
                String command = "ActionTool." + actionMessage.getExecutor() + "(property)";
                context.set("ActionTool", ActionTool.class);
                context.set("property", actionMessage.getProperty());
                boolean result = (boolean) engine.createExpression(command).evaluate(context);
                log.info("方法[{}]执行结果[{}]", actionMessage.getExecutor(), result);
            }
        }
    }
 /**
     * 插座控制
     *
     * @param property
     */
    public static boolean socket(ActionPropertyMessage property) {
        log.info("插座控制[{}]", property.getSwitchSocket());
        autoService.testService();
        return true;
    }

    /**
     * 插座控制
     *
     * @param property
     */
    public static boolean sendDing(ActionPropertyMessage property) {
        log.info("发送钉钉消息[{}]", property.getMessage());
        return true;
    }

    /**
     * 延迟
     *
     * @param property
     */
    public static boolean delay(ActionPropertyMessage property) {
        log.info("延时控制 时[{}] 分[{}] 秒[{}] ", property.getDelayHour(), property.getDelayMinutes(), property.getDelaySeconds());
        return true;
    }

    /**
     * 空调控制
     *
     * @param property
     */
    public static boolean airCondition(ActionPropertyMessage property) {
        log.info("空调控制 类型[{}] 值[{}]", property.getAirConditionType().getStr(), property.getAirConditionValue());
        return true;
    }
调用入口
 /**
     * 调用入口
     *
     * @param code  触发条件
     * @return SimpleMessage 
     */
    @Override
    public SimpleMessage imitate(String code) {
        // 获取正在开启的活动
        List<SceneAutomation> sceneAutomationList = sceneAutomationDao.getSuitableScene(code);
        // 校验是否存在该场景
        if (CollectionUtils.isEmpty(sceneAutomationList)) {
            return new SimpleMessage(ErrorCodeEnum.NO, "查询不到该场景");
        }
        // 遍历场景
        for (SceneAutomation sceneAutomation : sceneAutomationList) {
            // 判断前置条件
            List<PreconditionMessage> preconditionMessageList = JSON.parseArray(sceneAutomation.getPreconditions(), PreconditionMessage.class);
            if (!PreconditionTool.judge(preconditionMessageList)) {
                log.info("任务[{}] 不满足前置条件", sceneAutomation.getSceneName());
                continue;
            }
            // 解析条件
            List<ConditionMessage> conditionMessages = JSON.parseArray(sceneAutomation.getConditions(), ConditionMessage.class);
            // 条件执行封装列表
            List<ConditionOperationMessage> conditionOperationMessageList = new ArrayList<>();
            // 获取条件值
            conditionMessages.forEach(conditionMessage -> {
                // 设备类型
                if (ConditionEntityTypeEnum.DEVICE.equals(conditionMessage.getEntityType())) {
                    // 条件值
                    Object param = sceneAutomationDao.getDeviceParam(conditionMessage.getDisplay().getCode(), conditionMessage.getEntityId());
                    log.info("设备码[{}]  类型[{}] 当前值[{}] ", conditionMessage.getEntityId(), conditionMessage.getDisplay().getCode(), param);
                    conditionOperationMessageList.add(ConditionOperationMessage.builder()
                            .param(param)
                            .operation(conditionMessage.getDisplay().getOperator().getStr())
                            .value(conditionMessage.getDisplay().getValue())
                            .build());
                } else {
                    // TODO 待定
                }
            });
            // 判断条件是否执行完成
            if (CollectionUtils.isEmpty(conditionOperationMessageList)
                    || !ConditionTool.judge(conditionOperationMessageList, sceneAutomation.getMatchType())) {
                log.info("任务[{}] 不满足条件", sceneAutomation.getSceneName());
                continue;
            }
            // 执行动作
            List<ActionMessage> actionMessages = JSON.parseArray(sceneAutomation.getActions(), ActionMessage.class);
            // 执行
            ActionTool.action(actionMessages);
        }
        return new SimpleMessage(ErrorCodeEnum.OK);
    }

三、还有什么要补充的?

1、mysql 查询 json格式数据
    /**
     * 获取符合条件的场景
     *
     * @param code 条件值
     * @return List<SceneAutomation>
     */
    @Select("SELECT " +
            " id, " +
            " scene_name, " +
            " store_code, " +
            " match_type, " +
            " background, " +
            " scene_state, " +
            " conditions, " +
            " preconditions, " +
            " actions  " +
            "FROM " +
            " `scene_automation`  " +
            "WHERE " +
            " scene_state = 'ON'  " +
            " AND JSON_CONTAINS( conditions, JSON_OBJECT( 'display', JSON_OBJECT( 'code', #{code} ) ) )")
    List<SceneAutomation> getSuitableScene(@Param("code") String code);

    /**
     * 获取参数值
     *
     * @param key       key
     * @param deviceUid 设备码
     * @return Object
     */
    @Select(" SELECT " +
            "device_detail ->> '$.${key}' as 'param' " +
            "from store_devices WHERE " +
            "device_uid = #{deviceUid} ")
    Object getDeviceParam(@Param("key") String key, @Param("deviceUid") String deviceUid);
2、static 穿透 service 执行
  /**
     * static穿透service执行方法
     */
    final
    AutoService innerAutoService;
    static AutoService autoService;


    public ActionTool(AutoService innerAutoService) {
        this.innerAutoService = innerAutoService;
    }

    /**
     * static穿透service初始化
     */
    @PostConstruct
    public void init() {
        autoService = innerAutoService;
    }

四、这玩意儿的意义是什么?

1、我们在遇到特殊问题的时候,要打破固有的编程思想,根据自己的业务需求找到最优的解决方案。
2、不要妥协,要让自己的程序变得优雅。

上一篇下一篇

猜你喜欢

热点阅读