ThingsBoard源码解析-规则引擎

news2025/8/10 5:37:26

描述

规则引擎是Thingsboard的核心部分,基于Actor编程模型,类似事件驱动;

每个actor都有自己的消息队列(mailBox)保存接收到的消息

actor可以创建actor

actor可以将消息转发给其他actor

分析

Actor模型实现

系统中与Actor模型相关类都在工程common/actor下,几个核心类说明如下:

  • TbActorSystem Actor系统接口,Actor系统类实现该接口
  • TbActor Actor接口,所有Actor需直接或间接实现该接口
  • TbActorCreator Actor创建接口,所有Actor创建器直接或间接实现该接口
  • TbActorRef Actor句柄接口,使用TbActorCreator创建Actor后返回此句柄,通常指向Actor的邮箱。
  • TbActorCtx Actor上下文接口,继承TbActorRef接口。
  • AbstractTbActor 抽象Actor,实现TbActor接口。
  • DefaultTbActorSystem Actor系统,用于Dispatcher的创建删除、Actor的创建查找和Actor之间消息传递等。
  • Dispatcher 调度器,用于调度Actor的创建或消息分发。
  • TbActorMailbox 邮箱,实现TbActorCtx接口,指向某个actor,同时存储消息到队列并使用调度器处理队列中的消息。

类继承关系图如下:

TbActorMailBox中有两个队列用于存储待处理的消息:

两个队列的类型是ConcurrentLinkedQueue,非阻塞并发队列,减少了线程切换,性能好。

基于以上类,实现一个Actor代码如下:

Plain Text

public class TbMyActor extends AbstractTbActor {

    public TbMyActor() {

    }

   

    @Override

    public boolean process(TbActorMsg msg) {

        //process some message

        return false;

    }

    /**

     * use this to create Actor

     */

    public static class ActorCreator implements TbActorCreator {

        public ActorCreator() {

        }

        @Override

        public TbActorId createActorId() {

            return new TbEntityActorId(new TenantId(EntityId.NULL_UUID));

        }

        @Override

        public TbActor createActor() {

            return new TbMyActor();

        }

    }

}

类继承关系如下:

引擎初始化

回到正题,DefaultActorService构造一个使用Actor模型系统的规则引擎,分为两个阶段:

阶段1:类初始化,方法:initActorSystem

Plain Text

//DefaultActorService 84

log.info("Initializing actor system.");

actorContext.setActorService(this);

TbActorSystemSettings settings = new TbActorSystemSettings(actorThroughput, schedulerPoolSize, maxActorInitAttempts);

//新建DefaultTbActorSystem对象

system = new DefaultTbActorSystem(settings);

//创建线程池用于后续异步处理消息

system.createDispatcher(APP_DISPATCHER_NAME, initDispatcherExecutor(APP_DISPATCHER_NAME, appDispatcherSize));

system.createDispatcher(TENANT_DISPATCHER_NAME, initDispatcherExecutor(TENANT_DISPATCHER_NAME, tenantDispatcherSize));

system.createDispatcher(DEVICE_DISPATCHER_NAME, initDispatcherExecutor(DEVICE_DISPATCHER_NAME, deviceDispatcherSize));

system.createDispatcher(RULE_DISPATCHER_NAME, initDispatcherExecutor(RULE_DISPATCHER_NAME, ruleDispatcherSize));

actorContext.setActorSystem(system);

//创建整个Actor模型的根

appActor = system.createRootActor(APP_DISPATCHER_NAME, new AppActor.ActorCreator(actorContext));

actorContext.setAppActor(appActor);

//创建状态Actor,也是一个根,用于统计状态

TbActorRef statsActor = system.createRootActor(TENANT_DISPATCHER_NAME, new StatsActor.ActorCreator(actorContext, "StatsActor"));

actorContext.setStatsActor(statsActor);

log.info("Actor system initialized.");

默认会将全部租户下的全部规则节点加载到内存中;可通过配置【TB_SERVICE_TENANT_ID 指定服务专用租户,即只会加载该租户下的规则节点

阶段2:应用准备完成,方法为onApplicationEvent

Plain Text

//DefaultActorService 120

log.info("Received application ready event. Sending application init message to actor system");

//给顶层AppActor邮箱发送消息AppInitMsg

appActor.tellWithHighPriority(new AppInitMsg());

AppActor收到消息后,在doProcess方法中进行处理

Plain Text

//AppActor 67

if (!ruleChainsInitialized) {

  //初始化多个租户Actors

  initTenantActors();

  ruleChainsInitialized = true;

  if (msg.getMsgType() != MsgType.APP_INIT_MSG) {

    log.warn("Rule Chains initialized by unexpected message: {}", msg);

  }

}

