Spring Boot集成statemachine快速入门demo

1.什么是statemachine?

Spring Statemachine 是应用程序开发人员在 Spring 应用程序中使用状态机概念的框架,从设计层面分析:状态机目的是解决复杂的状态管理流程,保证程序单一原则和开闭原则;业务角度分析:状态机应有初始化状态、加载所有已有的状态、事件、转移、动作、触发下一个状态为驱动,并解决了业务逻辑与状态管理之间的强耦合。

Spring Statemachine 提供以下功能:

  • 易于使用的平面(一级)状态机,适用于简单的用例。
  • 分层状态机结构,以简化复杂的状态配置。
  • 状态机区域提供更复杂的状态配置。
  • 触发器、转换、守卫和动作的使用。
  • 类型安全的配置适配器。
  • 状态机事件监听器。
  • Spring IoC 集成将 bean 与状态机相关联。

Spring Statemachine 原理

Spring状态机建立在有限状态机(FSM)的概念之上,提供了一种简洁且灵活的方式来定义、管理和执行状态机。它将状态定义为Java对象,并通过配置来定义状态之间的转换规则。状态转换通常由外部事件触发,我们可以根据业务逻辑定义不同的事件类型,并与状态转换关联。Spring状态机还提供了状态监听器,用于在状态变化时执行特定的逻辑。同时,状态机的状态可以持久化到数据库或其他存储介质中,以便在系统重启或故障恢复时保持状态的一致性。 Spring状态机核心主要包括以下三个关键元素:

  1. 状态(State):定义了系统可能处于的各个状态,如订单状态中的待支付、已支付等。
  2. 转换(Transition):描述了在何种条件下,当接收到特定事件时,系统可以从一个状态转移到另一个状态。例如,接收到“支付成功”事件时,订单状态从“待支付”转变为“已支付”。
  3. 事件(Event):触发状态转换的动作或者消息,它是引起状态机从当前状态迁移到新状态的原因。

接下来,我们将上述状态模式中关于订单状态的示例转换为状态机实现。  

2.代码工程

实验目标:订单状态的示例转换为状态机实现。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>springboot-demo</artifactId><groupId>com.et</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>Statemachine</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-core</artifactId><version>2.0.0.RELEASE</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
</project>

定义状态机的状态以及事件类型

定义状态(States): 状态是状态机的核心组成单元,代表了系统或对象在某一时刻可能存在的条件或模式。在状态机中,每一个状态都是系统可能处于的一种明确的条件或阶段。例如,在一个简单的咖啡机状态机中,可能有的状态包括“待机”、“磨豆”、“冲泡”和“完成”。每个状态都是独一无二的,且在任何给定时间,系统只能处于其中一个状态。 定义转换(Transitions): 转换则是指状态之间的转变过程,它是状态机模型动态性的体现。当一个外部事件(如用户按下按钮、接收到信号、满足特定条件等)触发时,状态机会从当前状态转移到另一个状态。在定义转换时,需要指出触发转换的事件(Event)以及事件发生时系统的响应,即从哪个状态(Source State)转到哪个状态(Target State)。

package com.et.statemachine.state;/*** @description:order status*/
public enum OrderStatusChangeEventEnum {PAYED,DELIVERY,RECEIVED;
}
package com.et.statemachine.state;/*** @description: order status*/
public enum OrderStatusEnum {WAIT_PAYMENT,WAIT_DELIVER,WAIT_RECEIVE,FINISH;
}

定义状态机以及状态流转规则

状态机配置类是在使用Spring State Machine或其他状态机框架时的一个重要步骤,这个类主要用于定义状态机的核心结构,包括状态(states)、事件(events)、状态之间的转换规则(transitions),以及可能的状态迁移动作和决策逻辑。 在Spring State Machine中,创建状态机配置类通常是通过继承StateMachineConfigurerAdapter类来实现的。这个适配器类提供了几个模板方法,允许开发者重写它们来配置状态机的各种组成部分:

  1. 配置状态configureStates(StateMachineStateConfigurer)): 在这个方法中,开发者定义状态机中所有的状态,包括初始状态(initial state)和结束状态(final/terminal states)。例如,定义状态A、B、C,并指定状态A作为初始状态。
  2. 配置转换configureTransitions(StateMachineTransitionConfigurer)): 在这里,开发者描述状态之间的转换规则,也就是当某个事件(event)发生时,状态机应如何从一个状态转移到另一个状态。例如,当事件X发生时,状态机从状态A转移到状态B。
  3. 配置初始状态configureInitialState(ConfigurableStateMachineInitializer)): 如果需要显式指定状态机启动时的初始状态,可以在该方法中设置。
