SmartEngine流程引擎之Custom模式

news2024/12/23 2:01:11

目录

一、为什么选用SmartEngine 

二、各类流程引擎框架简单对比

 1、流程设计器推荐

 2、什么是BPMN

流程定义解释说明

三、SmartEngine之Custom实操

1、引入依赖

2、典型的初始化代码如下

3、节点如何流转以及流程实例存储问题

4、定义Delegation

关键类


一、为什么选用SmartEngine 

阿里目前新开源了一套流程引擎框架,相比于flowable等传统流程引擎框架,SmartEngine更加轻便,它支持两种模式,其中的Custom模式,并不强依赖数据库,经典场景如请求密集型的互联网业务,这种业务对高并发和存储成本比较敏感流程实例的状态可由一个简短的json文本来存储,意味着所有的流程更新,都会体现在这一个json字符串里。结合公司业务场景需要,以及未来考量,Custom模式完全符合当下场景。

二、各类流程引擎框架简单对比

市场上比较有名的开源流程引擎有osworkflow、jbpm、activiti、flowable、camunda。其中:Jbpm4、Activiti、Flowable、camunda四个框架同宗同源,祖先都是Jbpm4,开发者只要用过其中一个框架,基本上就会用其它三个。流程可视化作为低代码开发平台的特征之一,它的核心是流程引擎和流程设计器。

 1、流程设计器推荐

SmartEngine推荐使用Camunda这个开源版本设计器。相关bpmn流程图可以直接用Camunda Modeler 绘制,绘制完成后导出xml文件,然后在SmartEngine中无缝使用,不用额外手工修改

Camunda下载地址

所以需要大家对Camunda有一个基本的了解

2、什么是BPMN

BPMN全称Business Process Model And Notation,是一套符合国际标准的业务流程建模符号。基本上,BPMN规范定义流程该怎么做,哪些结构可以与其他进行连接等等。不符合BPMN的流程引擎使用效果将大打折扣。也就是说所有流程引擎都要遵循BPMN这一标准,好比所有浏览器都要遵循HTTP协议

BPMN片段实例

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:smart="http://smartengine.org/schema/process" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" targetNamespace="Examples">

    <process id="exclusiveTest" version="1.0.0">

        <startEvent id="theStart">
        </startEvent>

        <sequenceFlow id="flow1" sourceRef="theStart" targetRef="submitTask"/>

        <userTask id="submitTask" name="SubmitTask">
        </userTask>

        <sequenceFlow id="flowFromSubmitTask" sourceRef="submitTask" targetRef="auditTask"/>

        <userTask id="auditTask" name="AuditTask">
        </userTask>

        <sequenceFlow id="flowFromAuditTask" sourceRef="auditTask" targetRef="exclusiveGw1"/>


        <exclusiveGateway id="exclusiveGw1" name="Exclusive Gateway 1"/>

        <sequenceFlow id="flow2" sourceRef="exclusiveGw1"
                      targetRef="executeTask">
            <conditionExpression xsi:type="mvel">approve == 'agree'
            </conditionExpression>
        </sequenceFlow>

        <sequenceFlow id="flow3" sourceRef="exclusiveGw1"
                      targetRef="advancedAuditTask">
            <conditionExpression xsi:type="mvel">approve == 'upgrade'
            </conditionExpression>
        </sequenceFlow>


        <serviceTask id="executeTask" name="ExecuteTask"
                     smart:class="com.alibaba.simplest.bpm.util.AuditProcessServiceTaskDelegation">
        </serviceTask>


        <sequenceFlow id="flow5" sourceRef="executeTask" targetRef="theEnd"/>


        <userTask id="advancedAuditTask" name="AdvancedAuditTask">
        </userTask>

        <sequenceFlow id="flowFromAdvancedAuditTask" sourceRef="advancedAuditTask"
                      targetRef="exclusiveGw2"/>

        <exclusiveGateway id="exclusiveGw2" name="Exclusive Gateway 2"/>

        <sequenceFlow id="flow9" sourceRef="exclusiveGw2"
                      targetRef="executeTask">
            <conditionExpression type="mvel">approve == 'agree'
            </conditionExpression>
        </sequenceFlow>

        <sequenceFlow id="flow10" sourceRef="exclusiveGw2"
                      targetRef="theEnd">
            <conditionExpression type="mvel">approve == 'deny'
            </conditionExpression>
        </sequenceFlow>

        <endEvent id="theEnd"/>

    </process>

</definitions>