TenantActorinit阶段进行租户下规则链RuleChainActor创建

Plain Text

// TenantActor 88

if (isRuleEngineForCurrentTenant) {

    try {

        if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {

            if (apiUsageState.isReExecEnabled()) {

                log.info("[{}] Going to init rule chains", tenantId);

                //规则链节点初始化

                initRuleChains();

            } else {

                log.info("[{}] Skip init of the rule chains due to API limits", tenantId);

            }

        } else {

            isRuleEngineForCurrentTenant = false;

        }

    } catch (Exception e) {

        cantFindTenant = true;

    }

}

RuleChainActorinit阶段,创建RuleChainActorMessageProcessor并调用其start,进行规则节点RuleNodeActor的创建

Plain Text

//RuleChainActorMessageProcessor 100

if (!started) {

    RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);

    if (ruleChain != null) {

        List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);

        log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());

        // Creating and starting the actors;

        for (RuleNode ruleNode : ruleNodeList) {

            log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);

            //创建规则节点

            TbActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);

            //加入到节点集合中

            nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));

        }

        //初始化节点路由

        initRoutes(ruleChain, ruleNodeList);

        started = true;

    }

} else {

    onUpdate(context);

}

RuleNodeActorinit阶段,创建RuleNodeActorMessageProcessor并调用其start,进行TbNode的创建

Plain Text

//RuleNodeActorMessageProcessor 62

//根据类型创建TbNode实例

tbNode = initComponent(ruleNode);

if (tbNode != null) {

    state = ComponentLifecycleState.ACTIVE;

}

当与设备相关消息开始上传时,TenantActor还会初始化DeviceActor

Plain Text

//TenantActor 153

case TRANSPORT_TO_DEVICE_ACTOR_MSG:

    //传递消息给DeviceActor,如果没有则创建

    onToDeviceActorMsg((DeviceAwareMsg) msg, false);

    break;

形成的结构如下:

消息传输

完成规则初始化后,规则引擎接受消息传输,以普通设备上传时序数据并存储为例,规则引擎处理的核心处理流程如下:

Plain Text

//ActorSystemContext 561

//传递消息到AppActor邮箱中

appActor.tell(tbActorMsg);

//AppActor 84

//根据消息类型转换消息为QueueToRuleEngineMsg,调用onQueueToRuleEngineMsg方法

onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);

//AppActor 140

//创建或获取租户Actor邮箱,并传递消息

getOrCreateTenantActor(msg.getTenantId()).tell(msg);

//TenantActor 150

//根据消息类型转换消息为QueueToRuleEngineMsg,调用onQueueToRuleEngineMsg方法

onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);

//TenantActor 185

//获取根规则链Actor邮箱,并传递消息

getRootChainActor().tell(msg);

//RuleChainActor 55

//根据消息类型转换消息为QueueToRuleEngineMsg,使用处理器RuleChainActorMessageProcessor处理消息

processor.onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);

//RuleChainActorMessageProcessor 215

//如果消息中未指定规则节点,targetCtx为第一个节点邮箱,否则为指定节点邮箱

pushMsgToNode(targetCtx, msg, "");

//RuleChainActorMessageProcessor 338

//新建RuleChainToRuleNodeMsg消息,并向规则节点邮箱发送消息

nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, ruleChainName, nodeCtx), msg, fromRelationType));

//RuleNodeActor 60

//根据消息类型转换消息为RuleChainToRuleNodeMsg,调用onRuleChainToRuleNodeMsg处理该消息

onRuleChainToRuleNodeMsg((RuleChainToRuleNodeMsg) msg);

//RuleNodeActor 94

//使用处理器RuleNodeActorMessageProcessor处理消息

//processor.onRuleChainToRuleNodeMsg(msg);

//RuleNodeActorMessageProcessor 136

//调用规则节点实例处理消息

tbNode.onMsg(msg.getCtx(), msg.getMsg());

//TbDeviceProfileNode -> TbMsgTypeSwitchNode 消息处理流程

//TbDeviceProfileNode 135

//获取或创建设备状态DeviceState,处理消息

deviceState.process(ctx, msg);

//DeviceState 140

//处理遥测数据

//stateChanged = processTelemetry(ctx, msg);

//DeviceState 260

//调用上下文tellSuccess方法处理消息

//ctx.tellSuccess(msg);

//DefaultTbContext 103

//调用tellNext处理关联关系为SUCCESS的消息。

tellNext(msg, Collections.singleton(TbRelationTypes.SUCCESS), null);

//DefaultTbContext 121