package com.et.statemachine.config;import com.et.statemachine.state.OrderStatusChangeEventEnum;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;import java.util.EnumSet;/*** @description: order statemachine*/
@Configuration
@EnableStateMachine
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusChangeEventEnum> {/*** configure state*/@Overridepublic void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> states) throws Exception {states.withStates().initial(OrderStatusEnum.WAIT_PAYMENT).end(OrderStatusEnum.FINISH).states(EnumSet.allOf(OrderStatusEnum.class));}/*** configure state transient  with event*/@Overridepublic void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusChangeEventEnum> transitions) throws Exception {transitions.withExternal().source(OrderStatusEnum.WAIT_PAYMENT).target(OrderStatusEnum.WAIT_DELIVER).event(OrderStatusChangeEventEnum.PAYED).and().withExternal().source(OrderStatusEnum.WAIT_DELIVER).target(OrderStatusEnum.WAIT_RECEIVE).event(OrderStatusChangeEventEnum.DELIVERY).and().withExternal().source(OrderStatusEnum.WAIT_RECEIVE).target(OrderStatusEnum.FINISH).event(OrderStatusChangeEventEnum.RECEIVED);}
}

定义状态机监听器

状态机监听器(State Machine Listener)是一种组件,它可以监听并响应状态机在运行过程中的各种事件,例如状态变迁、进入或退出状态、转换被拒绝等。 在Spring Statemachine中,监听器可以通过实现StateMachineListener接口来定义。该接口提供了一系列回调方法,如transitionTriggeredstateEnteredstateExited等,当状态机触发转换、进入新状态或离开旧状态时,这些方法会被调用。同时,我们也可以通过注解实现监听器。注解方式可以在类的方法上直接声明该方法应该在何种状态下被调用,简化监听器的编写和配置。例如@OnTransition@OnTransitionEnd@OnTransitionStart

package com.et.statemachine.listener;import com.et.statemachine.state.Order;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;/*** @description: state listener*/
@Component
@WithStateMachine
@Transactional
public class OrderStatusListener {@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")public boolean payTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_DELIVER);System.out.println("pay,feedback by statemachine:" + message.getHeaders().toString());return true;}@OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")public boolean deliverTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.WAIT_RECEIVE);System.out.println("deliver,feedback by statemachine:" + message.getHeaders().toString());return true;}@OnTransition(source = "WAIT_RECEIVE", target = "FINISH")public boolean receiveTransition(Message message) {Order order = (Order) message.getHeaders().get("order");order.setOrderStatus(OrderStatusEnum.FINISH);System.out.println("receive,feedback by statemachine:" + message.getHeaders().toString());return true;}}

service

