Activi7工作流经典实战(附:常用流程流转代码片段)

news2024/12/25 13:11:56

一、Activiti7介绍

Activiti正是目前使用最为广泛的开源工作流引擎。Activiti的官网地址是 https://
www.activiti.org 历经6.x和5.x两个大的版本。
在这里插入图片描述

1. Activiti工作流引擎

他可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义。业务流程按照预先定义的流程执行,整个实现流程完全由activiti进行管理

获取流程引擎的两种方式:

    ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration");
    ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();

默认方式

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService processEngine = processEngine.getRepositoryService();

注: 如果与springBoard整合的话,项目启动的时候会自动部署resources/processes目录下流程资源及自动获取流程引擎

2. 建模语言BPMN

整个BPMN是用一组符号来描述业务流程中发生的各种事件的。BPMN通过在这些符号事件之间连线来描述一个完整的业务流程。

  • 在线设计地址: http://www.bpmnmodeler.com/
  • SpringBoot集成activitimodeler实现在线绘制流程图

在这里插入图片描述

  • idea开发工具安装插件 Activiti BPMN visualizer(重启生效)

在这里插入图片描述

3. Activiti使用步骤

  • 部署activiti: Activiti包含一堆Jar包,因此需要把业务系统和Activiti的环境集成 在一起进行部署。
  • 定义流程: 使用Activiti的建模工具定义业务流程.bpmn文件。
  • 部署流程定义: 使用Activiti提供的API把流程定义内容存储起来,在Acitivti执行过程汇总可以查询定义的内容。Activiti是通过数据库来存储业务流程的。
  • 启动流程实例: 流程实例也叫ProcessInstance。启动一个流程实例表示开始一次业务流程的运作。例如员工提交请假申请后,就可以开启一个流程实例,从而 推动后续的审批等操作。
  • 用户查询待办任务(task):因为现在系统的业务流程都交给了activiti管理,通过activiti就可以查询当前流程执行到哪个步骤了。当前用户需要办理哪些任务也就同样可以由activiti帮我们管理,开发人员不需要自己编写sql语句进行查询了。
  • 用户办理任务:用户查询到自己的待办任务后,就可以办理某个业务,如果这个业务办理完成还需要其他用户办理,就可以由activiti帮我们把工作流程往后面的 步骤推动。
  • 流程结束:当任务办理完成没有下一个任务节点后,这个流程实例就执行完成 了。

二、Activiti核心类

当拿到ProcessEngine之后,我们可以简单的看一下他的方法
这几个service就是activiti最为核心的几个服务实现类。围绕activiti的核心业务功能 大都通过这几个service来组成。

在这里插入图片描述

service名称service作用
RepositoryServiceactiviti的资源管理类
RuntimeServiceactiviti的流程运行管理类
TaskServiceactiviti的任务管理类
HistoryServiceactiviti的历史管理类
ManagerServiceactiviti的引擎管理类
  • RepositoryService

    是activiti的资源管理类,提供了管理和控制流程发布包和流程定义的操作。
    使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。
    除了部署流程定义以外还可以:查询引擎中的发布包和流程定义。
    暂停或激活发布包,对应全部和特定流程定义。
    暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。
    获得多种资源,像是包含在发布包里的文件, 或引擎自动生成的流程图。

  • RuntimeService
    Activiti的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息

  • TaskService
    Activiti的任务管理类。可以从这个类中获取任务的信息。

  • HistoryService
    Activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的 参与者, 完成任务的时间,每个流程实例的执行路径等等。 这个服务主要通过查询功能来获得这些数据。

  • ManagementService
    Activiti的引擎管理类,提供了对 Activiti 流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于 Activiti 系统的日常维护

三、流程部署

如果与springBoard整合的话,项目启动的时候会自动部署resources/processes目录下流程资源及自动获取流程引擎

1、流程部署的两种方式,手动部署:

 @Test
    public void deployProcess(){
        Deployment deployment = repositoryService.createDeployment()
                .addClasspathResource("bpmn/incluProcess.bpmn20.xml") // 添加bpmn资源
                //png资源命名是有规范的。Leave.[key].[png|jpg|gif|svg] 或者Leave.[png|jpg|gif|svg]
                .addClasspathResource("bpmn/incluProcess.png") // 添加png资源
                .name("流程部署名称")
                .deploy();
        // 4、输出部署信息
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }

2、手动压缩包方式部署:

 @Test
    public void deployProcessByZip() {
       // 定义zip输入流
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("bpmn/incluProcess.zip");
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
       // 流程部署
        Deployment deployment = repositoryService.createDeployment()
                .addZipInputStream(zipInputStream)
                .name("流程部署名称")
                .deploy();
        System.out.println("流程部署id:" + deployment.getId());
        System.out.println("流程部署名称:" + deployment.getName());
    }

3、根据流程部署id获取流程部署资源:

	@Test
    public void  queryBpmnFile() throws IOException {
        //流程定义key
        String processDefinitionKey = "incluProcess";
        //得到查询器:ProcessDefinitionQuery,设置查询条件,得到想要的流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(processDefinitionKey)
                .latestVersion()//最新版本
                .singleResult();
                //.list();
        //通过流程定义信息,得到部署ID
        String deploymentId = processDefinition.getDeploymentId();
        //通过repositoryService的方法,实现读取图片信息和bpmn信息
        //png图片的流
        InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName());
        //bpmn文件的流
        InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
        //构造OutputStream流
        File file_png = new File("d:/incluProcess.png");
        File file_bpmn = new File("d:/incluProcess.bpmn20.xml");
        FileOutputStream bpmnOut = new FileOutputStream(file_bpmn);
        FileOutputStream pngOut = new FileOutputStream(file_png);
        //输入流,输出流的转换
        IOUtils.copy(pngInput,pngOut);
        IOUtils.copy(bpmnInput,bpmnOut);
        //关闭流
        pngOut.close();
        bpmnOut.close();
        pngInput.close();
        bpmnInput.close();
    }