流程定义解释说明

  1. process,表示一个流程。
  2. id="exclusiveTest" version="1.0.0",分别表示流程定义的id和版本。这两个字段唯一区分一个流程定义。
  3. startEvent,表示流程开始节点。只允许有一个开始节点。
  4. endEvent ,表示流程结束节点。可以有多个结束节点。
  5. sequenceFlow ,表示环节流转关系。sourceRef="theStart" targetRef="submitTask" 分别表示起始节点和目标节点。该节点有个子节点, <conditionExpression type="mvel">approve == 'agree' </conditionExpression>,这个片段很重要,用来描述流程流转的条件.approve == 'upgrade'使用的是MVEL表达式语法. 另外,还值得注意的是,在驱动流程运转时,需要传入正确的参数。 比如说,在后面介绍的api中,通常会需要在Map中传递业务请求参数。 那么需要将map中的key 和 Mvel的运算因子关联起来。 以这个例子来说,  request.put("approve", "agree"); 里面的approve 和 approve == 'agree'  命名要一致。
  6. exclusiveGateway ,表示互斥网关。该节点非常重要。用来区分流程节点的不同转向。 互斥网关在引擎执行conditionExpression  后,有且只能选择一条匹配的sequenceFlow 继续执行。
  7. serviceTask,服务任务,用来表示执行一个服务,所以他会有引擎默认的扩展:smart:class="com.alibaba.smart.framework.example.AuditProcessServiceTaskDelegation". Client Developer使用时,需要自定义对应的业务实现类。在该节点执行时,它会自动执行服务调用,执行 smart:class 这个 delegation 。 该节点不暂停,会自动往下一个流转。
  8. receiveTask ,接收任务。在引擎遇到此类型的节点时,引擎执行会自动暂停,等待外部调用signal方法。 当调用signal方法时,会驱动流程当前节点离开。 在离开该节点时,引擎会自动执行 smart:class 这个 delegation。 在一般业务场景中,我们通常使用receiveTask来表示等需要等待外部回调的节点。
  9. userTask ,表示用户任务节点,仅用于DataBase模式。该节点需要人工参与处理,并且通常需要在待办列表中展示。 在Custom 模式下,建议使用receiveTask来代替。
  10. parallelGateway,这个节点并未在上述流程定义中体现,这里详细说一下。 parallelGateway 首先必须成对出现,分别承担fork 和join 职责。 其次,在join时需要实现分布式锁接口:LockStrategy。第三,fork 默认是顺序遍历多个sequeceFlow,但是你如果需要使用并发fork功能的话,则需要实现该接口:ExecutorService

大致了解一下标签的定义即可,最终这些片段都可以通过Camunda,定义好流程直接转化成xml文件

三、SmartEngine之Custom实操

无论是使用Custom 还是DataBase 模式,以下均是必须了解的知识。

  1. 第一步,要选择正确的SmartEngine 版本,将其添加到pom依赖中。
  2. 第二步,要实现InstanceAccessor接口。 这个接口主要便于SmartEngine和Spring等IOC框架集成,获取各种微服务的bean。 SmartEngine 会根据流程定义中的smart:class属性值,在结合InstanceAccessor的实现类,去调用Delegation。值得一提的是,smart:class 的属性值可以使className 或者 beanName,只要在逻辑上和InstanceAccessor的实现类保持一致即可。
  3. 第三步,完成SmartEngine初始化。在初始化时,一般要加载流程定义到应用中。 集群情况下,要注意流程定义的一致性(如果纯静态记载则无此类问题)。 在初始化时,可以根据需要定义Bean的加载优先级。

1、引入依赖

<dependency>
    <groupId>com.alibaba.smart.framework</groupId>
    <artifactId>smart-engine-extension-storage-custom</artifactId>
    <version>3.0.0</version>
</dependency>

2、典型的初始化代码如下

package com.ibuscloud.order.core.service.flow;

import com.alibaba.smart.framework.engine.SmartEngine;
import com.alibaba.smart.framework.engine.configuration.InstanceAccessor;
import com.alibaba.smart.framework.engine.configuration.ProcessEngineConfiguration;
import com.alibaba.smart.framework.engine.configuration.impl.DefaultProcessEngineConfiguration;
import com.alibaba.smart.framework.engine.configuration.impl.DefaultSmartEngine;
import com.alibaba.smart.framework.engine.exception.EngineException;
import com.alibaba.smart.framework.engine.service.command.RepositoryCommandService;
import com.alibaba.smart.framework.engine.util.IOUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import java.io.InputStream;