package com.et.statemachine.service;import com.et.statemachine.state.Order;import java.util.Map;/*** @author liuhaihua* @version 1.0* @ClassName OrderService* @Description todo* @date 2024年05月27日 15:15*/public interface OrderService {Order create();Order pay(long id);Order deliver(long id);Order receive(long id);Map<Long, Order> getOrders();
}
package com.et.statemachine.service;import com.et.statemachine.state.Order;
import com.et.statemachine.state.OrderStatusChangeEventEnum;
import com.et.statemachine.state.OrderStatusEnum;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;/*** @description: order service*/
@Service
public class OrderServiceImpl implements OrderService {@Resourceprivate StateMachine<OrderStatusEnum, OrderStatusChangeEventEnum> orderStateMachine;private long id = 1L;private Map<Long, Order> orders = new ConcurrentHashMap<>();@Overridepublic Order create() {Order order = new Order();order.setOrderStatus(OrderStatusEnum.WAIT_PAYMENT);order.setOrderId(id++);orders.put(order.getOrderId(), order);System.out.println("order create success:" + order.toString());return order;}@Overridepublic Order pay(long id) {Order order = orders.get(id);System.out.println("try to pay,order no:" + id);Message message = MessageBuilder.withPayload(OrderStatusChangeEventEnum.PAYED).setHeader("order", order).build();if (!sendEvent(message)) {System.out.println(" pay fail, error,order no:" + id);}return orders.get(id);}@Overridepublic Order deliver(long id) {Order order = orders.get(id);System.out.println(" try to deliver,order no:" + id);if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.DELIVERY).setHeader("order", order).build())) {System.out.println(" deliver fail,error,order no:" + id);}return orders.get(id);}@Overridepublic Order receive(long id) {Order order = orders.get(id);System.out.println(" try to receiver,order no:" + id);if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEventEnum.RECEIVED).setHeader("order", order).build())) {System.out.println(" deliver fail,error,order no:" + id);}return orders.get(id);}@Overridepublic Map<Long, Order> getOrders() {return orders;}/*** send transient  event* @param message* @return*/private synchronized boolean sendEvent(Message<OrderStatusChangeEventEnum> message) {boolean result = false;try {orderStateMachine.start();result = orderStateMachine.sendEvent(message);} catch (Exception e) {e.printStackTrace();} finally {if (Objects.nonNull(message)) {Order order = (Order) message.getHeaders().get("order");if (Objects.nonNull(order) && Objects.equals(order.getOrderStatus(), OrderStatusEnum.FINISH)) {orderStateMachine.stop();}}}return result;}
}

controller

package com.et.statemachine.controller;import com.et.statemachine.service.OrderService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;@RestController
public class HelloWorldController {@RequestMapping("/hello")public Map<String, Object> showHelloWorld(){Map<String, Object> map = new HashMap<>();map.put("msg", "HelloWorld");return map;}@Resourceprivate OrderService orderService;@RequestMapping("/testOrderStatusChange")public String testOrderStatusChange(){orderService.create();orderService.create();orderService.pay(1L);orderService.deliver(1L);orderService.receive(1L);orderService.pay(2L);orderService.deliver(2L);orderService.receive(2L);System.out.println("all orders:" + orderService.getOrders());return "success";}}

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.

3.测试

启动Spring Boot应用

测试状态机

访问http://127.0.0.1:8088/testOrderStatusChange,查看控制台输出

order create success:Order(orderId=1, orderStatus=WAIT_PAYMENT)
order create success:Order(orderId=2, orderStatus=WAIT_PAYMENT)
try to pay,order no:1
2024-05-27 22:58:14.208 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.209 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / WAIT_PAYMENT / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
pay,feedback by statemachine:{order=Order(orderId=1, orderStatus=WAIT_DELIVER), id=fc2c0720-e6ba-9bf8-d359-72a6c61b4186, timestamp=1716821894196}try to deliver,order no:1
deliver,feedback by statemachine:{order=Order(orderId=1, orderStatus=WAIT_RECEIVE), id=e743d376-22e1-bfc3-1c62-7131ff1bf7c1, timestamp=1716821894227}try to receiver,order no:1
receive,feedback by statemachine:{order=Order(orderId=1, orderStatus=FINISH), id=652167b8-e74f-bde2-62f7-94bdbb5bad7e, timestamp=1716821894229}
2024-05-27 22:58:14.230 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.230 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
try to pay,order no:2
2024-05-27 22:58:14.231 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.231 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : started WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / WAIT_PAYMENT / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
pay,feedback by statemachine:{order=Order(orderId=2, orderStatus=WAIT_DELIVER), id=d331fa76-8a28-aaa7-6257-a9404c2084d6, timestamp=1716821894230}try to deliver,order no:2
deliver,feedback by statemachine:{order=Order(orderId=2, orderStatus=WAIT_RECEIVE), id=4e930443-6b04-fd86-6740-5631db2aea1d, timestamp=1716821894232}try to receiver,order no:2
receive,feedback by statemachine:{order=Order(orderId=2, orderStatus=FINISH), id=6473cc9e-5cd9-0de5-12c8-7d51dd3f9da6, timestamp=1716821894233}
2024-05-27 22:58:14.234 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor@13e24b22
2024-05-27 22:58:14.234 INFO 13306 --- [nio-8088-exec-2] o.s.s.support.LifecycleObjectSupport : stopped WAIT_RECEIVE WAIT_DELIVER FINISH WAIT_PAYMENT / / uuid=7a254cb7-5a92-4f0b-b6c7-3edc4d9e2de2 / id=null
all orders:{1=Order(orderId=1, orderStatus=FINISH), 2=Order(orderId=2, orderStatus=FINISH)}

