Skip to content

Latest commit

 

History

History
251 lines (193 loc) · 9.91 KB

系统命令节点.md

File metadata and controls

251 lines (193 loc) · 9.91 KB

环境准备

  • release-3.3分支源码

描述

官方提供了扩展机制,可以无侵入式的开发节点后端以及前端(可选),本文提供另外一种方式,直接在源码工程上增加规则节点,以增加一个执行系统命令的节点为例。

开发

阅读本文前,请先了解如何进行后端调试前端调试,建议也先阅读官方的规则节点开发

后端开发

打开源码工程,进入rule-engine-components子工程,然后进入src\main\java\org\thingsboard\rule\engine下。

规则节点工程路径

新建command文件夹,在command文件夹下新建Java文件TbSystemCommandNode用于处理消息

package org.thingsboard.rule.engine.command;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Slf4j
@RuleNode(
        type = ComponentType.EXTERNAL,
        name = "system command",
        configClazz = TbSystemCommandNodeConfiguration.class,
        nodeDescription = "run system command",
        nodeDetails = "run system command, you can config data keys which used for system environments,if success, <code>result</code> must in data",
        icon = "")
public class TbSystemCommandNode implements TbNode {

    private static final ObjectMapper mapper = new ObjectMapper();

    private TbSystemCommandNodeConfiguration config;

    private List<String> messageNamesList;
    private String command;


    @Override
    public void init(TbContext tbContext, TbNodeConfiguration configuration) throws TbNodeException {
        this.config = TbNodeUtils.convert(configuration, TbSystemCommandNodeConfiguration.class);
        messageNamesList = config.getMessageNames();
        command = config.getCommand();

    }

    @Override
    public void onMsg(TbContext ctx, TbMsg msg) {
        try {
            Map<String, Object> dataMap = genDataMap(msg);
            String result = execCommand(dataMap, command);
            //new msg and tell
            String data = "{\"result\":\"" + result + "\"}";
            TbMsg tbMsg = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(),data);
            ctx.tellSuccess(tbMsg);
            //need ack old msg
            ctx.ack(msg);
        } catch (IOException e) {
            ctx.tellFailure(msg, e);
        } catch (RuntimeException e) {
            ctx.tellFailure(msg,e);
        }
    }

    private Map<String, Object> genDataMap(TbMsg msg) throws JsonProcessingException {
        HashMap<String, Object> dataMap = new HashMap<>();
        JsonNode dataJsonNode = mapper.readTree(msg.getData());
        for (String messageName : messageNamesList) {
            JsonNode jsonNode = dataJsonNode.get(messageName);
            if (jsonNode != null && !jsonNode.isNull()) {
                if (jsonNode.isBoolean()) {
                    dataMap.put(messageName, jsonNode.asBoolean());
                } else if (jsonNode.isNumber()) {
                    dataMap.put(messageName, jsonNode.asDouble());
                } else {
                    dataMap.put(messageName, jsonNode.asText());
                }
            }
        }
        return dataMap;
    }

    private static String execCommand(Map<String, Object> dataMap, String command) {
        ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", command);
        dataMap.forEach((String key,Object obj)->{
            processBuilder.environment().put(key,String.valueOf(obj));
        });
        Process process = null;
        String result = "";
        String errorResult = "";
        try {
            process = processBuilder.start();
            //get input stream
            InputStream in = process.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            //get error stream
            InputStream error = process.getErrorStream();
            BufferedReader errorBr = new BufferedReader(new InputStreamReader(error));
            String lineStr;
            while ((lineStr = br.readLine()) != null) {
                result += lineStr + "\n";
            }
            if(result.length() > 0){
                result = result.substring(0,result.length()-1);
            }
            br.close();
            in.close();
            while ((lineStr = errorBr.readLine()) != null) {
                errorResult += lineStr + "\n";
            }
            if(errorResult.length() > 0){
                errorResult = errorResult.substring(0,errorResult.length()-1);
            }
            errorBr.close();
            error.close();
            int exitCode = process.waitFor();
            if(exitCode != 0){
                throw new RuntimeException(errorResult);
            }
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        } catch (InterruptedException e) {
            throw new RuntimeException(e.getMessage());
        } finally {
            if(process != null) {
                process.destroy();
            }
        }
        return result;
    }

    @Override
    public void destroy() {
    }
}

command文件夹下新建Java文件TbSystemCommandNodeConfiguration如下:

package org.thingsboard.rule.engine.command;

import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;

import java.util.Collections;
import java.util.List;

@Data
public class TbSystemCommandNodeConfiguration implements NodeConfiguration {

    private List<String> messageNames;
    private String command;



    @Override
    public NodeConfiguration defaultConfiguration() {
        TbSystemCommandNodeConfiguration tbSystemCommandNodeConfiguration = new TbSystemCommandNodeConfiguration();
        tbSystemCommandNodeConfiguration.setMessageNames(Collections.emptyList());
        tbSystemCommandNodeConfiguration.setCommand("");
        return tbSystemCommandNodeConfiguration;
    }
}

在左侧代码树上我们应该能看到如下结构:

规则节点工程路径

找到ThingsboardServerApplication运行工程,点击左侧菜单规则链库,随意进入一条规则链,在左侧菜单中可以找到system command规则节点。

系统命令节点

将其拖拽到画布,可以看到如下信息:

规则节点工程添加

虽然丑,但不影响正常使用。如果需要UI,接着往下看。

前端开发(可选)

如果需要界面,还需要进行以下操作:

  • TbSystemCommandNode@RuleNode注解中增加如下配置:
uiResources = {"static/rulenode/custom-nodes-config.js"},//指定前端资源文件
configDirective = "tbActionNodeSystemCommandConfig",//指定前端资源文件中的组件
M       projects/custom-nodes-config/src/lib/custom-nodes-config.module.ts
M       projects/custom-nodes-config/src/lib/locale/custom-nodes-locale.constant.ts
A       projects/custom-nodes-config/src/lib/components/action/custom-nodes-config-action.module.ts
A       projects/custom-nodes-config/src/lib/components/action/system-command-config.component.html
A       projects/custom-nodes-config/src/lib/components/action/system-command-config.component.ts

使用yarn build打包后,将custom-nodes-config.js文件从target/generated-resources/public/static拷贝至thingsboard工程rule-engine/rule-engine-components/src/main/resources/public/static/rulenode目录下

UI扩展工程路径

重启应用程序,再次拖拽系统命令节点,查看UI。

系统命令节点UI添加

实现细节较多,具体可参考我的3.3扩展分支:release-3.3-x

TIPS