4、根据流程部署id删除流程部署:

	@Test
    public void deleteDeployment() {
        //流程部署id
        String processDeploymentId = "f46f3734-e4bb-11ed-822f-9c5c8e74980c";
        //删除流程定义,如果该流程定义已有流程实例启动则删除时出错
		//repositoryService.deleteDeployment(processDeploymentId);
        //设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
        repositoryService.deleteDeployment(processDeploymentId, true);
    }

四、流程定义与流程实例

流程定义 ProcessDefinition 和流程实例ProcessInstance是Activiti中非常重要的两个概念。他们的关系其实类似于JAVA中类和对象的概念。
流程定义ProcessDefinition是以BPMN文件定义的一个工作流程,是一组工作规范。例如我们之前定义的请假流程。
流程实例ProcessInstance则是指一个具体的业务流程。例如某个员工发起一次请假,就会实例化一个请假的流程实例,并且每个
不同的流程实例之间是互不影响的。

1、启动流程实例:

/**
     * 启动流程实例(设置流程变量的值和添加businessKey)
     * 同一个流程定义key部署多个版本,默认使用最新版本
     * 一个流程定义下有多个实例
     */
    @Test
    public void startProcess(){
        //流程定义key
        String processDefinitionKey = "incluProcess";
        //businessKey业务关键字
        // 可以根据业务场景,设计成不同的数据格式,比如关键信息逗号拼接,甚至是json都可以,唯一需要注意的是这个字段的数据库长度设计是255
        String businessKey = "{\"workerId\": 1,\"departmentId\": \"123\",\"roleId\": 2,\"other\": 3}";
        //创建Global流程变量(启动流程时),在线程实例中传递的流程变量。这个流程变量可以在整个流程实例中使用
        Map<String, Object> map = new HashMap<>();
        map.put("assignee0","tom");
        map.put("assignee1","jack");
        //启动流程实例,并设置流程变量的值(把map传入)&&启动流程实例时,添加Businesskey,可以用这个businessKey来关联业务
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey,businessKey,map);
        //输出
        System.out.println("流程定义id=="+processInstance.getProcessDefinitionId());//流程定义key:版本号:流程部署ID ==> incluProcess:1:1b99a469-e425-11ed-a092-9c5c8e74980c
        System.out.println("流程实例名称="+processInstance.getName());
        System.out.println("流程实例id:" + processInstance.getId());//56987f81-e429-11ed-8ad9-9c5c8e74980c
        System.out.println("当前活动Id:" + processInstance.getActivityId());
        System.out.println("BusinessKey:" +  processInstance.getBusinessKey());

    }

2、流程定义

查询流程定义:

    @Test
    public void queryProcessDefinition(){
        //流程定义key
        String processDefinitionKey = "incluProcess";
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        //查询出当前所有的流程定义
        List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey(processDefinitionKey)
                .orderByProcessDefinitionVersion()
                .desc()按照版本倒叙
                .list();
        //输出流程定义信息
        for (ProcessDefinition processDefinition : definitionList) {
            System.out.println("流程部署ID ="+processDefinition.getDeploymentId());//1b598e26-e425-11ed-a092-9c5c8e74980c
            System.out.println("流程定义 id="+processDefinition.getId());//incluProcess:1:1b99a469-e425-11ed-a092-9c5c8e74980c
            System.out.println("流程定义 name="+processDefinition.getName());//出差申请-包含网关
            System.out.println("流程定义 key="+processDefinition.getKey());//incluProcess
            System.out.println("流程定义 Version="+processDefinition.getVersion());//1
            System.out.println("<==========================>");
        }
    }

流程定义的挂起与激活:

repositoryService.suspendProcessDefinitionById(processDefinitionId);
repositoryService.activateProcessDefinitionById(processDefinitionId);

3、流程实例

查询流程实例:

 @Test
    public void queryProcessInstance() {
        //流程定义key
        String processDefinitionKey = "incluProcess";
        List<ProcessInstance> list = runtimeService
                .createProcessInstanceQuery()
                .processDefinitionKey(processDefinitionKey)
				//.processInstanceId("9f50db66-e4a0-11ed-bd31-9c5c8e74980c")
                .list();
        for (ProcessInstance processInstance : list) {
            System.out.println("所属流程定义id:"+ processInstance.getProcessDefinitionId());//incluProcess:1:1b99a469-e425-11ed-a092-9c5c8e74980c
            System.out.println("所属流程定义 Version="+processInstance.getProcessDefinitionVersion());
            System.out.println("流程实例id:"+ processInstance.getProcessInstanceId());//56987f81-e429-11ed-8ad9-9c5c8e74980c
            System.out.println("是否执行完成:" + processInstance.isEnded());//false
            System.out.println("是否暂停:" + processInstance.isSuspended());//false
            System.out.println("当前活动标识:" + processInstance.getActivityId());//null
            System.out.println("业务关键字:"+processInstance.getBusinessKey());//null
            System.out.println("流程变量 Variables="+ JSON.toJSONString(processInstance.getProcessVariables()));
            System.out.println("<==========================>");
        }
    }

流程实例的挂起与激活:

runtimeService.suspendProcessInstanceById(processInstanceId);
runtimeService.activateProcessInstanceById(processInstanceId);

4、查看历史信息:

