• 从零开始 Spring Boot 21:Activiti


    从零开始 Spring Boot 21:Activiti

    spring boot

    图源:简书 (jianshu.com)

    Activiti是一个开源的工作流引擎,可以帮助我们实现一些流程自动化,比如OA审批流等。

    官网:Open Source Business Automation | Activiti

    整合

    添加依赖

            
            <dependency>
                <groupId>org.activitigroupId>
                <artifactId>activiti-spring-boot-starter-basicartifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.mybatisgroupId>
                        <artifactId>mybatisartifactId>
                    exclusion>
                exclusions>
                <version>6.0.0version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    mvn中最新的依赖版本是6.0.0,可以通过下面的页面查看所有的mvn依赖版本:

    这里通过exclusion标签屏蔽了项目对activitii包中的mybatis的传递依赖,这是因为项目本身引用MybatisPlus,并依赖相应的Mybatis,如果这里再通过activiti引入其他版本的Mybatis就会导致版本冲突,会导致无法正常运行。

    添加配置

    # ...
    # activity
    # 是否每次启动时检查数据库表需要更新
    spring.activiti.database-schema-update=false
    # 是否检查存在流程配置文件
    spring.activiti.check-process-definitions=false
    # 流程配置文件目录
    spring.activiti.process-definition-location-prefix=classpath:/processes/
    # 流程配置文件后缀名
    spring.activiti.process-definition-location-suffixes[0]=**.bpmn
    spring.activiti.process-definition-location-suffixes[1]=**.bpmn20.xml
    # 保存历史数据级别,full为最高
    spring.activiti.history-level=full
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    修改注解

    package cn.icexmoon.demo.books;
    
    import org.activiti.spring.boot.SecurityAutoConfiguration;
    //...
    
    @SpringBootApplication(exclude = SecurityAutoConfiguration.class)
    @MapperScan("cn.icexmoon.demo.books.*.mapper")
    public class BooksApplication {
        public static void main(String[] args) {
    
    		//...
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    需要修改SpringBootApplication注解,通过exclude属性排除对SecurityAutoConfiguration类的自动化配置。

    建表

    Activiti本身需要数据库支持才能工作,所以需要创建其需要的表结构。Activiti官方提供建表所需的sql,这包含在Activiti的Jar包(activity-engine-x.x.x.jar)中。

    首先,在mvn仓库中找到下载的目标jar包:

    image-20220830112719171

    用解压工具直接解压该jar包,可以在db目录下找到这样几个目录:

    image-20220830112853698

    这几个目录分别对应创建表、删除表、更新表等的DLL SQL语句,这里我们只需要用到creat。

    image-20220830113019824

    创建语句是按照数据库分类的,因为虽然SQL语法是通用的,但是因为数据库类型和版本的不同,在某些特性上会有差异导致SQL语句不会完全相同。

    这里按照项目自身使用的数据库类型选择即可,我这里是MySQL。

    之后就是将这些建表的DDL导入数据库了,选择自己顺手的工具即可,我这里是使用sqlyog。

    其实Activiti可以在启动后扫描数据库,如果缺少相应的表结构会自动创建,但是这里我整合后并不能自动创建表结构,进而因为缺少表结构无法启动应用,不知道是不是配置的问题。

    此外,很多商业部署因为安全方面考虑,生产环境是没有DDL语句的数据库执行权限的,因此需要通过上述方式提取DDL语句后提交给DBA来完成建表工作。、

    开始

    制作流程

    Activiti使用的是由BPMN2.0标准定义的流程图,有很多工具都可以制作该标准的流程图,使用最多的是在IDE中集成的各种BPMN相关插件。不过我这里使用Activiti官方提供的一个在线工具:

    使用该工具可以绘制一个最简单的流程图:

    image-20220901154855783

    该流程图仅包含3个最简单的元素:

    • StartEvent,流程的开始。
    • Task,流程中需要执行的动作,可能是用户审批或者某些代码完成的自动化工作。
    • EndEvent,流程结束。

    用在线工具绘制好后可以通过左下角菜单导出BMPN2.0文件,我们需要将这个文件保存到Spring Boot的静态资源目录下的processes目录下:

    image-20220901155402399

    实际上该文件是一个XML文件:

    
    <bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1kwvfrz" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="9.3.2">
      <bpmn:process id="Process_0wu4lop" isExecutable="true">
        <bpmn:startEvent id="StartEvent_0vx5axl">
          <bpmn:outgoing>Flow_0nv17f1bpmn:outgoing>
        bpmn:startEvent>
        <bpmn:endEvent id="Event_1294r00">
          <bpmn:incoming>Flow_1ke9x09bpmn:incoming>
        bpmn:endEvent>
        <bpmn:task id="Activity_0nt5d38" name="approve">
          <bpmn:incoming>Flow_0nv17f1bpmn:incoming>
          <bpmn:outgoing>Flow_1ke9x09bpmn:outgoing>
        bpmn:task>
        <bpmn:sequenceFlow id="Flow_0nv17f1" sourceRef="StartEvent_0vx5axl" targetRef="Activity_0nt5d38" />
        <bpmn:sequenceFlow id="Flow_1ke9x09" sourceRef="Activity_0nt5d38" targetRef="Event_1294r00" />
      bpmn:process>
      <bpmndi:BPMNDiagram id="BPMNDiagram_1">
        <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0wu4lop">
          <bpmndi:BPMNEdge id="Flow_0nv17f1_di" bpmnElement="Flow_0nv17f1">
            <di:waypoint x="210" y="118" />
            <di:waypoint x="210" y="200" />
          bpmndi:BPMNEdge>
          <bpmndi:BPMNEdge id="Flow_1ke9x09_di" bpmnElement="Flow_1ke9x09">
            <di:waypoint x="210" y="280" />
            <di:waypoint x="210" y="352" />
          bpmndi:BPMNEdge>
          <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_0vx5axl">
            <dc:Bounds x="192" y="82" width="36" height="36" />
          bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="Event_1294r00_di" bpmnElement="Event_1294r00">
            <dc:Bounds x="192" y="352" width="36" height="36" />
          bpmndi:BPMNShape>
          <bpmndi:BPMNShape id="Activity_0nt5d38_di" bpmnElement="Activity_0nt5d38">
            <dc:Bounds x="160" y="200" width="100" height="80" />
            <bpmndi:BPMNLabel />
          bpmndi:BPMNShape>
        bpmndi:BPMNPlane>
      bpmndi:BPMNDiagram>
    bpmn:definitions>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    这个文件包含两部分,process节点中包含的是流程定义,BPMNDiagram节点中包含的是流程对应的可视化图形。

    process包含这么几种子节点:

    • startEvent,对应流程图的StartEvent
    • endEvent,对应流程图的EndEvent
    • task,对应流程图中的Task
    • sequenceFlow,对应流程图中连接Event和Task的线段

    通过sequenceFlowsourceRef属性和targetRef属性可以很清楚看出这些流程元素的关联关系。

    部署流程

    流程文件准备好后需要装载(部署)到Activiti中才可以使用,分为两种方式:自动和手动。

    先来看自动部署,只要将activiti的相关配置设置为true

    # ...
    # 是否检查存在流程配置文件
    spring.activiti.check-process-definitions=true
    # ...
    
    • 1
    • 2
    • 3
    • 4

    这样项目启动后Activiti会自动检索processes目录下的流程文件进行加载。

    实际上该配置的默认值就是true,也就是说缺省时的行为就是自动加载。

    当然也可以手动部署,这需要将上边说的Acitiviti配置设置为false,并且在Spring Boot的入口类中注入一个CommandLineRunner

    package cn.icexmoon.demo.books;
    
    // ...
    
    @SpringBootApplication(exclude = SecurityAutoConfiguration.class)
    @MapperScan("cn.icexmoon.demo.books.*.mapper")
    @Log4j2
    public class BooksApplication {
        public static void main(String[] args) {
    
            SpringApplication.run(BooksApplication.class, args);
    
        }
    
        @Bean
        public CommandLineRunner init(final RepositoryService repositoryService,
                                      final RuntimeService runtimeService,
                                      final TaskService taskService
        ) {
            return new CommandLineRunner() {
                @Override
                public void run(String... args) throws Exception {
                    repositoryService.createDeployment()
                            .addClasspathResource("processes/diagram.bpmn")
                            .key("Process_0wu4lop")
                            .name("示例流程")
                            .deploy();
                    log.debug("流程{}已部署", "示例流程");
                }
            };
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    这样就可以在应用启动后部署指定流程。

    部署流程后就可以在Activiti的数据表中看到相关数据了,Activiti用表名前缀来区分不同用途的表:

    • ACT_RE_rerepository(仓库)的缩写,即流程相关的静态信息,包括流程定义和流程资源(图片,规则)等。
    • ACT_RU_ruruntime(运行时)的缩写,这些表包含流程在运行时产生的数据,包括(流程实例、用户任务、变量、定时任务等)。Activiti会在流程实例启动时存储相关数据,并在流程实例结束时移除这些数据,这样可以保持一个精简的表数据规模,以维持高效的数据库性能。
    • ACT_ID_ididentity的缩写,这些表包含了相关的ID信息,包括用户、用户组等。
    • ACT_HI_hihistory的缩写,这些表包含了历史数据,包括运行过的流程实例、变量、任务等。
    • ACT_GE_gegeneral(通用)的缩写,这些表包含了一般性的数据。

    时间检查表就会发现下面这些表产生了数据:

    act_ge_bytearray

    image-20220901163823463

    显然这个表保存的是从processes加载的BPMN文件信息。

    act_re_deployment

    image-20220901164023463

    这个包含的是我们部署流程时的信息,其中SpringAutoDeployment是自动部署时产生的部署信息。

    act_re_procdef

    image-20220901164428478

    这个表包含了流程定义,其中HAS_START_FORM_KEY_字段表示流程是否启动。

    当然实际使用中我们并不需要每次在项目启动时部署流程,我们可以将流程部署进行封装:

    public interface IActivitiService {
        /**
         * 部署流程
         *
         * @param classPathResource 资源路径,如processes/diagram.bpmn
         * @param key               流程key,如Process_0wu4lop
         * @param name              流程名称,如示例流程
         */
        void deploy(String classPathResource, String key, String name);
    
     	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    package cn.icexmoon.demo.books.book.service.impl;
    
    // ...
        
    @Log4j2
    @Service
    public class ActivitiServiceImpl implements IActivitiService {
        @Autowired
        private RepositoryService repositoryService;
        @Autowired
        private TaskService taskService;
        @Autowired
        private RuntimeService runtimeService;
    
    
        @Override
        public void deploy(String classPathResource, String key, String name) {
            log.info("========开始部署流程=======");
            log.info("资源路径:" + classPathResource);
            log.info("key:" + key);
            log.info("名称:" + name);
            DeploymentBuilder deployment = repositoryService.createDeployment();
            deployment
                    .addClasspathResource(classPathResource)
                    .key(key)
                    .name(name)
                    .deploy();
            log.info("=======流程已部署=========");
        }
    
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    这里我们通过自动装配获取了几个Activiti的核心对象,Activiti通过这几个核心对象的API对外提供服务:

    • RepositoryService,流程仓库服务,用来管理Activiti的静态资源,比如部署后的流程等。
    • TaskService,流程任务服务,用来管理流程任务,通过它可以获取到当前有多少任务以及某个人需要处理的任务等。
    • RuntimeService,流程运行时服务,用来管理流程的运行相关功能,比如获取运行时产生的数据或启动某个流程实例。

    在调试中我发现通过mvn启动的项目是无法加断点debug的,原因是mvn启动的是独立的进程,所以需要通过添加JVM启动参数并远程调试的方式进行debug,具体可以参考spring-boot-maven-plugin:debug调试程序_lyterrific的博客-CSDN博客_maven springboot 调试

    在Controller中编写Handler方法:

    package cn.icexmoon.demo.books.book.controller;
    
    // ...
    
    @RestController
    @RequestMapping("/activiti")
    @Api(tags = "Activiti示例接口")
    public class ActivityController {
        @Resource
        private RuntimeService runtimeService;
        @Autowired
        private IActivitiService activitiService;
    
    	// ...
        
        @Data
        private static class DeployProcessDTO {
            @ApiModelProperty(value = "流程定义资源文件(bpmn文件的classPath路径)", required = true, example = "processes/diagram.bpmn")
            @NotBlank
            private String classPathResource;
            @ApiModelProperty(value = "流程key(bpmn文件中流程的key)", required = true, example = "Process_0wu4lop")
            @NotBlank
            private String key;
            @ApiModelProperty(value = "部署后的流程名称,可以为null", example = "A example process")
            private String name;
        }
    
        @ApiOperation("部署流程")
        @PostMapping("/deploy")
        public Result deployProcess(@RequestBody DeployProcessDTO dto) {
            activitiService.deploy(dto.getClassPathResource(), dto.getKey(), dto.getName());
            return Result.success();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    之后就可以通过接口调试工具部署流程了:

    image-20220908115421078

    流程定义

    流程部署后,会以流程定义(process definition)的形式存在,我们可以通过RepositoryService获取当前所有的流程定义:

    package cn.icexmoon.demo.books.book.service.impl;
    // ...
    @Log4j2
    @Service
    public class ActivitiServiceImpl implements IActivitiService {
        @Autowired
        private RepositoryService repositoryService;
        @Autowired
        private TaskService taskService;
        @Autowired
        private RuntimeService runtimeService;
    
    	// ...
    	
        public List<ProcessDefinition> listProcessDefinition() {
            return repositoryService.createProcessDefinitionQuery().list();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    当然还需要编写对应的Controller的Handler方法:

    package cn.icexmoon.demo.books.book.controller;
    
    // ...
    @RestController
    @RequestMapping("/activiti")
    @Api(tags = "Activiti示例接口")
    public class ActivityController {
        @Resource
        private RuntimeService runtimeService;
        @Autowired
        private IActivitiService activitiService;
    	// ...
        @Data
        @Accessors(chain = true)
        private static class GetProcessDefineListVO {
            @ApiModelProperty("部署流程时的deployment的id,可以用于删除流程部署")
            private String deploymentId;
            @ApiModelProperty("流程定义id")
            private String id;
            @ApiModelProperty("流程定义名称")
            private String name;
            @ApiModelProperty("流程的key")
            private String key;
            @ApiModelProperty("bpmn资源文件名称")
            private String resourceName;
            @ApiModelProperty("流程定义版本")
            private Integer version;
    
            public static GetProcessDefineListVO newInstance(ProcessDefinition pd) {
                GetProcessDefineListVO vo = new GetProcessDefineListVO();
                vo.setId(pd.getId())
                        .setName(pd.getName())
                        .setDeploymentId(pd.getDeploymentId())
                        .setKey(pd.getKey())
                        .setResourceName(pd.getResourceName())
                        .setVersion(pd.getVersion());
                return vo;
            }
        }
    
        @ApiOperation("获取流程定义列表")
        @PostMapping("/process-define/list")
        public List<GetProcessDefineListVO> getProcessDefineList() {
            List<ProcessDefinition> pds = activitiService.listProcessDefinition();
            List<GetProcessDefineListVO> vo = pds.stream().map(pd -> GetProcessDefineListVO.newInstance(pd)).collect(Collectors.toList());
            return vo;
        }
    
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    最后通过接口调用可以获取到类似下面这样的信息:

    {
    	"success": true,
    	"msg": "",
    	"data": [
    		{
    			"deploymentId": "27501",
    			"id": "Process_0wu4lop:10:27504",
    			"name": "A test process",
    			"key": "Process_0wu4lop",
    			"resourceName": "processes/diagram.bpmn",
    			"version": 10
    		},
    		{
    			"deploymentId": "2501",
    			"id": "Process_0wu4lop:1:2504",
    			"name": null,
    			"key": "Process_0wu4lop",
    			"resourceName": "processes/diagram.bpmn",
    			"version": 1
    		},
    		{
    			"deploymentId": "12501",
    			"id": "Process_0wu4lop:2:12505",
    			"name": null,
    			"key": "Process_0wu4lop",
    			"resourceName": "D:\\workspace\\learn_spring_boot\\ch21\\books\\target\\classes\\processes\\diagram.bpmn",
    			"version": 2
    		},
    		// ...
    		{
    			"deploymentId": "12501",
    			"id": "oneTaskProcess:1:12506",
    			"name": "The One Task Process",
    			"key": "oneTaskProcess",
    			"resourceName": "D:\\workspace\\learn_spring_boot\\ch21\\books\\target\\classes\\processes\\one-task-process.bpmn20.xml",
    			"version": 1
    		}
    	],
    	"code": 200
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    可以看到即使是相同的流程定义文件(bpmn)部署的流程,也是不同的“流程定义”,只不过它们之间有着一些相同的联系:

    • 具有相同的key
    • 具有相同的resourceName(自动部署的流程定义用的是绝对路径)
    • 版本号逐次递增

    流程定义的id由3部分组成:流程key、版本号、部署器id。

    deploymentId是部署(employment,这里是名词,表示某次部署的结果)的id。是不是"同一批"部署的可以用deploymentId来区分,比如Process_0wu4lop:2:12505oneTaskProcess:1:12506就是同一个部署(employment)自动部署的。

    删除部署

    流程部署后,如果要删除,必须按照流程所属的部署来进行删除。也就是说通过部署的id将部署相关的流程全部删除:

    package cn.icexmoon.demo.books.book.service.impl;
    
    // ...
    @Log4j2
    @Service
    public class ActivitiServiceImpl implements IActivitiService {
    	// ...
    	@Override
        public void delDeployment(String deploymentId, boolean cascadeDel) {
            repositoryService.deleteDeployment(deploymentId, cascadeDel);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    RepositoryService.deleteDeployment有两个参数,第一个参数是要删除的部署的id,第二个参数是是否要级联删除。如果是级联删除,会删除相关流程定义的所有内容,包括正在执行的流程实例和历史信息。如果是非级联删除,只会删除部署,但如果相关流程有正在进行中的流程实例,就会报错,无法删除。

    启动流程

    可以用流程key启动一个对应的流程实例:

    package cn.icexmoon.demo.books.book.service.impl;
    // ...
    @Log4j2
    @Service
    public class ActivitiServiceImpl implements IActivitiService {
        @Autowired
        private RepositoryService repositoryService;
        @Autowired
        private TaskService taskService;
        @Autowired
        private RuntimeService runtimeService;
    
    
    	// ...
        @Override
        public ProcessInstance startProcessInstance(String processKey) {
            logInfo();
            ProcessInstance process = runtimeService.startProcessInstanceByKey(processKey);
            log.info("流程" + processKey + "已启动一个新实例");
            logInfo();
            return process;
        }
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在Handler方法中通过返回的ProcessInstance可以获取一些生成的流程实例的信息:

    package cn.icexmoon.demo.books.book.controller;
    // ...
    @RestController
    @RequestMapping("/activiti")
    @Api(tags = "Activiti示例接口")
    public class ActivityController {
        @Autowired
        private IActivitiService activitiService;
    
        @Data
        @Accessors(chain = true)
        private static class StartProcessVO implements IResult {
            private String id;
            private String name;
            private String deploymentId;
            private String processDefinitionId;
            private String startUserId;
            private String processDefinitionName;
    
            public static StartProcessVO newInstance(ProcessInstance pi) {
                StartProcessVO vo = new StartProcessVO();
                return vo.setId(pi.getId())
                        .setName(pi.getName())
                        .setDeploymentId(pi.getDeploymentId())
                        .setProcessDefinitionId(pi.getProcessDefinitionId())
                        .setStartUserId(pi.getStartUserId())
                        .setProcessDefinitionName(pi.getProcessDefinitionName());
            }
        }
    
        @ApiOperation("启动流程实例")
        @PostMapping("/process/start/{key}")
        public StartProcessVO startProcess(@ApiParam("流程key") @PathVariable String key) {
            ProcessInstance pi = activitiService.startProcessInstance(key);
            return StartProcessVO.newInstance(pi);
        }
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    接口调用后的返回信息:

    {
    	"success": true,
    	"msg": "",
    	"data": {
    		"id": "30001",
    		"name": null,
    		"deploymentId": null,
    		"processDefinitionId": "Process_0wu4lop:9:25004",
    		"startUserId": null,
    		"processDefinitionName": "A test process"
    	},
    	"code": 200
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这里请求的是http://localhost:8081/activiti/process/start/Process_0wu4lop,实际上Process_0wu4lop有9个部署,版本号是1~9,可以看到Activiti会选择最高版本号的流程定义来启动一个流程实例。

    虽然这里的确可以启动一个流程实例,但其实是无法在数据库或者输出中观察到这个流程执行的,这是因为这个示例流程实际上只有一个普通的task任务,该任务既不会执行特定程序也不会等待用户操作,所以流程实例启动后就会立即结束,我们不会观察到任何行为。

    可以将task替换为serviceTask,这种类型的任务可以触发相应的程序执行。具体的方式为先定义一个实现了JavaDelegate接口的类:

    package cn.icexmoon.demo.books.book.entity.task;
    // ...
    public class MyTestTask implements JavaDelegate {
        @Override
        public void execute(DelegateExecution delegateExecution) {
            System.out.println("MyTestTask is executed.");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    修改bpmn定义:

    
    <bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        
        <bpmn:serviceTask id="Activity_0nt5d38" activiti:exclusive="true" name="approve" activiti:class="cn.icexmoon.demo.books.book.entity.task.MyTestTask">
          <bpmn:incoming>Flow_0nv17f1bpmn:incoming>
          <bpmn:outgoing>Flow_1ke9x09bpmn:outgoing>
        bpmn:serviceTask>
        
      bpmn:process>
       
    bpmn:definitions>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这里的关键是通过serviceTask节点的activiti:class属性”绑定“任务需要执行的Task类。Activiti就会在执行流程实例时,在流程执行到这个Task时执行对应的Task类中的execute方法。

    然后按之前做的,重新加载和启动一个Process_0wu4lop流程,一切都OK的话会在控制台看到MyTestTask is executed.输出。

    下面看一个更复杂点的流程:

    
    <bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
                        xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
                        xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
                        xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
                        xmlns:activiti="http://activiti.org/bpmn"
                        id="Definitions_13909a0" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="9.3.2">
      <bpmn:process id="Process_0hy83oz" isExecutable="true" name="A test process 2">
        <bpmn:startEvent id="StartEvent_07jmeqi">
          <bpmn:outgoing>Flow_1cydcrqbpmn:outgoing>
        bpmn:startEvent>
        <bpmn:serviceTask id="Activity_1tlvosh" activiti:exclusive="true" name="task1" activiti:class="cn.icexmoon.demo.books.book.entity.task.MyTestTask2">
    
    
          <bpmn:extensionElements>
              <activiti:field name="text1">
                  <activiti:string>activiti:string>
              activiti:field>
          bpmn:extensionElements>
        bpmn:serviceTask>
        <bpmn:sequenceFlow id="Flow_1cydcrq" sourceRef="StartEvent_07jmeqi" targetRef="Activity_1tlvosh" />
        <bpmn:serviceTask id="Activity_00xge6t" activiti:exclusive="true" name="task2" activiti:class="cn.icexmoon.demo.books.book.entity.task.MyTestTask3">
    
    
          <bpmn:extensionElements>
              <activiti:field name="text2">
                  <activiti:string>activiti:string>
              activiti:field>
          bpmn:extensionElements>
        bpmn:serviceTask>
        <bpmn:sequenceFlow id="Flow_1x26529" sourceRef="Activity_1tlvosh" targetRef="Activity_00xge6t" />
        <bpmn:endEvent id="Event_0j69tgw">
          <bpmn:incoming>Flow_135u817bpmn:incoming>
        bpmn:endEvent>
        <bpmn:sequenceFlow id="Flow_135u817" sourceRef="Activity_00xge6t" targetRef="Event_0j69tgw" />
      bpmn:process>
      <bpmndi:BPMNDiagram id="BPMNDiagram_1">
      	
      bpmndi:BPMNDiagram>
    bpmn:definitions>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    这个流程包含两个ServiceTask,并且通过extensionElements节点添加了Activiti自定义节点,通过该自定义节点我们可以在相应的Task执行时,获取或重写对应的属性。

    这里有个奇怪的问题,必须注释掉serviceTask下的incomingoutgoing节点,否则无法正常部署这个流程定义到Activiti,会提示extensionElements节点定义不正确。

    我翻阅了bpmn2官方定义,但并没有发现xsd文件中定义了extensionElements节点不能与incoming节点共存这样的东西:

    <xsd:element name="flowNode" type="tFlowNode"/>
    <xsd:complexType name="tFlowNode" abstract="true">
        <xsd:complexContent>
            <xsd:extension base="tFlowElement">
                <xsd:sequence>
                    <xsd:element name="incoming" type="xsd:QName" minOccurs="0" maxOccurs="unbounded"/>
                    <xsd:element name="outgoing" type="xsd:QName" minOccurs="0" maxOccurs="unbounded"/>
                xsd:sequence>
            xsd:extension>
        xsd:complexContent>
    xsd:complexType>
    
    <xsd:element name="extensionElements" type="tExtensionElements"/>
    <xsd:complexType name="tExtensionElements">
        <xsd:sequence>
        	<xsd:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
        xsd:sequence>
    xsd:complexType>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这个问题目前不清楚是Activiti解析的问题还是bpmn定义的问题。

    ServiceTask对应的Java类:

    @Log4j2
    public class MyTestTask2 implements JavaDelegate {
        private Expression text1;
    
        @Override
        public void execute(DelegateExecution delegateExecution) {
            log.info("MyTestTask2 is executed.");
            String value = (String) this.text1.getValue(delegateExecution);
            log.info("get text1's value:" + value);
            delegateExecution.setVariable("text", value);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    @Log4j2
    public class MyTestTask3 implements JavaDelegate {
        private Expression text2;
    
        @Override
        public void execute(DelegateExecution delegateExecution) {
            log.info("MyTestTask3 is executed.");
            String value = (String) this.text2.getValue(delegateExecution);
            log.info("get text2's value:" + value);
            String textVal = (String) delegateExecution.getVariable("text");
            textVal += value;
            delegateExecution.setVariable("text", textVal);
            log.info("finally, the text's value is:" + textVal);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    部署并执行后可以看到下面这样的输出:

    15:18:39.764 [http-nio-8081-exec-3] INFO  c.i.d.b.book.entity.task.MyTestTask2 - MyTestTask2 is executed.
    15:18:39.764 [http-nio-8081-exec-3] INFO  c.i.d.b.book.entity.task.MyTestTask2 - get text1's value:test1
    15:18:39.766 [http-nio-8081-exec-3] INFO  c.i.d.b.book.entity.task.MyTestTask3 - MyTestTask3 is executed.
    15:18:39.766 [http-nio-8081-exec-3] INFO  c.i.d.b.book.entity.task.MyTestTask3 - get text2's value:test2
    15:18:39.766 [http-nio-8081-exec-3] INFO  c.i.d.b.book.entity.task.MyTestTask3 - finally, the text's value is:test1test2
    
    • 1
    • 2
    • 3
    • 4
    • 5

    关于Activiti就先到这里了,到这里只算是对Activiti有一个大概了解,要实际使用还需要尝试将Activiti与项目内的组织架构整合,实现流程审批等才行,有空再尝试。

    谢谢阅读。

    本篇文章最终的完整示例代码见learn_spring_boot/ch21 (github.com)

    参考资料

  • 相关阅读:
    基于SSM框架流浪猫救援网站的设计与实现 毕业设计-附源码201502
    Node.js - nvm管理node.js版本
    Python语言学习:Python语言学习之文件读取&写入/操作系统(OS模块详解)的简介、案例应用之详细攻略
    【优化算法】加权黑猩猩优化算法(WChOA)(Matlab代码实现)【与ChOA、PSO、WOA、BH、ALO、GA和GWO算法比较】
    【Proteus仿真】【STM32单片机】便携式血糖仪
    中国MEMS陀螺仪应用及市场需求空间测算研究报告
    【C++】函数重载 & 引用 & 内联函数
    C++ 程序员入门需要多久,怎样才能学好?
    【VUE项目实战】55、商品添加功能(五)-商品内容模块
    vite+vue3项目中集成ESLint与prettier
  • 原文地址:https://blog.csdn.net/hy6533/article/details/126784523