//新建消息RuleNodeToRuleChainTellNextMsg,并传递给所在规则链Actor邮箱。

nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));

//RuleChainActor 58

//根据消息类型转换消息为RuleNodeToRuleChainTellNextMsg,使用处理器RuleChainActorMessageProcessor处理消息

processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);

//RuleChainActorMessageProcessor 252

//根据消息来源编号originatorNodeId(一般是上一个节点的Id)和关联类型过滤关联联系

List<RuleNodeRelation> relations = nodeRoutes.get(originatorNodeId).stream()

                    .filter(r -> contains(relationTypes, r.getType()))

                    .collect(Collectors.toList());

//RuleChainActorMessageProcessor 282

//如果关联关系为1,调用pushToTarget到关联目标实体(一般是下一个节点,也有可能是下一个规则链)

pushToTarget(tpi, msg, relation.getOut(), relation.getType());

//RuleChainActorMessageProcessor 304

//获取关联目标实体的Actor邮箱,调用pushMsgToNode方法处理消息

pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg, fromRelationType);

//RuleChainActorMessageProcessor 338

//新建消息RuleChainToRuleNodeMsg,向目标实体Actor邮箱发送该消息

nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, ruleChainName, nodeCtx), msg, fromRelationType));

//TbMsgTypeSwitchNode 101

//计算relationType(这里是`Post telemetry`),调用上下文tellNext处理消息

ctx.tellNext(msg, relationType);

//TbMsgTypeSwitchNode-> TbMsgTimeseriesNode 消息处理流程类似

核心流程时序图(省掉一些非核心时序)如下:

为了防止蒙圈,提供一张核心流程示意图:

规则链加载

应用启动时,从DefaultActorService的PostConstruct的initActorSystem方法开始:

通过类DefaultActorSystem的createActor方法创建AppActor,该方法时创建Actor的实现,其他Actor也是通过该方法创建的。创建actor之后会执行actor中的initActor方法

AppActor的init方法:

定时向AppActor的mail box中发送一条消息;

而tryProcessQueue方法最终会执行actor的process方法

在创建租户Actor后,也执行TanentActor的init方法:

同样创建RuleChainActor后也会执行init方法:

进入processor.start方法:

然后查看RuleNodeActor的init方法,发现和RuleChainActor是一样的,定义在抽象类ComponentActor中,顺着查看到RuleNodeActorMessageProcessor的start方法:

Actor System

服务启用后,只有一个AppActor,但会包含所有的TalentActor;一个TalentActor有且只有一条RootRuleChainActor,同时可以有多条RuleChainActor;每个RuleChainActor可以包含多个RuleNodeActor和RuleChainActor

 

Actor执行

Actor的执行逻辑定义在process方法中:

通过TbActorRef类的tell方法将消息传递给Actor执行:

TIPS

  • Actor模型 wiki
  • 需要注意,在微服务架构下,Core服务和RuleEngine服务都拥有自己的Actor模型实现,Core包含AppActorTenantActor以及DeviceActorRuleEngine包含AppActorTenantActor RuleEngine以及RuleNodeActor

一条遥测数据的旅程

在TB租户下默认有一条【根规则链】,其中定义了默认情况下数据的处理流程,遥测数据也不例外,无论遥测数据是通过哪种协议还是通过API,都会进入到根规则链,默认流程如下:

遥测数据以此通过每个规则节点的处理,解析来我们结合代码深入了解一下。

根规则链的配置文件是:root_rule_chain.json。

提前设置告警规则,通过MQTTX模拟上报设备遥测数据。

物模型过滤(Model Filter Node

JSON

{

  "additionalInfo": {

    "description": "根据设备的物模型对报文进行测点/属性过滤",

    "layoutX": 310,

    "layoutY": 200

  },

  "type": "org.thingsboard.rule.engine.transform.TbTransformAndModelFilterMsgNode",

  "name": "Model filter node",

  "debugMode": false,

  "configuration": {

  }

}

在根规则链配置文件中可以看到【物模型过滤】规则节点的实现类【TbTransformAndModelFilterMsgNode】,该类集成【TbAbstractTransformNode】,属于转换类节点

规则节点的入口方法是onMsg:

TypeScript

@Override

public void onMsg(TbContext ctx, TbMsg msg) {

    boolean ifMsgTypeFilter = msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())||msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name());

    if(!(ifMsgTypeFilter && EntityType.DEVICE.name().equals(msg.getOriginator().getEntityType().name()))){

        ctx.tellSuccess(msg);

        return;

    }

    withCallback(transform(ctx, msg),

            m -> transformSuccess(ctx, msg, m),

            t -> transformFailure(ctx, msg, t),

            ctx.getDbCallbackExecutor());

}