@Test
    public void findHistoryInfo(){
        //获取 actinst表的查询对象
        HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery();
        //查询 actinst表,条件:根据 DefinitionId 查询,查询一种流程定义的所有历史信息
        instanceQuery.processDefinitionId("incluProcess:1:9fe8bf00-e4d9-11ed-8e1f-9c5c8e74980c");
        //查询 actinst表,条件:根据 InstanceId 查询,查询一个流程实例的所有历史信息
		//instanceQuery.processInstanceId("a65107ee-e42e-11ed-b64e-9c5c8e74980c");
        //增加排序操作,orderByHistoricActivityInstanceStartTime 根据开始时间排序 asc 升序
        instanceQuery.orderByHistoricActivityInstanceStartTime().asc();
        //查询所有内容
        List<HistoricActivityInstance> activityInstanceList = instanceQuery.list();
        //输出
        for (HistoricActivityInstance hi : activityInstanceList) {
            System.out.println("流程定义id:"+hi.getProcessDefinitionId());
            System.out.println("流程实例id:"+hi.getProcessInstanceId());
            System.out.println("ActivityType:"+hi.getActivityType());
            System.out.println("ActivityId:"+hi.getActivityId());
            System.out.println("ActivityName:"+hi.getActivityName());
            System.out.println("执行人Assignee:"+hi.getAssignee());
            System.out.println("TaskId:"+hi.getTaskId());
            System.out.println("<==========================>");
        }
    }

五、流程变量

变量的作用域可以设置为Global和Local两种。

1. Global变量

这个是流程变量的默认作用域,表示是一个完整的流程实例。 Global变量中变量
名不能重复。如果设置了相同的变量名,后面设置的值会直接覆盖前面设置的变量值

  • 通过启动流程时候设置变量
ProcessInstance processInstance =runtimeService.startProcessInstanceByKey(key, map);
  • 通过当前流程实例设置变量
// 通过流程实例执行实例编号设置流程变量
runtimeService.setVariable(executionId, "evection", evection);
// 一次设置多个值
runtimeService.setVariables(executionId, variables)

注: executionId必须是当前未完成的流程实例的执行ID。通常此ID设 置流程实例的ID。流程变量设计完成后,也可以通过 runtimeService.getVariable()获取流程变量

  • 通过当前任务设置变量
//通过任务设置流程变量
taskService.setVariable(taskId, "evection", evection);
//一次设置多个值
taskService.setVariables(taskId, variables)

注: 任务id必须是当前待办任务id,act_ru_task中存在。如果该任务已结束,会报错。也可以通过taskService.getVariable()获取流程变量

  • 任务办理是设置变量
taskService.complete(task.getId(),map);

2. Local变量

Local变量的作用域只针对一个任务或一个执行实例的范围,没有流程实例大。
Local变量由于作用在不同的任务或不同的执行实例中,所以不同变量的作用域是
互不影响的,变量名可以相同。Local变量名也可以和Global变量名相同,不会有影响

  • 通过当前流程实例设置变量
// 通过流程实例执行实例编号设置流程变量
runtimeService.setVariableLocal(executionId, "evection", evection);
// 一次设置多个值
runtimeService.setVariableLocal(executionId, variables)
  • 通过当前任务设置变量
// 通过任务设置流程变量
taskService.setVariableLocal(taskId, "evection", evection);
// 一次设置多个值
taskService.setVariablesLocal(taskId, variables)

3. 使用流程变量

Activiti中可以使用UEL表达式来使用这些流程变量。UEL表达式可以直接获取一
个变量的值,可以计算一个Boolean结果的表达式,还可以直接使用某些对象的属性。

例如:
设置数组大于等于3: ${num <= 3}
也可以使用对象参数: ${eveaction.num <= 3}

六、网关

1、流程符号

网关是用来控制流程流向的重要组件,通常都会要结合流程变量来使用。
介绍网关之前先来了解一下流程符号,BPMN2.0的基本符号主要包含以下几类:

  • 事件 Event:

在这里插入图片描述 事件是驱动工作流发展的核心对象,在工作流的流程定制过程中会经常看到。

  • 活动 Activity:

在这里插入图片描述

活动是工作或任务的一个通用术语。一个活动可以是一个任务,也可以是当前流程
的子处理流程。并且,活动会有不同的类型。例如Activiti中定义了UserTask,ScriptTask,ServiceTask,MailTask等等多种类型。这些活动是构成整个业
务流程的主体。

  • 网关 gateWay:

在这里插入图片描述
网关是用来处理角色的,他决定了工作流的业务走向

  • 流向 Flow:

在这里插入图片描述
流就是连接两个流程节点的连线,代表了流程之间的关联关系。

2、 排他网关ExclusiveGateway*

排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条 件是否为true,如果为true则执行该分支

不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。所以为什么要用排他网关呢

因为设置condition条件存在缺点:如果条件都不满足,流程就结束了(是异常结束)。
如果从网关出去的线所有条件都不满足则系统抛出异常。

排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会按指定规则选择一条分支去执行

3、 并行网关ParallelGateway

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功 能是基于进入和外出顺序流的:
fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚: 所有到达并行网关,在此等待的进入分支,
直到所有进入顺序流的分支 都到达以后, 流程就会通过汇聚网关。

与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略

4、包含网关InclusiveGateway

和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主 要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续 执行, 会为每个顺序流创建一个分支。
汇聚: 所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的 进入顺序流的分支都到达。
这是与并行网关的最大不同。换句话说,包含网关只会 等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网关继续执行。

包含网关可以看做是排他网关和并行网关的结合体。

5、事件网关EventGateway

事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获 事件。
当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同 时,会为每个外出顺序流创建相对的事件订阅。
事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们 让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。

在这里插入图片描述

