Bootstrap

Activiti8整合SpringBoot3

工作流:我的理解-->工作流是一种简化审批业务流程开发的技术 至少我现在看到的都是这样

当然例如退货,甚至前后企业的ERP系统都感觉可以使用工作流来完成

activiti的一些缺点

  • 提供的设计器只能开发者使用,而且体验不是很好

  • 不支持直接面向业务人员使用

  • 不支持表单

  • 不支持国产功能,比如会签、跳转等功能

  • 不支持流程状态图

  • 与SpringClound微服务集成方式不优雅

  • 使用门槛、成本比较高

如果将工作流单独抽出成一个应用

一般的审批业务大致

1.集成流程引擎

2.画流程

3.保存流程图

4.部署和运行流程图

5.开发同意和不同意审批功能

SpringBoot集成Activiti8

使用最新版本SpringBoot

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>demo</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>8.7.0</version>
<!--            <exclusions>-->
<!--                &lt;!&ndash; 排除 mybatis(低版本的activiti-spring-boot-starter需要移除Mybatis依赖) 相关依赖 &ndash;&gt;-->
<!--                <exclusion>-->
<!--                    <groupId>org.mybatis</groupId>-->
<!--                    <artifactId>mybatis</artifactId>-->
<!--                </exclusion>-->
<!--            </exclusions>-->
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.31</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.mybatis.spring.boot</groupId>-->
<!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!--            <version>3.0.4</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.9</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-jsqlparser</artifactId>
            <version>3.5.9</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>3.0.4</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <repositories>
        <repository>
            <id>activiti-releases</id>
            <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-releases</url>
        </repository>
    </repositories>



    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Activity中引入了Security  

package com.example.demo;

import org.activiti.api.runtime.shared.identity.UserGroupManager;
import org.activiti.core.common.spring.identity.config.ActivitiSpringIdentityAutoConfiguration;
import org.activiti.spring.boot.ProcessEngineAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    //SecurityManager在JDK17已经废除
//    @Bean
//    public SecurityManager securityManager() {
//        return new SecurityManager() {
//            @Override
//            public String getAuthenticatedUserId() {
//                return "miukoo-user-1";
//            }
//
//            @Override
//            public List<String> getAuthenticatedUserGroups() throws SecurityException {
//                return null;
//            }
//
//            @Override
//            public List<String> getAuthenticatedUserRoles() throws SecurityException {
//                return null;
//            }
//        };
//    }

    @Bean
    public UserGroupManager userGroupManager() {
        return new UserGroupManager(){
            @Override
            public List<String> getUserGroups(String username) {
                return null;
            }

            @Override
            public List<String> getUserRoles(String username) {
                return null;
            }

            @Override
            public List<String> getGroups() {
                return null;
            }

            @Override
            public List<String> getUsers() {
                return null;
            }
        };
    }




}

application.yml中的配置

server:
  port: 8081
spring:
  activiti:
    database-schema-update: true  # 自动创建数据库
    db-history-used: true         # 开启流程历史记录
    history-level: full           # 历史流程数据会全部记录到历史表中,支持none, activity, audit, full, 4种粒度的数据,常用的就是full

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/activiti?allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&nullCatalogMeansCurrent=true#nullCatalogMeansCurrent=true一定要有
    username: root
    password: 123456
