当前位置: 首页 > news >正文

分布式规则引擎框架的设计

MirAIe 规则引擎是一个可扩展且可扩展的规则引擎框架,允许用户对多个活动进行分组和自动化。

        过去几年,在开发MirAIe 物联网平台时,我们意识到需要一个可扩展、可扩展的规则引擎框架。规则引擎使您能够对各种操作进行分组、管理和自动化,并可用于各种应用程序,例如家庭自动化、欺诈检测、风险管理和工作流程自动化。在松下,我们参与了移动、工业 4.0、楼宇管理和家庭自动化领域的多项举措。因此,该框架必须能够适应各种应用程序。在本文中,我将描述我们的规则引擎框架的高级设计。

规则剖析

        规则本质上允许用户对大量任务进行分组和自动化。用户生成规则的示例包括,只要室温升至 27 度以上,就打开卧室空调;每天晚上 6 点打开办公室大厅的一组灯;当装配线中的机器效率低于以下时通知您80%,或者当电池电量低于 30% 时向您的电动汽车发送推送通知,并且在附近发现有空位的充电站。

 

        规则具有指定何时激活规则的触发条件。触发可能取决于用户的位置、传感器的状况、特定时刻、外部天气条件或任何其他因素。可以使用一个或多个逻辑运算符来耦合多个触发器,这些逻辑运算符一起指定规则的触发条件。

        规则还具有在满足这些触发条件时激活的一个或多个操作。操作可能像打开灯一样简单,也可能像创建报告并将其作为附件发送给多个用户一样复杂。

设计注意事项

        规则触发条件可能取决于多个输入源。数据源可以是内部的,例如安装在实验室中的传感器,每隔几秒发送一次周期性状态,也可以是外部的,例如天气数据,需要每天检索和存储一次,因为数据不会经常更改。挑战在于吸收多个输入源以创建单个值并确定是否应激活规则。

        由于我们的框架必须支持广泛的应用,我们决定重点关注两个基本设计原则。第一个原则是架构必须可扩展以支持各种输入触发和输出操作。对于家庭自动化用例,最常见的触发因素可能取决于时间、设备状态或外部天气条件。而对于我们的智能工厂用例,触发器可能是用户活动或随时间收集的聚合数据,例如机器效率。其想法是确保可以添加新的触发器类型,而无需对架构进行太多更改。同样,规则操作也是可扩展的。常见操作可以是调用 API、发送通知或创建任务并将其推送到任务队列。 

        我们对此设计的第二个指导原则是优先考虑灵活性,允许每个组件独立扩展。灵活的系统可以适应不断变化的需求,而无需扩展整个系统,从而提高弹性和成本效率。例如,如果晚上6点需要触发大量规则,系统只需适当扩展定时器触发服务和规则执行服务,而其他服务则继续按原有规模运行。通过提供这种灵活性,系统可以高效且有效地满足不同的需求。

规则引擎组件

        为了实现可扩展性,我们解耦了规则触发服务、规则处理引擎和规则执行服务。规则触发服务是微服务的集合,每个微服务能够处理特定类型的规则触发逻辑。规则引擎根据规则定义组合各种触发状态,以确定是否应触发规则。最后,规则执行服务是特定于应用程序的规则执行逻辑,用于执行规则中指定的预期操作。每个组件都是独立开发的。它们实现了定义良好的接口并且可以独立扩展。

 