七、Activiti7与SpringBoot整合开发

1、 引入maven依赖

<dependency>
    <groupId>org.activiti</groupId>
    <artifactId>activiti-spring-boot-starter</artifactId>
    <version>7.0.0.Beta2</version>
	<exclusions>
		<exclusion>
			<artifactId>mybatis</artifactId>
			<groupId>org.mybatis</groupId>
		</exclusion>
	</exclusions>
</dependency>

2、 创建配置文件application.yml

spring:
  # activiti 配置
  activiti:
    #1.flase:默认值。activiti在启动时,对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常
    #2.true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建
    #3.create_drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)
    #4.drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)
    database-schema-update: true
    #检测历史表是否存在 activiti7默认没有开启数据库历史记录 启动数据库历史记录
    db-history-used: true
    #记录历史等级 可配置的历史级别有none, activity, audit, full
    #none:不保存任何的历史数据,因此,在流程执行过程中,这是最高效的。
    #activity:级别高于none,保存流程实例与流程行为,其他数据不保存。
    #audit:除activity级别会保存的数据外,还会保存全部的流程任务及其属性。audit为history的默认值。
    #full:保存历史数据的最高级别,除了会保存audit级别的数据外,还会保存其他全部流程相关的细节数据,包括一些流程参数等。
    history-level: full
    #校验流程文件,默认校验resources下的processes文件夹里的流程文件
    check-process-definitions: false

3、 在resources/processes目录下创建BPMN文件

将设计好的

支持这两种格式:
在这里插入图片描述

4、 使用junit方式测试

在这里插入图片描述

八、附:常用流程流转代码片段

1、流程的发起、待办、任务办理、驳回、跳转、撤销

发起流程

    @Override
    public int insertLeaveapply(Leaveapply leaveapply)
    {
        int rows = leaveapplyMapper.insertLeaveapply(leaveapply);
        // 发起请假流程
        identityService.setAuthenticatedUserId(leaveapply.getUserId());

        HashMap<String, Object> variables = new HashMap<>();
        variables.put("applyuserid", leaveapply.getUserId());
        variables.put("deptleader", leaveapply.getDeptleader());
        runtimeService.startProcessInstanceByKey("leave", String.valueOf(leaveapply.getId()), variables);
        // 自动完成第一个任务,根据流程定义key和businessKey获取任务ID
        Task autoTask = taskService.createTaskQuery().processDefinitionKey("leave").processInstanceBusinessKey(String.valueOf(leaveapply.getId())).singleResult();
        taskService.complete(autoTask.getId());
        return rows;
    }

查询任务待办列表

    @PostMapping("/mylist")
    @ResponseBody
    public TableDataInfo mylist(TaskInfo param)
    {
        SysUser user = getSysUser();
        String username = user.getLoginName();
        //构建任务查询条件
        TaskQuery condition = taskService.createTaskQuery().taskAssignee(username);
        if (StringUtils.isNotEmpty(param.getTaskName())) {
            condition.taskName(param.getTaskName());
        }
        if (StringUtils.isNotEmpty(param.getProcessName())) {
            condition.processDefinitionName(param.getProcessName());
        }
        // 过滤掉流程挂起的待办任务
        int total = condition.active().orderByTaskCreateTime().desc().list().size();
        int start = (param.getPageNum()-1) * param.getPageSize();
        //根据构建的任务条件分页查询任务列表
        List<Task> taskList = condition.active().orderByTaskCreateTime().desc().listPage(start, param.getPageSize());
        List<TaskInfo> tasks = new ArrayList<>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        taskList.stream().forEach(a->{
            //根据任务列表中的所属流程实例ID获取流程实例对象
            ProcessInstance process = runtimeService.createProcessInstanceQuery().processInstanceId(a.getProcessInstanceId()).singleResult();
            TaskInfo info = new TaskInfo();
            info.setAssignee(a.getAssignee());//任务执行人(办理人)
            info.setCreateTime(sdf.format(a.getCreateTime()));//任务创建时间
            info.setTaskName(a.getName());//任务名称
            info.setProcessInstanceId(a.getProcessInstanceId());//流程实例ID
            info.setTaskId(a.getId());//任务ID
            info.setExecutionId(a.getExecutionId());//执行实例编号ID
            String formKey = formService.getTaskFormData(a.getId()).getFormKey();
            info.setFormKey(formKey);//流程自定义表单Key
            //从流程实例中获取流程定义名称、流程开始人、businessKey、流程启动事件
            info.setProcessName(process.getProcessDefinitionName());//流程定义名称
            info.setStarter(process.getStartUserId());//任务发起人
            info.setBusinessKey(process.getBusinessKey());//businessKey
            info.setStartTime(sdf.format(process.getStartTime()));//流程启动事件
            tasks.add(info);
        });
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(0);
        rspData.setRows(tasks);
        rspData.setTotal(total);
        return rspData;
    }

流程审批

	@GetMapping("/deptleadercheck")
    public String deptleadercheck(String taskid, ModelMap mmap)
    {
        //根据taskId获取任务对象和流程实例
        Task t = taskService.createTaskQuery().taskId(taskid).singleResult();
        String processId = t.getProcessInstanceId();
        ProcessInstance p = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
        if (p != null) {
            //根据流程实例中的businessKey获取业务对象
            Leaveapply apply = leaveapplyService.selectLeaveapplyById(Long.parseLong(p.getBusinessKey()));
            mmap.put("apply", apply);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            mmap.put("startTime", sdf.format(apply.getStartTime()));
            mmap.put("endTime", sdf.format(apply.getEndTime()));
            mmap.put("taskid", taskid);
            mmap.put("userlist", userService.selectUserList(new SysUser()));
        }
        return prefix + "/deptleadercheck";
    }