如上图示,消息进入onMsg,可以看到消息类型是:POST_TELEMETRY_REQUEST

onMsy主要调用了transform方法:

TypeScript

@Override

protected ListenableFuture<List<TbMsg>> transform(TbContext ctx, TbMsg msg) {

    //ctx.logJsEvalRequest();

    //return jsEngine.executeUpdateAsync(msg);

    return Futures.immediateFuture(Arrays.asList(msg));

}

并没有任何转换,其中被注释的copy自【TbTransformMsgNode】消息脚本转换节点

所以这个转换器目前没有开发完,也可能是测试着玩的,先忽略。

设备配置文件(DeviceProviceNode)-生成告警

规则节点实现类是org.thingsboard.rule.engine.profile.TbDeviceProfileNode

作用是根据设备配置完成特殊处理,比如给设备定义了报警策略,则该节点则会校验报警条件,如果满足则生成告警。

消息进入onMsg方法后,根据消息类型不同执行不同的处理,而遥测数据将用于更新设备状态(DeviceState)

进入deviceState.process方法,然后根据消息类型执行processTelemetry方法

processTelemetry方法中,会遍历设备配置(deviceProfile)中的告警规则(alarmSetting)

进入alarmState.process方法

可以看到执行createOrClearAlarms方法,即生成或者清楚告警

由于我提前设置了规则:当亮度(brightness)大于50时则告警,而当前遥测数据中brightness=100,因此evalResult的结果是TRUE

然后继续执行calculateAlarmResult方法,该方法中会调用alarmService保存到数据库中alarm表

消息类型路由器(Message Type Switch)

规则节点实现类是:org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode

从根规则链图中可以看到不同的消息类型和relationType匹配,会路由到不同的规则节点,有5个对应的规则节点:

然后执行 DefaultTbContext#tellNext 方法,然后构造RuleNodeToRuleChainTellNextMsg消息并传入ChainActor

这里的ChainActor的类就是RuleChainActor,进入doProcess方法

进入该方法后,会查询到后面关联的节点有5个:

过滤出目标的关联节点,并转发到保存时序数据的规则节点

保存时序数据(Save Timeseries)

规则节点实现类是:org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode

消息进入onMsg方法,最终调用时序数据库Service保存数据

保存时序数据过程中,还会触发websocket更新,详情参考:TingsBoard源码解析-WebSocket_imagine0623的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/33937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

戴尔科技集团通过多云数据保护和安全创新增强网络弹性

中国北京——2022年11月18日 Dell PowerProtect Data Manager软件更新和新一代备份一体机可帮助客户提高运维安全和网络弹性 戴尔多云数据保护解决方案利用内置的安全运维功能加速采用零信任原则 2022年全球数据保护指数(GDPI)调查结果公布 戴尔科技集团(NYSE:Dell)扩大其在数据…

OA系统,有效提升企业办公效率落实执行力

企业管理的成功将最终取决于企业的执行情况&#xff0c;只要有良好的经营管理&#xff0c;管理系统&#xff0c;一个好的领导者&#xff0c;充分调动员工的积极性&#xff0c;将能最大限度的管理执行力。 OA协同办公系统提供了工作流和协同工作互补结合。工作流程严格规定了工作…

PCB铺铜的优点与缺点

PCB设计铺铜是电路板设计的一个非常重要的环节。 什么是PCB铺铜&#xff0c;就是将PCB上无布线区域闲置的空间用固体铜填充。铺铜的意义在于减小地线阻抗&#xff0c;提高抗干扰能力;降低压降&#xff0c;提高电源效率&#xff0c;与地线相连&#xff0c;还可以减小环路面积。 …

基于蛙跳算法求解简单调度问题附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

python与Electron联合编程记录之九(Electron与Flask联合编程实现)

前面铺垫了这么多&#xff0c;这一节就要真正的实现Electron与python联合编程。这一节我通过加法器这个简单的例子来演示如何真正实现Electron和Flask联合编程。 1、安装Axios包 在终端工具选项卡中输入如下命令安装Axios包: npm i --save-dev axios2、项目结构 项目结构如下…

C语言源代码系列-管理系统之家庭财务小管家

往期文章分享点击跳转>《导航贴》- Unity手册&#xff0c;系统实战学习点击跳转>《导航贴》- Android手册&#xff0c;重温移动开发 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过…

COLMAP输出的文件类型(bin, txt)

默认情况下&#xff0c;COLMAP使用二进制文件格式(bin&#xff0c;机器可读&#xff0c;速度速)来存储稀疏模型。此外&#xff0c;COLMAP也可以将稀疏模型存储为文本文件(txt&#xff0c;人类可读&#xff0c;速度慢)。在这两种情况下&#xff0c;模型导出的信息被分为关于相机…