规则触发服务

        规则触发服务实现逻辑以确定何时应触发规则。它是微服务的集合,每个微服务都能够处理非常特定类型的触发器。例如,时间点激活和基于持续时间的触发器的逻辑由定时器触发器的微服务处理。同样,不同的服务处理设备状态的触发器或基于天气的触发器。 

        根据规则定义中的规则触发条件,规则在首次创建时将其自身注册到一个或多个规则触发服务。每个触发器服务提供三个主要 API 来注册、更新和取消注册规则。注册触发器的实际有效负载可能因服务而异,但是,规则创建/更新 API 的设计方式使得规则管理服务可以快速识别触发器类型并将解析和解释触发条件委托给相应的触发器服务。各个触发器类型的端点可以作为配置或环境变量的一部分进行共享,也可以使用标准服务发现模式在运行时发现它们。

        每种触发器类型都有不同的激活服务逻辑。触发器可以从一个或多个输入源捕获数据,对其进行处理,必要时对其进行缓存,并在决定是否应触发规则时发出事件。当特定规则满足触发条件时,规则触发服务会发出布尔值 true,否则会发出 false。 

         规则可以包括一个或多个触发器,其中每个触发器都建立一个特定条件,必须满足该条件才能执行规则。例如,考虑每天下午 6 点或当环境照明水平低于 100 勒克斯时打开客厅灯的规则。该规则使用 OR 逻辑组合两个条件,第一个是基于时间的触发器,第二个是设备(ALS 传感器)状态触发器。还可以通过多个触发器和逻辑运算符的组合来创建更复杂的规则。 

 

        为了管理每个触发器的状态,使用了持久缓存,该缓存由相应的触发器服务更新。这确保了规则处理引擎始终可以使用最新的触发器状态,从而允许其评估条件并调用适当的操作。上图中,红色触发状态表示当前触发条件不满足,绿色状态表示触发条件已满足。一旦规则的触发状态发生变化,相应的触发服务会将规则id添加到队列中进行处理,随后由规则处理引擎消费。 

        每个规则触发服务都被设计为可水平扩展,并且可以根据注册规则的数量独立于其他系统组件进行扩展。这种解耦还允许每个触发器的激活逻辑随着应用程序的发展而独立发展。此外,可以以最小的更改将新的触发器类型添加到系统中。

规则处理引擎

        规则处理引擎从待处理规则队列中处理规则,并根据触发状态执行规则。如果触发逻辑是一个或多个规则触发器的组合,则处理引擎根据规则定义中指定的触发逻辑组合每个输入触发器的状态,以计算最终的布尔值。一旦它确定必须触发该规则,它就会调用规则执行服务来执行该规则。

        触发状态大致有两类。时间点触发仅在触发状态发生变化时才有效,例如下午六点激活规则或设备状态变化触发(例如空调打开时关闭风扇)。如果还满足所有其他触发条件,则应在事件发生后立即激活此类规则。规则处理引擎在处理完规则后立即重置此类触发器的值。

         第二类触发器代表实体在较长时间内的持久状态。例如,考虑这样一种场景:如果在下午 6 点到早上 6 点之间检测到运动,则应打开门廊灯。计时器触发服务会在下午 6 点将触发值设置为 true,然后在上午 6 点将其设置为 false。这些状态不会由规则处理引擎重置,并且保持不变,直到由触发器服务显式修改。这使得系统能够维护实体的持久状态并根据其持续状态做出决策。

 

规则执行服务

        规则执行服务可以调用 HTTP API、发送 MQTT 消息或触发推送通知来执行规则。规则可以执行的操作列表是特定于应用程序的并且是可扩展的。与规则触发服务一样,规则操作服务与核心规则引擎解耦,并且可独立扩展。 

 

        将规则执行服务与多个规则操作服务解耦的一种方法是使用消息队列,例如 Kafka。根据操作类型,规则执行服务可以将规则操作发布到单独的 Kafka 主题,一组消费者可以使用这些主题并执行相关操作。规则操作有效负载可以特定于操作类型,并作为规则定义的一部分捕获并按原样传递到任务队列。

扩展触发服务

        规则触发服务可以是有状态的,因此扩展它们可能是一个挑战。没有通用的方法来扩展所有触发器服务,因为它们的底层实现根据触发器类型和它们可能依赖的外部服务而变化。在本节中,我将解释用于两种重要触发器类型的缩放方法。

设备状态触发器

        为了向设备状态触发服务注册规则,规则管理服务将提供设备标识符、设备属性及其相应的阈值。设备状态触发服务会将这些存储在共享缓存(例如 Redis)中,并确保仅使用设备 ID 即可访问它们。

         在给定的示例中,有关设备状态变化的通知通过 MQTT 协议发送,然后添加到 Kafka 消息队列中。负责设备状态的 Kafka 消费者接收并处理每个传入事件。它检查规则触发器缓存以确定是否有任何规则与设备关联。根据此信息,消费者更新相应的触发器状态缓存,反映该设备触发器的当前状态。这种机制确保触发状态缓存与最新的设备状态变化保持同步,从而使系统能够根据最新的信息准确地评估规则。

        我们所有的服务都是容器化的,并在 Kubernetes 集群中运行。设备状态触发服务是标准API服务,通过应用程序负载均衡和自动伸缩组进行扩展。设备状态使用者组根据传入设备状态更改事件的速率进行扩展。Kubernetes 事件驱动自动缩放 (KEDA)可以根据需要处理的事件数量来驱动设备状态使用者的缩放。此外,还有一些工具可以预测 Kafka 工作负载,可以用来更迅速地扩展消费者,从而提高性能。