办理任务

	@RequestMapping(value = "/completeTask/{taskId}", method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult completeTask(@PathVariable("taskId") String taskId, @RequestBody(required=false) Map<String, Object> variables) {
        SysUser user = getSysUser();
        String username = user.getLoginName();
        //设置办理任务的执行人
        taskService.setAssignee(taskId, username);
        // 查出流程实例id
        String processInstanceId = taskService.createTaskQuery().taskId(taskId).singleResult().getProcessInstanceId();
        if (variables == null) {
            taskService.complete(taskId);
        } else {
            // 添加审批意见
            if (variables.get("comment") != null) {
                //根据流程实例ID和任务ID添加审批意见
                taskService.addComment(taskId, processInstanceId, (String) variables.get("comment"));
                variables.remove("comment");
            }
            taskService.complete(taskId, variables);
        }
        return AjaxResult.success();
    }

驳回或跳转到指定节点

	@GetMapping(value = "/jump/{taskId}/{sid}")
    @ResponseBody
    public AjaxResult jump(@PathVariable String taskId, @PathVariable String sid) {
        //根据任务id获取任务实例,通过流程定义ID获取bpmn模型
        Task t = taskService.createTaskQuery().taskId(taskId).singleResult();
        String processDefinitionId = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult().getProcessDefinitionId();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        //根据执行实例编号executionId获取流程图(FlowNode)中当前活动的id(activityId);寻找流程实例当前任务的activeId
        Execution execution = runtimeService.createExecutionQuery().executionId(t.getExecutionId()).singleResult();
        String activityId = execution.getActivityId();
        FlowNode currentNode = (FlowNode)bpmnModel.getMainProcess().getFlowElement(activityId);
        FlowNode targetNode = (FlowNode)bpmnModel.getMainProcess().getFlowElement(sid);
        // 创建连接线
        List<SequenceFlow> newSequenceFlowList = new ArrayList<SequenceFlow>();
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setId("newFlow");
        newSequenceFlow.setSourceFlowElement(currentNode);
        newSequenceFlow.setTargetFlowElement(targetNode);
        newSequenceFlowList.add(newSequenceFlow);
        // 备份原有方向
        List<SequenceFlow> dataflows = currentNode.getOutgoingFlows();
        List<SequenceFlow> oriSequenceFlows = new ArrayList<SequenceFlow>();
        oriSequenceFlows.addAll(dataflows);
        // 清空原有方向
        currentNode.getOutgoingFlows().clear();
        // 设置新方向
        currentNode.setOutgoingFlows(newSequenceFlowList);
        //添加流程说明并完成当前任务
        taskService.addComment(taskId, t.getProcessInstanceId(), "comment", "跳转节点");
        taskService.complete(taskId);
        // 恢复原有方向
        currentNode.setOutgoingFlows(oriSequenceFlows);
        return AjaxResult.success();
    }

撤销:强制结束一个流程

@GetMapping(value = "/forceEnd/{taskId}")
    @ResponseBody
    public AjaxResult forceEnd(@PathVariable String taskId) {
        Task t = taskService.createTaskQuery().taskId(taskId).singleResult();
        String processDefinitionId = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult().getProcessDefinitionId();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        // 寻找流程实例当前任务的activeId
        Execution execution = runtimeService.createExecutionQuery().executionId(t.getExecutionId()).singleResult();
        String activityId = execution.getActivityId();
        FlowNode currentNode = (FlowNode)bpmnModel.getMainProcess().getFlowElement(activityId);
        // 创建结束节点和连接线
        EndEvent end = new EndEvent();
        end.setName("强制结束");
        end.setId("forceEnd");
        List<SequenceFlow> newSequenceFlowList = new ArrayList<SequenceFlow>();
        SequenceFlow newSequenceFlow = new SequenceFlow();
        newSequenceFlow.setId("newFlow");
        newSequenceFlow.setSourceFlowElement(currentNode);
        newSequenceFlow.setTargetFlowElement(end);
        newSequenceFlowList.add(newSequenceFlow);
        // 备份原有方向
        List<SequenceFlow> dataflows = currentNode.getOutgoingFlows();
        List<SequenceFlow> oriSequenceFlows = new ArrayList<SequenceFlow>();
        oriSequenceFlows.addAll(dataflows);
        // 清空原有方向
        currentNode.getOutgoingFlows().clear();
        // 设置新方向并添加说明完成任务后恢复原有方向
        currentNode.setOutgoingFlows(newSequenceFlowList);
        // 完成当前任务
        taskService.addComment(taskId, t.getProcessInstanceId(), "comment", "撤销流程");
        taskService.complete(taskId);
        // 恢复原有方向
        currentNode.setOutgoingFlows(oriSequenceFlows);
        return AjaxResult.success();
    }

2、流程定义和流程实例可视化管理

查询已部署工作流列表

	@RequestMapping(value = "/getprocesslists", method = RequestMethod.POST)
    @ResponseBody
    public TableDataInfo getlist(@RequestParam(required = false) String key, @RequestParam(required = false) String name,
                                 @RequestParam(required = false) Boolean latest, Integer pageSize, Integer pageNum) {
        //构建流程定义条件查询实例
        ProcessDefinitionQuery queryCondition = repositoryService.createProcessDefinitionQuery();
        if (StringUtils.isNotEmpty(key)) {
            queryCondition.processDefinitionKey(key);
        }
        if (StringUtils.isNotEmpty(name)) {
            queryCondition.processDefinitionName(name);
        }
        if (latest) {
            queryCondition.latestVersion();
        }
        int total = queryCondition.list().size();
        int start = (pageNum - 1) * pageSize;
        //分页查询流程定义
        List<ProcessDefinition> pageList = queryCondition.orderByDeploymentId().desc().listPage(start, pageSize);
        List<Process> mylist = new ArrayList<Process>();
        for (int i = 0; i < pageList.size(); i++) {
            Process p = new Process();
            p.setDeploymentId(pageList.get(i).getDeploymentId());
            p.setId(pageList.get(i).getId());
            p.setKey(pageList.get(i).getKey());
            p.setName(pageList.get(i).getName());
            p.setResourceName(pageList.get(i).getResourceName());
            p.setDiagramresourceName(pageList.get(i).getDiagramResourceName());
            p.setVersion(pageList.get(i).getVersion());
            p.setSuspended(pageList.get(i).isSuspended());
            mylist.add(p);
        }
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(0);
        rspData.setRows(mylist);
        rspData.setTotal(total);
        return rspData;
    }

根据流程部署ID删除部署的工作流

	@RequestMapping(value = "/remove/{deploymentId}", method = RequestMethod.POST)
    @ResponseBody
    public AjaxResult remove(@PathVariable String deploymentId) {
        //根据流程部署ID删除部署的工作流
        repositoryService.deleteDeployment(deploymentId, true);
        return AjaxResult.success();
    }

根据流程定义ID查看工作流图片

@RequestMapping(value = "/showresource", method = RequestMethod.GET)
    public void showresource(@RequestParam("pdid") String pdid,
                       HttpServletResponse response) throws Exception {
        //根据流程定义ID查询工作流图片
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pdid);
        //@Resource
        //ProcessEngineConfiguration configuration;
        ProcessDiagramGenerator diagramGenerator = configuration.getProcessDiagramGenerator();
        InputStream is = diagramGenerator.generateDiagram(bpmnModel, "png",  "宋体", "宋体", "宋体", configuration.getClassLoader(), 1.0);
        ServletOutputStream output = response.getOutputStream();
        IOUtils.copy(is, output);
    }