【吴恩达机器学习笔记】三、矩阵

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

Cygwin安装

Cygwin是一个在Windows平台上运行的类UNIX模拟环境&#xff0c;在其提供的命令行界面中完美地兼容了Windows和Linux的命令行指令&#xff0c;安装和使用教程很容易百度到&#xff0c;可从官网下载安装包&#xff1a;Cygwin官网。安装步骤如下所示&#xff0c;也可自行百度安装方…

web网页设计实例作业HTML+CSS+JavaScript蔬菜水果商城购物设计

常见网页设计作业题材有 个人、 美食、 公司、 学校、 旅游、 电商、 宠物、 电器、 茶叶、 家居、 酒店、 舞蹈、 动漫、 服装、 体育、 化妆品、 物流、 环保、 书籍、 婚纱、 游戏、 节日、 戒烟、 电影、 摄影、 文化、 家乡、 鲜花、 礼品、 汽车、 其他等网页设计题目, A…

测试工程师们需要认真思考的几个问题

一、如何保证合适的测试用例覆盖率 测试是一个经济学的概念&#xff0c;不计成本的测试最终会受到市场的惩罚和用户的抛弃。所以为了体现这种明智&#xff0c;测试用例设计所追求的目标不是100%覆盖&#xff0c;而应该是均匀覆盖。让测试用例均匀覆盖功能点的理念&#xff0c;其…

Buildroot 开发

转载&#xff1a;https://wiki.t-firefly.com/AIO-3288C/buildroot_develop.html Buildroot 开发 Buildroot 是 Linux 平台上一个构建嵌入式 Linux 系统的框架。整个 Buildroot 是由 Makefile(*.mk) 脚本和 Kconfig(Config.in) 配置文件构成的。你可以和编译 Linux 内核一样&am…

PreScan快速入门到精通第三十八讲基于车道线识别传感器的车道偏离算法Demo讲解

车道偏离系统介绍: 什么是车道偏离警告? 车道偏离警告是一种先进的驾驶辅助系统(ADAS),在许多较新的车辆中发现。它在司机无意离开自己的车道时发出声音、视觉或者通过方向盘振动,甚至安全带预紧的方式给与驾驶员警告。 当汽车意外地离开道路时,就会发生车祸--而且可能…

户外运动耳机如何选择、最优秀的五款户外运动耳机推荐

有些人花时间在户外纯粹是为了听听大自然的声音。其他人可能不想在没有娱乐或鼓舞人心的音频选择的情况下跑步、徒步、散步或骑自行车。找到适合锻炼的耳机相当简单&#xff0c;就像健身耳机一样&#xff0c;您会希望这些耳机能够舒适、安全地贴合您的耳朵&#xff0c;这样它们…

hadoop集群安装(四):安装hadoop集群

文章目录说明分享环境节点规划如下安装hadoop上传安装包配置hadoop配置说明默认配置自定义配置修改配置修改core-site.xml修改hdfs-site.xml修改yarn-site.xml修改mapred-site.xml同步配置添加环境变量并同步启动hadoop配置workers文件格式化集群启动HDFS启动yarn验证验证hdfs验…

Design Compiler工具学习笔记(6)

目录 引言 知识储备 实际操作 设计源码 仿真源码 VCS执行仿真 DC 综合 引言 本篇继续学习 DC的基本使用。本篇主要学习 DC 综合之后的效果分析&#xff0c;重点在时序分析。 前文链接&#xff1a; Design Compiler工具学习笔记&#xff08;1&#xff09; Design Comp…

【华为上机真题 2022】字符串比较

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

Document-level Event Extraction via Parallel Prediction Networks论文解读

Document-level Event Extraction via Parallel Prediction Networks paper&#xff1a;Document-level Event Extraction via Parallel Prediction Networks - ACL Anthology code&#xff1a;HangYang-NLP/DE-PPN (github.com) 期刊/会议&#xff1a;ACL2021 摘要 当在整…

你认为低代码能够完全取代程序猿吗?

前言 最近在接入低代码平台&#xff0c;忙着把功能塞进去&#xff0c;没有时间思考&#x1f914;我们公司也在寻找低代码可以发力的点&#xff0c;所以我做完第一批小白鼠去试验了&#xff0c;我的想法是从一个问题带大家思考&#xff0c;从大方面来讲低代码的作用、应用场景&…

【Linux】基础IO —— 动静态库的制作与使用

&#x1f308;欢迎来到Linux专栏~~动静态库的制作与使用 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自…