定时器触发

        定时器触发服务处理时间点触发和持续时间触发。触发请求有效负载可以像一天中的特定时间一样简单,也可以像Unix Cron 作业的规范一样详细。该服务不需要在内存中保存所有已注册的规则请求,因为规则可能几天或几个月都不会触发。相反,一旦它收到规则注册请求,它就会计算下一次应该运行该规则的时间,并将规则详细信息以及下一次运行时间存储在数据库中。
        该服务会定期以固定的时间间隔获取后续时间窗口中需要激活的所有规则并将其拉入内存。这可以通过过滤规则的下一个运行时间字段来完成。一旦确定了需要运行的所有规则,该服务就会按触发时间对规则进行排序,并生成一个或多个 Kubernetes Pod 来处理这些规则。每个 Pod 只会被分配规则的一个子集。可以通过 Zookeeper 或 Kubernetes自定义资源定义(CRD)共享分配给不同 Pod 的规则。

 

        Kubernetes CRD 可用于通过定义代表特定任务的自定义资源来共享数据并在多个 Pod 之间分配工作。定时器触发服务通过将下一个时间窗口中需要处理的所有规则划分为单独的任务并将它们存储在 CRD 中来使用此功能。随后创建多个工作单元并分配特定任务。然后,每个 Pod 处理规则并相应地更新触发器状态缓存。 

可维护性和可扩展性

        规则管理服务和规则执行服务是无状态服务,逻辑相当简单。规则管理提供用于规则创建、更新和删除的标准API。规则执行服务独立工作以执行规则操作,主要调用特定于应用程序的操作。

        规则管理服务和触发服务之间的通信可以异步进行,从而无需服务发现。例如,每个触发器服务都可以在 Kafka 消息代理中拥有其专用队列。规则管理服务可以根据触发类型将触发请求添加到相应的队列中,供触发类型的Kafka消费者(触发服务)处理来消费。

        添加对新触发器类型和新操作类型的支持很简单,因为所有关键组件都是相互解耦的。规则管理服务具有基于插件的设计,其中为每个支持的触发器类型和操作类型添加插件,以验证规则定义中相应的触发器和操作有效负载。规则触发器和操作类型名称可以转换为相应的 Kafka 队列名称,用于规则管理服务和触发器服务之间以及规则处理服务和执行服务之间的通信。

可以在多个级别上测试系统。通过单元测试覆盖各个服务并专注于特定功能相对容易。为了促进分布式环境中的轻松调试和故障排除,必须遵守分布式日志记录和跟踪的最佳实践。正确实施的日志记录和跟踪机制将使我们能够跟踪不同服务之间的请求流,有效地识别问题并诊断问题。遵循这些最佳实践可确保更好地了解系统行为并简化调试过程。