根据流程定义ID定时唤醒一个流程定义

@RequestMapping(value = "/activateProcessDefinition", method = RequestMethod.GET)
    @ResponseBody
    public AjaxResult activateProcessDefinition(@RequestParam("pdid") String pdid, @RequestParam("flag") Boolean flag, @RequestParam(value="date") String date) throws Exception {
        //激活一个流程定义
        if (StringUtils.isNotEmpty(date)) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            repositoryService.activateProcessDefinitionById(pdid, flag,  sdf.parse(date));
        } else {
            repositoryService.activateProcessDefinitionById(pdid, flag, null);
        }
        return AjaxResult.success();
    }

根据流程定义ID定时挂起一个流程定义

	@RequestMapping(value = "/suspendProcessDefinition", method = RequestMethod.GET)
    @ResponseBody
    public AjaxResult suspendProcessDefinition(@RequestParam("pdid") String pdid, @RequestParam("flag") Boolean flag,
                                               @RequestParam(value="date") String date) throws Exception {
        //定时挂起一个流程定义
        if (StringUtils.isNotEmpty(date)) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            repositoryService.suspendProcessDefinitionById(pdid, flag,  sdf.parse(date));
        } else {
            repositoryService.suspendProcessDefinitionById(pdid, flag, null);
        }
        return AjaxResult.success();
    }

查询所有正在运行的流程实例列表

@RequestMapping(value = "/listProcess", method = RequestMethod.POST)
    @ResponseBody
    public TableDataInfo getlist(@RequestParam(required = false) String bussinesskey, @RequestParam(required = false) String name,
                                 Integer pageSize, Integer pageNum) {
        int start = (pageNum - 1) * pageSize;
        //构建流程实例条件查询实例
        ProcessInstanceQuery condition = runtimeService.createProcessInstanceQuery();
        if (StringUtils.isNotEmpty(bussinesskey)) {
            condition.processInstanceBusinessKey(bussinesskey);
        }
        if (StringUtils.isNotEmpty(name)) {
            condition.processDefinitionName(name);
        }
        int total = condition.orderByProcessDefinitionId().desc().list().size();
        //分页查询流程实例
        List<ProcessInstance> processList = condition.orderByProcessDefinitionId().desc().listPage(start, pageSize);
        List<FlowInfo> flows = new ArrayList<>();
        processList.stream().forEach(p -> {
            FlowInfo info = new FlowInfo();
            info.setProcessInstanceId(p.getProcessInstanceId());
            info.setBusinessKey(p.getBusinessKey());
            info.setName(p.getProcessDefinitionName());
            info.setStartTime(p.getStartTime());
            info.setStartUserId(p.getStartUserId());
            info.setSuspended(p.isSuspended());
            info.setEnded(p.isEnded());
            // 查看当前活动任务
            List<Task> tasks =  taskService.createTaskQuery().processInstanceId(p.getProcessInstanceId()).list();
            if (tasks.size() > 0) {
                String taskName = "";
                String assignee = "";
                for (Task t : tasks) {
                    taskName += t.getName() + ",";
                    assignee += t.getAssignee() + ",";
                }
                taskName = taskName.substring(0, taskName.length() -1);
                assignee = assignee.substring(0, assignee.length() -1);
                info.setCurrentTask(taskName);
                info.setAssignee(assignee);
            }
            flows.add(info);
        });
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(0);
        rspData.setRows(flows);
        rspData.setTotal(total);
        return rspData;
    }

根据流程实例ID流程实例的挂起

	@RequestMapping(value = "/suspend/{processInstanceId}", method = RequestMethod.GET)
    @ResponseBody
    public AjaxResult suspend(@PathVariable String processInstanceId) {
        //根据流程实例ID挂起一个流程实例
        runtimeService.suspendProcessInstanceById(processInstanceId);
        return AjaxResult.success();
    }