4.引用

  • Spring State Machine
  • Spring Boot集成statemachine快速入门demo | Harries Blog™

   

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

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

相关文章

【面试】什么是Java虚拟机

目录 1. 说明2. 关键点 1. 说明 1.Java虚拟机&#xff08;Java Virtual Machine&#xff0c;简称JVM&#xff09;是运行所有Java程序的抽象计算机&#xff0c;是Java语言的运行环境。2.JVM是Java平台无关性的关键&#xff0c;它允许Java程序在任何支持JVM的硬件和操作系统上运…

【大数据面试题】34 手写一个 Flink SQL 样例

一步一个脚印,一天一道大数据面试题 博主希望能够得到大家的点赞收,藏支持!非常感谢~ 点赞,收藏是情分,不点是本分。祝你身体健康,事事顺心! 我们来看看 Flink SQL大概流程和样例: 流程: 1.创建 流处理环境 StreamExecutionEnvironment env 2.创建 表环境 StreamTab…

为啥装了erlang,还报错erl: command not found?

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 问题背景&#xff1a; 在一台不通外网的服务器上装rabbitmq&#xff0c;然后在启动的时候&#xff0c;遇到了报错 “/usr/lib/…

C#中使用Mapster

Mapster是一个开源的.NET对象映射库&#xff0c;它提供了一种简单而强大的方式来处理对象之间的映射。 多个映射框架的性能对比&#xff1a; 第一步安装Mapster 使用方法 public class Test {public string name { get; set; }public string sex { get; set; }public string…

C语言数据结构(超详细讲解)| 二叉树的实现

二叉树 引言 在计算机科学中&#xff0c;数据结构是算法设计的基石&#xff0c;而二叉树&#xff08;Binary Tree&#xff09;作为一种基础且广泛应用的数据结构&#xff0c;具有重要的地位。无论是在数据库索引、内存管理&#xff0c;还是在编译器实现中&#xff0c;二叉树都…

记录Win11安装打印机驱动过程

1. 首先下载打印机对应型号的驱动 可以从这里下载&#xff1a;打印机驱动,打印机驱动下载 - 打印机驱动网 2. 下载 3. 打开控制面板-->设备和打印机 找到目标打印机添加设备即可 新增打印纸张尺寸

B站稿件生产平台高可用建设分享

背景 B站作为国内领先的内容分享平台&#xff0c;其核心功能之一便是支持UP主们创作并分享各类视频内容。UP主稿件系统作为B站内容生产的关键环节&#xff0c;承担着从内容创作到发布的全过程管理。为了满足不同创作者的需求&#xff0c;B站提供了多种投稿渠道&#xff0c;包括…

方差分析的七种类型

方差分析&#xff08;ANOVA&#xff09;是一种用于检验两个以上样本均数差别的显著性统计方法。根据不同的研究设计和数据类型&#xff0c;方差分析可以分为以下7种类型。 一、单因素方差分析 ①单因素方差分析说明 单因素方差分析用于研究一个定类数据&#xff08;自变量&am…

【原创教程】MES服务器与成品打标机控制说明

1 实现的功能及应用的场合 MES即制造执行系统(manufacturing execution system,简称MES),即在加强MRP计划的执行功能,把MRP计划同车间作业现场控制,通过执行系统联系起来。 MES是一个生产管理智能化的一个系统,是用于生产时记录数据、产量等信息的智能管理系统。 该项…

SpockMockStatic方法

SpockMockStatic方法 参考: https://blog.csdn.net/knighttools/article/details/44630975 ‍ static方法 import com.meituan.mafka.client.producer.IProducerProcessor; import com.meituan.mdp.langmodel.api.message.AssistantMessage; import com.sankuai.gaigc.arrang…