可靠性

        让我们首先确定可能出现的潜在挑战。重要的是要承认系统内的任何服务都可能会意外停机,系统负载的增长速度可能快于其有效扩展的能力,并且某些服务或基础设施组件可能会遇到暂时不可用的情况。 

        使用 Kafka 进行通信有助于实现多个级别的可靠性。Kafka 提供有助于消息传递和消费可靠性的功能,包括消息持久性、强大的持久性保证、容错复制、消费者组之间的负载分配以及至少一种传递语义。

        最直接的可靠性案例涉及触发服务。对于设备状态触发,Kafka配置为保证至少一次投递,从而保证状态变化事件不会丢失。然而,实现定时器触发服务的可靠性需要额外的步骤。在这里,重要的是要避免让任何单个工作人员因需要同时处理的大量事件而不堪重负。  

        我们的方法是按时间顺序排列下一个时间窗口中要处理的规则列表,并以循环方式在工作人员服务之间分发它们。此外,worker pod 的数量与时间窗口内的计时器任务数量以及任何给定时间要执行的最大任务数量成正比。这确保了有足够数量的工作节点可同时处理潜在的大量计时器任务。此外,将工作单元配置为在崩溃时自动重新启动也是有益的,使其能够恢复并完成分配的任务,而无需手动干预。  

        此外,Kubernetes 还具有为每个服务定义资源限制和最低要求的优势。这包括指定服务可以利用的最大 CPU 或 RAM 资源量以及成功启动所需的最少资源。借助 Kubernetes,可以减轻对“吵闹邻居”问题(即一个 Pod 的资源密集型行为影响同一集群节点上的其他 Pod)等问题的担忧。Kubernetes 提供隔离和资源管理能力,有助于维护整个系统的稳定性和可靠性。 

概括

        MirAIe 规则引擎是一个可扩展且可扩展的规则引擎框架,允许用户对多个活动进行分组和自动化。该框架支持各种内部或外部触发器,并侧重于两个设计原则:可扩展性和灵活性。该架构必须可扩展,以支持各种输入触发器和输出操作,从而允许在不进行太多更改的情况下添加新类型。该系统还优先考虑灵活性,以实现每个组件的独立扩展,从而提高弹性和成本效率。

相关文章:

分布式规则引擎框架的设计

MirAIe 规则引擎是一个可扩展且可扩展的规则引擎框架,允许用户对多个活动进行分组和自动化。 过去几年,在开发MirAIe 物联网平台时,我们意识到需要一个可扩展、可扩展的规则引擎框架。规则引擎使您能够对各种操作进行分组、管理和自动化&…...

C#开发FFMPEG例子(API方式) FFmpeg推送udp组播流

代码及工程见https://download.csdn.net/download/daqinzl/88156926 开发工具:visual studio 2019 播放,可采用ffmpeg工具集里的ffplay.exe, 执行命令 ffplay udp://238.1.1.10:6016 也可以参考(C#开发FFMPEG例子(API方式) FFmpeg拉取udp组播流并播放)…...

nvm下载node导致npm报错无法使用

有个依赖库需要更新下node,用nvm下载后项目跑不起来了,npm -v 还报错 其实一开始是npm下载不来,然后换了淘宝镜像后还是报错 然后就只能手动下载下了 进入node.js官网 https://nodejs.org/en/download 下载后注意要安装在你nvm目录中&#x…...

LeetCode 热题 100JavaScript--2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 …...

zookeeper总结