import static org.springframework.core.Ordered.LOWEST_PRECEDENCE;

/**
 * @Author dotaer
 * @Date 2023/4/30
 */
@Order(LOWEST_PRECEDENCE)
@Configuration
@ConditionalOnClass(SmartEngine.class)
public class SmartEngineAutoConfiguration implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Bean
    @ConditionalOnMissingBean
    public SmartEngine constructSmartEngine() {
        ProcessEngineConfiguration processEngineConfiguration = new DefaultProcessEngineConfiguration();
        // 实现InstanceAccessor接口
        processEngineConfiguration.setInstanceAccessor(new CustomInstanceAccessService());

        SmartEngine smartEngine = new DefaultSmartEngine();
        smartEngine.init(processEngineConfiguration);

        // 加载流程定义
        deployProcessDefinition(smartEngine);

        return smartEngine;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    private class CustomInstanceAccessService implements InstanceAccessor {
        @Override
        public Object access(String name) {
            return applicationContext.getBean(name);
        }
    }

    private void deployProcessDefinition(SmartEngine smartEngine) {
        RepositoryCommandService repositoryCommandService = smartEngine
                .getRepositoryCommandService();

        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        try {
            Resource[] resources = resolver.getResources("classpath*:/smart-engine/*.xml");
            for (Resource resource : resources) {
                InputStream inputStream = resource.getInputStream();
                repositoryCommandService.deploy(inputStream);
                IOUtil.closeQuietly(inputStream);
            }
        } catch (Exception e) {
            throw new EngineException(e);
        }

    }
}

其中实现InstanceAccessor接口是为了让SmartEngine能在Spring容器中找到对应的流程节点bean。比如说通过Camunda有这样一个创建订单的节点,其bean的名称为createOrderActivity,你就可以定义一个类为CreateOrderActivity并实现JavaDelegation接口,那么SmartEngine 会根据流程定义中的smart:class属性值也就是对应的bean的名称createOrderActivity,在结合InstanceAccessor的实现类,去调用Delegation也就是这个bean,执行成功后,会自动的流转到下一个节点serviceTask节点(对应Camunda就是左上角为小齿轮的节点),遇到receiveTask(对应Camunda就是左上角为白色信封的节点)就会暂停。

3、节点如何流转以及流程实例存储问题

Custom 模式支持持久化流程实例数据和不持久化流程实例数据。 通常针对服务编排场景,是不需要持久化流程实例相关信息的;但是如果需要暂停后并恢复继续执行的也就是如果遇到receiveTask这样的节点,则需要考虑持久化流程实例到存储介质里。