文件批量重命名001到100如何操作?这几个文件改名小技巧学起来

文件批量重命名001到100怎么操作&#xff1f;作为打工一族&#xff0c;每天都需要跟很多文件打交道&#xff0c;有时文件太多了&#xff0c;查找起来像是大海捞针&#xff0c;特别是图片文件。这个时候我们就需要对大量文件进行整理和排序&#xff0c;这样有助于提高我们的工作…

微信小程序 自定义 tabBar

自定义 tabBar | 微信开放文档 本文案例使用的Taro 非原生微信小程序 使用流程 1. 配置信息 在 app.json 中的 tabBar 项指定 custom 字段&#xff0c;同时其余 tabBar 相关配置也补充完整。所有 tab 页的 json 里需声明 usingComponents 项&#xff0c;也可以在 app.json 全局…

Java语言的应用场景

1、开发移动应用程序 例如&#xff1a;Android。 2、开发服务应用程序&#xff0c;搭建WEB界面。 例如&#xff1a;Servlet、JSP。 3、开发应用服务器。 例如Tomcat。 4、开发网络通信程序。 5、开发图形化界面桌面端。 Java支持用AWT、Swing、JavaFX三种包来开发图形化界面…

电脑提示缺少vcruntime140_1.dll的解决方法,总结7种有效方法

vcruntime140_1.dll是Microsoft Visual C 2015运行时库的一部分&#xff0c;它为使用Visual Studio 2015开发的应用程序提供了必要的运行时组件。该文件支持C程序的执行&#xff0c;包括内存管理、输入输出操作以及多线程功能等。缺失或损坏此文件可能导致应用程序无法启动或运…

广告联盟四大家

国内四大广告承接商&#xff1a;①抖音旗下-穿山甲②快手旗下-快手联盟③百度旗下-百青藤④腾讯旗下-优量汇 我们目前在互联网上能看到的所有广告都是由他们发放的&#xff0c;在其中我们打小游戏复活看广告&#xff0c;获得道具看广告&#xff0c;看剧看广告&#xff0c;这…

数据库的隔离级别和索引使用

先看一下隔离级别&#xff0c; 隔离级别首先要明确 &#xff0c;隔离的越重&#xff0c;那么自然会失去效率&#xff0c;为什么有这么多的隔离级别&#xff0c;其实就是平衡业务关系尽可能的提高效率。 下面看下隔离级别和介绍&#xff1a; 读未提交是指&#xff1a;一个事务…

Oracle SQL详解

Oracle SQL是一种用于管理和操作Oracle数据库的编程语言。以下是一些基本的Oracle SQL语法和建表建用户的详解。 创建用户 在Oracle中&#xff0c;创建用户通常需要具有足够权限的用户&#xff08;通常是具有DBA角色的用户&#xff09;。以下是一个创建用户的例子&#xff1a;…

基于词频统计的聚类算法(kmeans)

基于词频统计的聚类算法&#xff08;kmeans&#xff09; 数据集是三个政府报告文件&#xff0c;这里就不做详细描述了&#xff0c;就是简单的txt文件。 实验过程主要分为如下几步&#xff1a; 1.读取数据并进行停用词过滤 2.统计词频 3.基于三篇文章词频统计的层次聚类 4.基于…

废品回收小程序怎么做?有哪些核心功能?

废品回收行业正逐步走向高质量发展的道路。在国家政策的推动下&#xff0c;再生资源市场需求旺盛&#xff0c;行业内部竞争格局逐渐明朗。 随着互联网技术的发展&#xff0c;"互联网回收"成为废品回收行业的一个新趋势。通过微信小程序这种线上平台&#xff0c;用户…

数据可视化在智慧园区中的核心价值解析

数据可视化在智慧园区中发挥着至关重要的价值。智慧园区是一种基于物联网、大数据、云计算等先进技术的现代化管理模式&#xff0c;旨在通过智能化手段提升园区的管理效率、服务水平和用户体验。而数据可视化作为数据处理和展示的重要工具&#xff0c;正是智慧园区实现这些目标…