mybatis-plus:
  #指定实体类
  type-aliases-package: com.example.activiti.entity#指定自己的包
  mapper-locations: classpath*:mappers/*.xml
  configuration:
    map-underscore-to-camel-case: true  # 驼峰命名
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 输出sql



BPMN介绍:

BPMN:业务流程建模标记语言.就是那个乱78遭的XML

流程图最终也是转成那个.xml

示例

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:tns="http://www.activiti.org/testm1685269119653" xmlns:xsd="http://www.w3.org/2001/XMLSchema" id="hiss_flow_1692440641378" name="" targetNamespace="http://www.activiti.org/testm1685269119653">
  <process id="hiss_process_12_1692440641431" name="hehe_process" isExecutable="true">
    <startEvent id="StartEvent_1" />
    <userTask id="Activity_1xi315o" name="填写请假单" activiti:assignee="张三">
      <incoming>Flow_109kst5</incoming>
      <outgoing>Flow_0wd9dr1</outgoing>
    </userTask>
    <sequenceFlow id="Flow_109kst5" sourceRef="StartEvent_1" targetRef="Activity_1xi315o" />
    <userTask id="Activity_0xmqcac" name="经理审批" activiti:assignee="李四">
      <incoming>Flow_0wd9dr1</incoming>
      <outgoing>Flow_1s2a4e5</outgoing>
    </userTask>
    <sequenceFlow id="Flow_0wd9dr1" sourceRef="Activity_1xi315o" targetRef="Activity_0xmqcac" />
    <endEvent id="Event_1qwztvn">
      <incoming>Flow_1s2a4e5</incoming>
    </endEvent>
    <sequenceFlow id="Flow_1s2a4e5" sourceRef="Activity_0xmqcac" targetRef="Event_1qwztvn" />
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="hiss_process_12_1692440641431">
      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
        <dc:Bounds x="173" y="102" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_1xi315o_di" bpmnElement="Activity_1xi315o">
        <dc:Bounds x="330" y="80" width="100" height="80" />
        <bpmndi:BPMNLabel />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Activity_0xmqcac_di" bpmnElement="Activity_0xmqcac">
        <dc:Bounds x="560" y="80" width="100" height="80" />
        <bpmndi:BPMNLabel />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape id="Event_1qwztvn_di" bpmnElement="Event_1qwztvn">
        <dc:Bounds x="792" y="102" width="36" height="36" />
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge id="Flow_109kst5_di" bpmnElement="Flow_109kst5" bioc:stroke="#060606" bioc:fill="none">
        <di:waypoint x="209" y="120" />
        <di:waypoint x="330" y="120" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_0wd9dr1_di" bpmnElement="Flow_0wd9dr1" bioc:stroke="#060606" bioc:fill="none">
        <di:waypoint x="430" y="120" />
        <di:waypoint x="560" y="120" />
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge id="Flow_1s2a4e5_di" bpmnElement="Flow_1s2a4e5" bioc:stroke="#060606" bioc:fill="none">
        <di:waypoint x="660" y="120" />
        <di:waypoint x="792" y="120" />
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

这个好比建模

@SpringBootTest
@Slf4j
class DemoApplicationTests {

    @Autowired
    private RepositoryService repositoryService;

    //@Test
    @ParameterizedTest
    @ValueSource(strings = {"D:\\test\\bpmn-01.xml"})
    public void contextLoads(String file) {
        Model model = repositoryService.newModel();//快速新建一个流程模型的对象
        model.setName("单人审批请假流程"); // 设置名称
        model.setCategory("学习"); // 设置分类
        //保存到ACT_RE_MODEL
        repositoryService.saveModel(model);// 保存BPMN设计好的流程文件的方法
        String xml = FileUtil.readUtf8String(file);
        //保存到ACT_GE_BYTEARRAY(可以看作是ACT_RE_MODEL的附件表)
        repositoryService.addModelEditorSource(model.getId(),xml.getBytes());// 保存xml信息
        assertNotNull(model.getId()); // 断言插入成功
        //4bc2b9d5-e292-11ef-8d3a-00ff5bcf811d
        System.out.println("model.getId() = " + model.getId());
    }

}

ACT_RE_MODEL表

ACT_GE_BYTEARRAY表

建模之后  要发布(部署) 为什么要这么做? 万一需要修改呢  建模之后需要修改

事实上部署的那张表就好比把建模的这张表数据拷贝了一份过去

那么部署之后  每启动一次  就是一个流程实例(当然流程里的填写的表单数据不一样)

模型好比.XML文件   部署好比model的备份   

部署

  @Autowired
    RuntimeService runtimeService;

    /**
     * 流程部署测试
     */
    @Test
    //示例 这样方式 "单人审批请假流程"不能重复
    public void testDeployment(){
        // 通过名称查询出之前保存的流程图
        Model model = repositoryService.createModelQuery().modelName("单人审批请假流程").singleResult();
        assertNotNull(model); // 断言查询成功
        byte[] xml = repositoryService.getModelEditorSource(model.getId());// 获取内容
        String xmlName = "222.bpmn20.xml";//bpmn20.xml结尾是一个约定
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
                .key(model.getKey())
                .name(model.getName())
                .category(model.getCategory())
                .enableDuplicateFiltering()// 自动去重
                .tenantId(model.getTenantId())
                .addBytes(xmlName, xml);// 设置部署的xml名称、内容
        Deployment deploy = deploymentBuilder.deploy(); // 部署
        assertNotNull(deploy.getId()); // 断言插入成功
        model.setDeploymentId(deploy.getId());
        repositoryService.saveModel(model); // 部署的id更新回Model对象
        log.info("new deployment id : {}", model.getId());
    }

    @Test
    public void testDeployment2() {
        String modelId = "99a327d6-e2b1-11ef-b8e6-00ff5bcf811d";
        String xmlName = UUID.randomUUID()+".bpmn20.xml";//这个只是个名称 无所谓 把byte[]放进去
        Model model = repositoryService.getModel(modelId); // 查询模型基本信息
        byte[] xmlContent = repositoryService.getModelEditorSource(modelId); // 读取模型的xml内容
        DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
                .category(model.getCategory())
                .name(model.getName())
                .addBytes(xmlName, xmlContent);
        //存到act_re_deployment
        Deployment deployment = deploymentBuilder.deploy(); // 部署(把原模型文件复制一份)
        model.setDeploymentId(deployment.getId());
        repositoryService.saveModel(model); // 把部署的id回写到模型表中
    }