在如下这个代码片段中,需要重点关注如下事项:

  1. Custom 模式必须在 try,finally块中使用 PersisterSession.create()和 PersisterSession.destroySession() 。 SmartEngine 内部执行时,会依赖该Session,从该session获取流程实例数据。
  2. mockCreateOrder方法的内容:模拟启动流程实例。然后将流程实例序列化一个字符串后,存储到你持久化介质中。
  3. signal:模拟驱动业务流程。该过程和启动流程实例类似,都是将signal返回的流程实例序列化后更新到持久化介质中,然后在需要使用的时候,再取出来。
  4. BusinessProcess 这个类由开发者自行决定,仅是个示例,服务编排场景忽略这种代码即可。
        @Autowired
        private SmartEngine smartEngine;

        public ProcessInstance mockCreateOrder(Order order) {
        // 1、流程上下文需要的参数
        Map<String, Object> request = new HashMap<>();
        request.put("order", order);
        try {
            PersisterSession.create();
            ProcessCommandService processCommandService = smartEngine.getProcessCommandService();
            // 其中processDefinitionId和processDefinitionVersion对应bpmn中的 process标签的id和versionTag
            // 调用start则流程流转开始
            ProcessInstance processInstance = processCommandService.start(processDefinitionId, processDefinitionVersion, request);

            // 将流程实例介质更新到库中,方便后面取出
            BusinessProcess businessProcess = new BusinessProcess();
            // 唯一id
            businessProcess.setUniqueId(order.getOrderNo());
     
            businessProcess.setProcessInstance(InstanceSerializerFacade.serialize(processInstance));
            myProcessInstacneService.updateProcessInstance(businessProcess);
            return processInstance;
        } catch (Exception e) {
            log.error("初始化流程失败", e);
            throw new RunTimeException("初始化流程失败");
        } finally {
            PersisterSession.destroySession();
        }
    }

 public void signal(Long uniqueId, String activityId, Map<String, Object> map) {
        try {
            PersisterSession.create();

            ExecutionQueryService executionQueryService = smartEngine.getExecutionQueryService();
            ExecutionCommandService executionCommandService = smartEngine.getExecutionCommandService();


            // 从存储介质中查询当前流程实例状态
            BusinessProcess businessProcess = myProcessInstacneService.findById(uniqueId);
            // 重新反序列化出来
            ProcessInstance processInstance = InstanceSerializerFacade.deserializeAll(businessProcess.getSerializedProcessInstance());

            PersisterSession.currentSession().setProcessInstance(processInstance);

            // 判断当前节点是否应该是处在正确的节点上
            List<ExecutionInstance> executionInstanceList = executionQueryService.findActiveExecutionList(processInstance.getInstanceId());
            boolean found = false;
            if (!CollectionUtils.isEmpty(executionInstanceList)) {
                for (ExecutionInstance executionInstance : executionInstanceList) {
                    if (executionInstance.getProcessDefinitionActivityId().equals(activityId)) {
                        found = true;


                        // 执行signal方法进行 流转到下一个节点
                        ProcessInstance newProcessInstance = executionCommandService.signal(executionInstance.getInstanceId(), map);

                        // 同样的将流转后节点更新,方便后面取出
                        BusinessProcess businessProcess = new BusinessProcess();
                        // 唯一id
                        businessProcess.setUniqueId(uniqueId);
                        bizInstance.setProcessInstance(InstanceSerializerFacade.serialize(newProcessInstance));
                        myProcessInstacneService.updateProcessInstance(businessProcess);

                    }
                }
                if (!found) {
                    LOGGER.error("No active executionInstance found for businessInstanceId " + uniqueId + ",activityId " + activityId);
                }

            } else {
                LOGGER.error("No active executionInstance found for businessInstanceId " + uniqueId + ",activityId " + activityId);
            }

        } finally {
            PersisterSession.destroySession();
        }
    }

其中序列化的的流程实例文本长这样:v1|4333,processDefinitionId:1.0.0,null,null,running|5033,null,WaitPayCallBackActivity,5133,true,|。 这个字符串的顺序依次是:序列化协议版本号,分隔符,流程实例,流程定义 id 和 version,父流程实例 id,父流程实例的执行实例 id,流程状态,分割符,(注释:后面是环节信息,可以有多个,用|分开),环节实例 id,环节实例 blockId(可忽略),执行实例 id,执行实例状态,分隔符

注意点:

  • 节点之间流转所需要的上下文参数可放在 Map<String, Object> request = new HashMap<>();中,包括互斥网关所需要用到的条件,都需要从这个上下文中获取,调用start初始化或者调用signal携带进去即可
  • 所有ServiceTask节点会自动流转到下一节点,无须调用signal方法,直到遇到ReceiveTask这样的节点才会暂停,而ReceiveTask节点需调用signal方法才会流转到下一节点
  • 所有流程节点流转每流转一次,需将新的流程实例存储到实例介质中,根据系统情况,选择合适的存储介质,以防丢失。

4、定义Delegation

@Component
@Slf4j
public class CreateOrderActivity implements JavaDelegation {

    @Autowired
    private OrderService orderService;

    @Override
    public void execute(ExecutionContext executionContext) {
        Map<String, Object> request = executionContext.getRequest();
        if (request != null && request.containsKey("order")) {
            Order order = (Order) request.get("order");
            maasOrderCoreService.createOrder(maasOrder);
            log.info("create order {}", maasOrder);
        } else {
            log.error("Invalid request, no order", new IllegalArgumentException());
        }
    }
}

这样调用processCommandService.start过后就会根据bpmn里面smart:class属性值找到这个bean进行执行,执行过后,自动的流转到下一个节点,直到遇到ReceiveTask等这样的节点才会暂停。大家可以根据自身业务去实现对应的JavaDelegation即可。

补充:

重要领域对象

  1. 部署实例: DeploymentInstance,描述这个流程定义是谁发布的,当前处于什么状态。
  2. 流程定义: ProcessDefinition, 描述一个流程有几个环节,之间的流转关系是什么样子的。
  3. 流程实例: ProcessInstance,可以简单理解为我们常见的一个工单。
  4. 活动实例: ActivityInstance,主要是描述流程实例(工单)的流转轨迹。
  5. 执行实例: ExecutionInstance,主要根据该实例的状态,来判断当前流程处在哪个节点上。
  6. 任务实例: TaskInstance,用来表示人工任务处理的.可以理解为一个需要人工参与处理的环节。
  7. 任务处理:TaskAssigneeInstance,用来表示当前任务共有几个处理者。通常在代办列表中用到此实体。
  8. 变量实例:VariableInstance,用来存储流程实例上下文。