1.概念 Zookeeper 是一个分布式协调服务,可用于服务发现,分布式锁,分布式领导选举,配置管理等。Zookeeper 提供了一个类似于 Linux 文件系统的树形结构(可认为是轻量级的内存文件系统,但只适合存少量信息&…...

【程序环境与预处理玩转指南】

本章重点: 程序的翻译环境 程序的执行环境 详解:C语言程序的编译链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符#和##的介绍 命令定义 预处理指令 #include 预处理指令 #undef 条件编译 1. 程序的翻译环境和执行环境 在…...

搭建简易syslog日志中转服务器

在某种场景下,无法接入日志审计设备,本文提供一种方式,可通过搭建简易日志中转服务器,收集到该环境下的日志后,再将其导入日志审计设备中。 0x1 开启服务 rsyslog守护进程来自于当前的linux发布版本的预装模块&#x…...

MongoDB文档-进阶使用-spring-boot整合使用MongoDB---MongoRepository完成增删改查

阿丹: 之前学习了在MongoDB客户端上的MongoDB语句现在将MongoDB整合到spring项目。 传送门: MongoDB文档--基本概念_一单成的博客-CSDN博客 MongoDB文档--基本安装-linux安装(mongodb环境搭建)-docker安装(挂载数据卷…...

什么是线程局部变量?

在Java中,线程局部变量(Thread Local Variable)是一种特殊类型的变量,每个线程都有其自己独立的副本。这意味着每个线程可以在该变量上进行操作,而不会影响其他线程的副本。线程局部变量通常用于在多线程环境中存储线程私有的数据&#xff0c…...

Jmeter响应中的乱码问题

文章目录 问题描述解决办法 问题描述 Jmeter在访问接口的时候,响应内容如果有中文可能会显示乱码 响应页面没有做编码处理,JMeter默认按照ISO-8859-1编码格式进行解析 解决办法 在线程组中添加BeanShell PostProcessor后置处理器 prev.setDataEnco…...

MongoDB文档-进阶使用-MongoDB索引-createindex()与dropindex()-在MongoDB中使用正则表达式来查找

阿丹: 之前研究了MongoDB的基础增删改查。在学会基础的数据库增删改查肯定是不够的。这个时候就涉及到了数据库搜索的时候的效率。需要提高数据的搜索效率。 MongoDB索引 在所以数据库中如果没有数据索引的时候。如果需要查找到一些数据。都会去主动扫描所有可能存…...

CentOS下ZLMediaKit的可视化管理网站MediaServerUI使用

一、简介 按照 ZLMediaKit快速开始 编译运行ZLMediaKit成功后,我们可以运行其合作开源项目MediaServerUI,来对ZLMediaKit进行可视化管理。通过MediaServerUI,我们可以实现在浏览器查看ZLMediaKit的延迟率、负载率、正在进行的推拉流、服务器…...

回归预测 | MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现POA-CNN-BiGRU鹈鹕算法优化卷积双向门控循环单元多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现POA-CNN-BiGRU鹈鹕…...

Rust 原生支持龙架构指令集

导读近日,Rust 开源社区发布 1.71.0 版本,实现对龙架构(LoongArch)指令集的原生支持。 龙架构操作系统发行版和开发者可基于上游社区源代码构建或直接下载 Rust 开源社区发布的龙架构二进制版本。Rust 开发者将在龙架构平台上获得…...

为生成式AI提速,亚马逊云科技Amazon EC2 P5满足GPU需求

生成式AI(Generative AI)已经成为全球范围内的一个重要趋势,得到越来越多企业和研究机构的关注和应用。纽约时间7月26日,亚马逊云科技数据库、数据分析和机器学习全球副总裁Swami Sivasubramanian在亚马逊云科技举办的纽约峰会上更…...

聊聊企业数据安全那些事~

保护企业数据安全的重要性与方法 随着信息技术的快速发展,企业数据的安全性变得越来越重要。在数字化时代,企业的核心业务和关键信息都存储在电脑系统中,一旦遭受到数据泄露、黑客攻击或恶意软件感染,将可能对企业造成严重的损害…...

日常随笔——如何把excel题库转换为word打印格式

将Excel题库转换为Word可以通过编程的方式实现。以下是一个使用Python的示例代码,该代码使用openpyxl库读取Excel文件,并使用python-docx库创建和保存Word文档。 首先,请确保已经安装了 openpyxl 和 python-docx 库。可以使用以下命令进行安…...

SpringCloud项目打包注意事项以及可能出错的几种情况

SpringCloud项目打包注意事项和可能出错的几种情况 1、检查子模块中的 parent的pom文件路径 \<relativePath/\>2、检查打包插件的位置3、检查module是否重复引用 欢迎访问我的个人博客&#xff1a;https://wk-blog.vip 1、检查子模块中的 parent的pom文件路径 <relat…...

ZABBIX 6.4 Mysql数据库分表

ZABBIX监控设备较多的时候&#xff0c;Mysql数据库容易成为性能的瓶颈&#xff0c;可以通过数据库分表的方式来进行优化。步骤如下&#xff1a; 一、停用zabbix服务 # 避免修改分区表时&#xff0c;数据还有写入 systemctl stop zabbix 二、备份MySQL zabbix DB 避免修改分…...

多线程-Runable和Callable的区别

在Java中&#xff0c;多线程可以通过实现Runnable接口或使用Callable接口来实现。这两种方式有一些区别&#xff0c;如下所示&#xff1a; 返回值&#xff1a; Runnable接口的run()方法没有返回值&#xff0c;它表示一个没有返回结果的任务。Callable接口的call()方法有返回值…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度

文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

使用LangGraph和LangSmith构建多智能体人工智能系统

现在&#xff0c;通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战&#xff0c;比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...