运行  需要一个流程定义ID  所谓流程定义ID就是BPMN文件中的process id =xxxxxx

也就是act_re_procdef表中的KEY_字段

返回的ProcessInstance就是流程实例  act_hi_procinst 流程实例表

/**
     * 运行测试
     */
    @ParameterizedTest
    @ValueSource(strings = {"hiss_process_12_1692440641431"})
    public void testRun(String key){
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(key);
        assertNotNull(processInstance.getId()); // 断言插入成功
        System.out.println("processInstance.getId() = " + processInstance.getId());
    }

运行实例

会往这些表中插入数据

  1. ACT_HI_TASKINST  任务分配表(只记录有人参与的节点信息) 
  2. ACT_HI_PROCINST  流程定义表(就是部署之后从model复制了一份)
  3. ACT_HI_ACTINST 
  4. ACT_HI_IDENTITYLINK 运行时用户关系表   任务与用户/用户组的关系表
  5. ACT_RU_EXECUTION  运行时流程执行实例(记录当前执行节点信息)
  6. ACT_RU_TASK     运行时任务表   任务指派给谁 里面的ASSIGNEE_字段 可定义为色(需要人参与的节点)  
  7. 另外一张是 act_ru_variable  ;运行时变量表  这里没有变量插入

这些表通常与工作流或流程引擎(如 Activiti 或 Flowable)相关,存储了任务、流程实例、执行、身份链接等数据。 ​

act_ru_task

上面    建模   部署   运行 都已经完成了 

下面开始办理功能

大致分为三个方法来完成上面BPMN的功能

节点二就是将请假单填写好  例如请假人  请假天数  原因等等   然后经理审批同意或拒绝

@Autowired
    private TaskService taskService;
    /**
     * 完成填写清单
     * @param taskId
     */
    @ParameterizedTest
    @ValueSource(strings = {"4da685db-e312-11ef-9761-00ff5bcf811d"})
    //说就是 act_ru_task 表的ID
    public void testCompleteNode2(String taskId) {
        //存储请假单的信息
        //流程变量:给流程传递的一些表单数据值
        Map<String,Object> variables = new HashMap<>();
        variables.put("apply","张三");
        variables.put("days","好几年");
        variables.put("reason","因公 visited");
        variables.put("startDate","2022-02-21");
        variables.put("endDate","2025-02-22");
        //taskService.complete(taskId);//告诉Activiti当前任务已经完成,可以下一步了
        taskService.complete(taskId, variables);
    }

运行上面的会有个报错  没关系 Security  L78Z的东西

在 act_ru_variable中

而在act_hi_varinst表中也会记录一份

原因是act_ru_variable中的记录在流程结束会清除   而act_hi_varinst做为历史记录

下面流程已经到经理审批了  那么审批暂时就同意   拒绝

/**
     * 同意
     * @param taskId
     */
    @ParameterizedTest
    //act_ru_task 表的ID因为已经变成李四  当然实际运用的时候关联查
    @ValueSource(strings = {"465544b8-e31b-11ef-9061-00ff5bcf811d"})
    public void testAgree(String taskId) {
        // 把【同意】看做是填写的【审批表单(包括:审批结果、审批意见)】中的approvalStatus字段
        Map<String, Object> variables = new HashMap<>();
        variables.put("approvalStatus","同意");//都是变量   实际中定义一个变量名  来表示这个变量是表示同意的
        variables.put("approvalNote","同意的备注意见:请好好玩,玩好了再上班");
        //taskService.complete(taskId);//告诉Activiti当前任务已经完成,可以下一步了 那么此时已经结束了
        taskService.complete(taskId,variables);//同意  但是有审批意见(变量)等
    }

act_ru_task表和act_ru_variable表中的数据全部已经清空了

历史活动表中的数据

要测试不同意  因为前面流程已经结束了

需要重新运行下

/**
     * 拒绝
     * @param processInstanceId
     */
    @ParameterizedTest
    //act_ru_execution表中的流程实例的id
    @ValueSource(strings = {"d3e0a207-e31f-11ef-977d-00ff5bcf811d"})
    public void testReject(String processInstanceId) {
        String reason="就是不同意就是不同意";
        Map<String, Object> variables = new HashMap<>();
        variables.put("approvalStatus", "不同意");//这个字段记录审批同意或者拒绝
        variables.put("approvalNote", reason);

        // 记录流程变量
        runtimeService.setVariables(processInstanceId, variables);

        // 添加流程变量,表示任务被拒绝
        runtimeService.deleteProcessInstance(processInstanceId, reason);
    }

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;