关键类

  1. ExecutionContextgetExecutionInstance() 方法可以通过这个对象获得当前环节的id;getBaseElement() 方法可以通过这个对象获得当前环节的id的流程定义配置;getRequest() 方法可以获得业务请求参数;getResponse() 方法设置返回值;getProcessDefinition() 方法可以获得完成的流程定义;

参考链接:

SmartEngine UserGuide Chinese Version (中文版) · alibaba/SmartEngine Wiki · GitHub

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

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

相关文章

RebbitMQ Windows安装

RabbitMQ是由Erlang语言写的,因此安装前要先安装Erlang Erlang及RabbitMQ安装版本的选择 下载时一定要注意版本兼容性 版本兼容说明地址&#xff1a;RabbitMQ Erlang Version Requirements — RabbitMQ 我们选择的版本 Erlang官网下载:https://www.erlang.org/downloads Ra…

[大家的项目] 获取主机IP地址

get-host-ip https://github.com/stuartZhang/get-host-ip/tree/main 这是一款用rust制作的命令行工具。其被设计用来从C:\Windows\System32\ipconfig.exe的执行结果内提取出指定【网卡】的属性值。比如&#xff0c;从电脑本的【无线局域网适配器WLAN】中提取出【IPv4地址】字符…

写给开发同学的 AI 强化学习入门指南

该篇文章是我学习过程的一些归纳总结&#xff0c;希望对大家有所帮助。 最近因为 AI 大火&#xff0c;搞的我也对 AI 突然也很感兴趣&#xff0c;于是开启了 AI 的学习之旅。其实我也没学过机器学习&#xff0c;对 AI 基本上一窍不通&#xff0c;但是好在身处在这个信息爆炸的…

数据结构---栈的实现

文章目录 前言一、什么是栈&#xff1f;二、栈的实现 1.栈的结构2.栈的接口实现过程总结 前言 栈&#xff08;stack&#xff09;又名堆栈&#xff0c;它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶&#xff0c;相对地&#xff0c;把另一…

用ChatGPT通过WebSocket开发一个交互性的五子棋微信小程序(二)

文章目录 1 前言1.1 实现的原理1.2 如何与微信小程序联系 2 五子棋项目2.1 申请OpenAI的API2.2 调用API代码2.3 界面代码 3 同步五子棋到前端小程序3.1 WebSocket长连接3.2 获取实时下棋 4 讨论 1 前言 1.1 实现的原理 大体方向是将ChatGPT作为后端语言模型&#xff0c;然后将…

AD19 基础应用技巧(差分线的添加走线与蛇形等长)

《差分线的添加走线与蛇形等长》 问:何为差分信号? 答:通俗地说&#xff0c;就是驱动端发送两个等值、反相的信号&#xff0c;接收端通过比较这两个电压的差值来判断逻辑状态“0”还是“1”。 问:差分线的优势在哪? 答:差分信号和普通的单端信号走线相比&#xff0c;最明量…

【SpringBoot2】三:基础入门---自动配置原理(自动配置原理入门+开发技巧)

文章目录 1.自动配置原理入门1.1 引导加载自动配置类1.2 按需开启自动配置项1.3 修改默认配置1.4 最佳实践 2.开发小技巧2.1 Lombok2.1.1 简化Bean开发2.1.2 简化日志开发 2.2 dev-tools2.3 Spring Initailizr&#xff08;项目初始化向导&#xff09; 1.自动配置原理入门 1.1 …

【数据库】Java的JDBC编程(idea链接数据库)

目录 前言 1、Java的数据库编程&#xff1a;JDBC 2、使用JDBC&#xff08;项目中导入数据库驱动包&#xff09; 2.1、获取驱动包 2.2、将数据库驱动包导入Java项目中 2.3、使用JDBC编写代码 2.3.1、创建并初始化一个数据源 2.3.2、 和数据库服务器建立链接 2.3.3、构…

C++(多态上)

目录: 1.多态的概念 2.多态的定义和实现 3.虚函数构成重写的特例 4.剖析一道非常经典的题 5.剖析多态的原理 ------------------------------------------------------------------------------------------------------------------------- 1.多态的概念 概念:通俗来说&#…