根据流程实例ID流程实例的唤醒

	@RequestMapping(value = "/run/{processInstanceId}", method = RequestMethod.GET)
    @ResponseBody
    public AjaxResult rerun(@PathVariable String processInstanceId) {
        //根据流程实例ID唤醒一个流程实例
        runtimeService.activateProcessInstanceById(processInstanceId);
        return AjaxResult.success();
    }

根据流程实例id生成流程追踪图

	public void generateFlowChart(String processInstanceId, OutputStream outputStream) {
        try {
            if (StringUtils.isEmpty(processInstanceId)) {
                logger.error("processInstanceId is null");
                return;
            }
            // 获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId)
                .singleResult();
            // 获取流程中已经执行的节点
            List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricActivityInstanceStartTime().asc().list();
            // 高亮节点-高亮已经执行流程节点ID集合
            List<String> highLightedActivitiIds = new ArrayList<>();
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                highLightedActivitiIds.add(historicActivityInstance.getActivityId());
            }

            ProcessDiagramGenerator processDiagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();

            BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
            // 高亮流程线-高亮流程已发生流转的线id集合
            List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstances);

            // 生成流程追踪图-使用默认配置获得流程图表生成器,并生成追踪图片字符流
            InputStream imageStream = processDiagramGenerator
                .generateDiagram(bpmnModel, "png", highLightedActivitiIds, highLightedFlowIds, "宋体", "微软雅黑", "黑体", null, 2.0);

            // 输出图片内容
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, 1024)) != -1) {
                outputStream.write(b, 0, len);
            }
        } catch (Exception e) {
            logger.error("processInstanceId" + processInstanceId + "生成流程图失败,原因:" + e.getMessage(), e);
        }
    }


	/**
     * 获取已经流转的线
     *
     * @param bpmnModel                 流程图模板
     * @param historicActivityInstances 历史实例
     */
    private List<String> getHighLightedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        // 高亮流程已发生流转的线id集合
        List<String> highLightedFlowIds = new ArrayList<>();
        // 已完成的活动节点
        List<FlowNode> historicActivityNodes = new ArrayList<>();

        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);
            historicActivityNodes.add(flowNode);
        }

        FlowNode currentFlowNode;
        FlowNode targetFlowNode;
        // 遍历已完成的活动实例,从每个实例的outgoingFlows中找到已执行的
        for (HistoricActivityInstance currentActivityInstance : historicActivityInstances) {
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
            List<SequenceFlow> sequenceFlows = currentFlowNode.getOutgoingFlows();

            // 通过outgoingFlows能够在历史活动中找到且目标节点和当前节点执行顺序相邻
            for (SequenceFlow sequenceFlow : sequenceFlows) {
                targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                if (historicActivityNodes.contains(targetFlowNode)) {
                    int target = historicActivityNodes.indexOf(targetFlowNode);
                    int current = historicActivityNodes.indexOf(currentFlowNode);
                    if (target - current == 1) {
                        //获取已经流转的线ID
                        highLightedFlowIds.add(sequenceFlow.getId());
                    }
                }
            }
        }
        return highLightedFlowIds;
    }

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

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

相关文章

彻底搞清楚Handler,再也不怕面试官

Handler Handler可以说是Android框架里面很精髓的一部分了&#xff0c;面试必问&#xff0c;用的也最多 Handler是什么&#xff1f; 提到Handler大家一定不陌生&#xff0c;我们经常用它来切换线程&#xff0c;或者是说做一些延时任务等等。最常用的地方可能就是在网络请求中…

Flask全栈解决小问题系列(1)搭建一个bootstrap开发框架

时间不多,闲话少说,实践出真知! 1.目的:为实现FlaskBootStrap开发效果,搞个开发测试项目 2.搭建项目 1)建个test-bootstrap项目,项目目录结构如下: 2)appstart.py内容如下: import json from flask import Flask,redirect,render_templateapp Flask("__main__") …

00后太卷了上班还没3年,跳到我们公司起薪18k....

都说00后已经躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。前段时间我们部门就来了个00后&#xff0c;工作都还没三年&#xff0c;跳到我们公司起薪18K&#xff0c;都快接近我了。 后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。最近和他…

Yolov5/Yolov7改进:小目标到大目标一网打尽,轻骨干重Neck的轻量级目标检测器GiraffeDet

1.GiraffeDet介绍 论文:https://arxiv.org/abs/2202.04256 🏆🏆🏆🏆🏆🏆Yolov5/Yolov7魔术师🏆🏆🏆🏆🏆🏆 ✨✨✨魔改网络、复现前沿论文,组合优化创新 🚀🚀🚀小目标、遮挡物、难样本性能提升 🍉🍉🍉定期更新不同数据集涨点情况 本文是…

gitlab上传大文件限制问题解决

gitlab上传大文件限制问题解决 前景提要&#xff1a; 今天收到同事反馈遇到gitlab 上传大文件时候报如下错误 error: RPC failed; result22, HTTP code 413 fatal: The remote end hung up unexpectedly fatal: The remote end hung up unexpectedly从报错来看是因为文件大…

什么样的冷链保温箱,既环保又实用?

冷链物流运输已经应用在了很多行业中&#xff0c;作为冷链物流运输中的重要设备——冷链保温箱&#xff0c;起到了举足轻重的作用。如果选择不当&#xff0c;选到了劣质产品&#xff0c;尤其是化学行业或者食品行业&#xff0c;就有可能造成试剂失效或者是影响粮食食品安全问题…

