Skip to content

自定义获取发起者配置类型节点 ​

正文、配图与代码来自原稿。TB 没有现成「取设备/资产配置(Profile)字段」节点时,可自定义 originator profile fields。

1、需求 ​

需要获取设备或资产的配置(Profile)信息,官方没有对应节点,于是自定义一个 Enrichment 节点来实现。

节点名称:originator profile fields。

2、案例实操 ​

2.1、TbGetOriginatorProfileFieldsNode ​

java
package org.thingsboard.rule.engine.custom;

import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
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.BaseData;
import org.thingsboard.server.common.data.EntityFieldsData;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;

import java.util.function.Function;

import static org.thingsboard.common.util.DonAsynchron.withCallback;

@Slf4j
@RuleNode(
        type = ComponentType.ENRICHMENT,
        name = "originator profile fields",
        configClazz = TbGetOriginatorProfileFieldsConfiguration.class,
        nodeDescription = "Add Message Originator profile fields values into Message Metadata",
        nodeDetails = "Will fetch fields values specified in mapping. If specified field is not part of originator fields it will be ignored.",
        uiResources = {"static/rulenode/rulenode-core-config.js"},
        configDirective = "tbEnrichmentNodeOriginatorFieldsConfig"
)
public class TbGetOriginatorProfileFieldsNode implements TbNode {

    private TbGetOriginatorProfileFieldsConfiguration config;
    private boolean ignoreNullStrings;

    @Override
    public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
        config = TbNodeUtils.convert(configuration, TbGetOriginatorProfileFieldsConfiguration.class);
        ignoreNullStrings = config.isIgnoreNullStrings();
    }

    @Override
    public void onMsg(TbContext ctx, TbMsg msg) {
        try {
            withCallback(putEntityFields(ctx, msg),
                    i -> ctx.tellSuccess(msg),
                    t -> ctx.tellFailure(msg, t),
                    ctx.getDbCallbackExecutor());
        } catch (Throwable th) {
            ctx.tellFailure(msg, th);
        }
    }

    private ListenableFuture<Void> putEntityFields(TbContext ctx, TbMsg msg) {
        if (config.getFieldsMapping().isEmpty()) {
            return Futures.immediateFuture(null);
        } else {
            return Futures.transform(
                    findAsync(ctx, msg.getMetaData().getValue("deviceType")),
                    data -> {
                        config.getFieldsMapping().forEach((field, metaKey) -> {
                            String val = data.getFieldValue(field, ignoreNullStrings);
                            if (val != null) {
                                msg.getMetaData().putValue(metaKey, val);
                            }
                        });
                        return null;
                    },
                    MoreExecutors.directExecutor()
            );
        }
    }

    private static ListenableFuture<EntityFieldsData> findAsync(TbContext ctx, String profileName) {
        return getAsync(
                Futures.immediateFuture(
                        ctx.getDeviceProfileService()
                                .findDeviceProfileByName(ctx.getTenantId(), profileName)
                ),
                EntityFieldsData::new
        );
    }

    private static <T extends BaseData> ListenableFuture<EntityFieldsData> getAsync(
            ListenableFuture<T> future, Function<T, EntityFieldsData> converter) {
        return Futures.transformAsync(future, in -> in != null
                        ? Futures.immediateFuture(converter.apply(in))
                        : Futures.immediateFailedFuture(new RuntimeException("Entity not found!")),
                MoreExecutors.directExecutor());
    }
}

逻辑要点:

  1. 从 metadata 读取 deviceType(设备配置名称)
  2. DeviceProfileService.findDeviceProfileByName 查 Profile
  3. 按配置的 fieldsMapping 把 Profile 字段写入 metadata

上游需保证 metadata 里已有 deviceType(可用 originator fields 等节点先写入)。

2.2、TbGetOriginatorProfileFieldsConfiguration ​

java
package org.thingsboard.rule.engine.custom;

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

import java.util.HashMap;
import java.util.Map;

@Data
public class TbGetOriginatorProfileFieldsConfiguration
        implements NodeConfiguration<TbGetOriginatorProfileFieldsConfiguration> {

    private Map<String, String> fieldsMapping;
    private boolean ignoreNullStrings;

    @Override
    public TbGetOriginatorProfileFieldsConfiguration defaultConfiguration() {
        TbGetOriginatorProfileFieldsConfiguration configuration =
                new TbGetOriginatorProfileFieldsConfiguration();
        Map<String, String> fieldsMapping = new HashMap<>();
        fieldsMapping.put("name", "profileName");
        fieldsMapping.put("description", "profileDescription");
        configuration.setFieldsMapping(fieldsMapping);
        configuration.setIgnoreNullStrings(false);
        return configuration;
    }
}

默认映射:Profile 的 name → metadata profileName,description → profileDescription。

打包放入 extensions/ 或并入主工程的方式,与 自定义 entity query 节点 相同。

3、测试效果 ​

验证效果如下:

节点 / 规则链效果 1

metadata 写入效果 2

4、文件 ​

原稿附件类名:

  • TbGetOriginatorProfileFieldsNode.java
  • TbGetOriginatorProfileFieldsConfiguration.java

补充(辅) ​

  • 本实现按 Device Profile 查询;资产配置需改为 AssetProfileService 及对应 metadata 键。
  • configDirective 复用了官方 originator fields 的 UI;字段映射界面与官方类似。
  • API 名(如 findDeviceProfileByName)以你分支为准。

相关文章 ​