嵌入式开发--无刷电机学习2--克拉克变换

克拉克变换 首先说明&#xff0c;有很多方法&#xff0c;在数学上是等价的&#xff0c;比如33333*412。下面说的事情也是。 为了更简明的控制&#xff0c;克拉克女士提出电机控制简化的方法&#xff0c;即建立一个坐标系&#xff0c;横轴是α 纵轴是β&#xff0c;并将三相电…

RabbitMQ入门Demo 简单模式

出现的问题,原本4个操作,要么全部执行,要么全部不执行------->强一致性 但是现在分开了-----------最终一致性 强一致性&#xff1a;指在消息传递的过程中&#xff0c;系统会确保每个消息被精确地按照发送的顺序被传递&#xff0c;并且每个消息都会被正确地处理。强一致性…

重大问题,Windows11出现重大BUG

重大问题&#xff0c;Windows11出现重大BUG 这种Windows11操作系统出现BUG已经可以说是非常常见的&#xff0c;但是&#xff0c;今天我将代表所有微软用户&#xff0c;解决一个关于UI设计非常不舒服的功能 关闭多平面覆盖 事情叙述问题 微软社区解决方案自己发现的解决方案解决…

模拟比较器(Comparator)

概述 ⚫ 两个比较器&#xff0c;Comp1为低功耗比较器&#xff0c;Comp2为rail-to-rail快速比较器 ⚫比较器负端输入为vref或者IO输入&#xff0c;比较器正端为IO输入 ⚫ Buffer有Bypass功能&#xff0c;Bypass使能有效则不经过Buffer直接输入至比较器 ⚫ Buffer有1/2分压功能 ⚫…

JSON.stringfy() 和 qs.stringfy()区别 以及post/get 的参数形式

axios中post请求 application/json和 application/x-www-form-urlencoded 前端向后端传输数据时&#xff0c;如果是get传输&#xff0c;直接传在url后&#xff1b;如果是post传输&#xff0c;则在请求体body中传输。 在body中的数据格式又有两种&#xff0c;一种是 json 数据…

【Linux】教你用进程替换制作一个简单的Shell解释器

本章的代码可以访问这里获取。 由于程序代码是一体的&#xff0c;本章在分开讲解各部分的实现时&#xff0c;代码可能有些跳跃&#xff0c;建议在讲解各部分实现后看一下源代码方便理解程序。 制作一个简单的Shell解释器 一、观察Shell的运行状态二、简单的Shell解释器制作原理…

Python+Yolov8+Deepsort入口人流量统计

程序示例精选 PythonYolov8Deepsort入口人流量统计 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonYolov8Deepsort入口人流量统计>>编写代码&#xff0c;代码整洁&#x…

[java]云HIS运维运营分系统功能实现(springboot框架)

运维运营分系统 一级菜单包括&#xff1a;系统运维、综合监管、系统运营 系统运维包括二级菜单&#xff1a;环境管理、应用管理、菜单管理、接口管理、任务管理、配置管理 综合监管包括二级菜单&#xff1a;综合监管 系统运营包括二级菜单&#xff1a;机构管理、药品目录管…

计算机组成原理4.2.3提高存储器访问速度的措施

提高存储器访问层次大概有三种方法 采用高速器件 采用层次结构 Cache 主存 调整主存结构 调整存储结构 单体多字系统 利用程序局部性原理&#xff0c;访问一个块 相邻的若干块都会被拿出来&#xff0c;缺点可能会碰到跳转类指令 多体并行系统 高位是体号&#xff0c;低位时地…

手动搭建高可用的 kubernetes 集群(v1.16.6)

手动搭建高可用的 kubernetes 集群(v1.16.6) 目录 手动搭建高可用的 kubernetes 集群(v1.16.6) 1、组件版本和配置策略 1.1 主要组件版本1.2 主要配置策略2、初始化系统和全局变量 2.1 集群规划2.2 初始化系统环境 2.2.1 关闭防火墙2.2.2 关闭 swap 分区2.2.3 关闭 SELinux2.2.…

【网络技术】什么是CNI

序言 你只管努力&#xff0c;其他交给时间&#xff0c;时间会证明一切。 Never look back unless you are planning to go that way. 文章标记颜色说明&#xff1a; 黄色&#xff1a;重要标题红色&#xff1a;用来标记结论绿色&#xff1a;用来标记一级论点蓝色&#xff1a;用…