2023英码科技激发团队活力,提升集体凝聚力团建拓展之旅圆满结束!

5月6日&#xff0c;时至立夏&#xff0c;风暖昼长&#xff0c;万物繁茂。 在这个生机盎然、活力四射的时节&#xff0c; 尤其适合出游&#xff0c;开展有益身心健康的活动。 这一天&#xff0c;英码科技全体家人们齐聚广州白云区钟落潭&#xff0c;开展一天好玩有趣又意义深…

SVN基本操作 使用教程

01-SVN概述 1、为什么需要SVN版本控制软件 2、解决之道 SCM&#xff1a;软件配置管理 所谓的软件配置管理实际就是对软件源代码进行控制与管理 CVS&#xff1a;元老级产品 VSS&#xff1a;入门级产品 ClearCase&#xff1a;IBM公司提供技术支持&#xff0c;中坚级产品 SVN&…

C++类与对象(三)

文章目录 一.初始化列表1.初始化列表的概念2.初始化列表的注意事项 二.explicit关键字1.单参数构造函数2.多参数构造函数 三.static成员1.static成员的概念2.static成员的特性 四.友元1.概念2.友元函数3.友元类 五.内部类1.概念2.内部类的性质 六.匿名对象七.拷贝对象时编译器的…

Docker安装MySQL主从配置

今天学习Docker安装MySQL主从配置 一、Master 1.1、拉取镜像 $docker pull mysql:8.0.25 1. 2、新建MySQL主服务器的容器实例&#xff0c;端口为3306 docker run -p 3306:3306 --name mysql-master \ -v /data/mysql/mysql-master/log:/var/log/mysql \ -v /data/mysql/mys…

WebSocket聊天功能小Demo

一、WebSocket简介 1.1 什么是WebSocket&#xff1f; WebSocket协议是基于TCP的一种网络协议&#xff0c;它实现了浏览器与服务器全双工&#xff08;Full-duplex&#xff09;通信。它允许服务端主动向客户端推送数据&#xff0c;这使得客户端和服务器之间的数据交换变得更加简…

模型微调的预处理

一.简历文本标注数据的准备 目标&#xff1a;把原始数据集转换为PaddleNLP支持的文本/文档抽取标注格式&#xff0c;为后续的模型微调做好准备。 工具&#xff1a;Label Studio 使用手册&#xff1a; applications/information_extraction/label_studio_text.md PaddlePad…

ai原创文章生成器-原创文章生成的软件

AI原创文章生成器——让你轻松批量生成高质量文章 随着内容创作的需求不断增加&#xff0c;人工撰写也难以满足快速高效的产出需求。在这种情况下&#xff0c;AI原创文章生成器应运而生&#xff0c;为人们创造了一种全新的自动化创作方式。下面我们就来了解一下这个神奇的工具…

无网络要求有网就能免费体验ChatGPT/GPT4

ChatGPT 是 OpenAI 公司开发的一款聊天机器人。它基于 OpenAI 的 GPT-3 语言模型,可以进行开域的自然语言聊天。主要特点如下: 开域聊天:ChatGPT可以聊任意话题,不需要预先定义话题范围或关键词,真正实现开放领域聊天。自然语言交互:ChatGPT可以理解并生成自然的语言表达,其对…

[答疑]事件和其影响的属性的对应是多样的

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>> 第五元素 2023-5-2 19:16 这题是不是缺少条件啊&#xff1f;“按钮默认isEnabled为true&#xff0c;被点击后&#xff0c;isEnabled变为false” 是通过什么渠道达到S4状态…

视频截取gif方法分享,利用gif制作工具在线制作动图

表情包作为聊天社交中调节氛围的工具&#xff0c;而动态的gif表情包更是深受大众的喜爱。那么&#xff0c;这种gif动态图片要怎么制作呢&#xff1f;其实&#xff0c;很简单不需要下载软件&#xff0c;小白也能轻松操作的。 一、什么工具能够制作gif动画呢&#xff1f; 使用G…

freeswitch两个DTMF转换接口的区别

概述 freeswitch支持三种模式的DTMF传输方式&#xff0c;分别时inband、INFO、2833。 在传统的PSTN网络中&#xff0c;所有的DTMF码都是inband模式&#xff0c;所以VOIP网络和PSTN网络对接中&#xff0c;需要将DTMF码做格式转换&#xff0c;通常是2833和inband之间的转换。 …

普乐蛙数字文旅动感5d电影设备5d动感电影体验馆

普乐蛙5d动感影院7d互动影院设备&#xff0c;它是通过视觉、听觉、触觉、嗅觉和味觉&#xff0c;在特定的环境中模拟形成一种特定的空间&#xff0c;营造出身临其境的效果。普乐蛙5d动感影院7d互动影院设备&#xff0c;它是根据人体工程学设计的座椅&#xff0c;让观众坐在座椅…

【iOS】—— 实现WebSocket发送消息(SocketRocket第三方库的使用和解析)

文章目录 WebSocketWebSocket特点 SocketRocket导入头文件设置代理SRWebSocket的初始化和建立连接SRWebSocketDelegate 代理方法实现加上简单UI实现两个用户之间简单通信浅看了一点点源码&#xff08;理解的不深&#xff09; 偶然之间了解到了利用WebSocket实现后端和前端的相互…

力扣刷题19天

106.从中序与后序遍历序列构造二又树(1、在中序、前序和后序&#xff0c;每轮取得时候数量都一样. 2、必须要有中序才能推测出来) 这道题下面是前提&#xff1a; 如果没有这个前提&#xff0c;会出现下面情况(前序遍历会变成新的树)&#xff1a; 运行代码&#xff1a; class S…