一、Flowable开发—简介安装
1. Flowable简介
- 官网地址:https://www.flowable.org/
- Flowable6.3中文教程:https://tkjohn.github.io/flowable-userguide/#_introduction
- 文档地址:https://www.flowable.org/docs/userguide/index.html#_getting_started
- 中文文档:https://tkjohn.github.io/flowable-userguide/#_getting_started
- Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。
- Flowable可以十分灵活地加入你的应用/服务/构架。可以将JAR形式发布的Flowable库加入应用或服务,来嵌入引擎。 以JAR形式发布使Flowable可以轻易加入任何Java环境:Java SE;Tomcat、Jetty或Spring之类的servlet容器;JBoss或WebSphere之类的Java EE服务器,等等。 另外,也可以使用Flowable REST API进行HTTP调用。也有许多Flowable应用(Flowable Modeler, Flowable Admin, Flowable IDM 与 Flowable Task),提供了直接可用的UI示例,可以使用流程与任务。
可以在官网下载对应的jar包在本地部署运行,官方提供了下面的五个应用程序:
Flowable引擎在使用前需要先通过配置来初始化ProcessEngine。
- 初始化ProcessEngineConfiguration一般有两种方式:
- 通过Spinrg配置文件进行依赖注入,通过flowable.cfg.xml文件来初始化ProcessEngineConfiguration(这里的文件名必须为flowable.cfg.xml,否则Flowable识别不到)
- 通过编写程序的方式来构造ProcessEngineConfiguration对象
流程引擎API架构图:
流程引擎API架构图
模型图:
模型图
ProcessEngineConfiguration在初始化过程中会同时初始化数据库,如果数据库已经存在,则不会做创建更新操作,如果数据库不存在,则会默认执行数据库创建脚本。
3. 流程引擎初体验
- 简单了解Bpmn
- Task任务:
用户任务(userTask)
系统任务(serviceTask ) - Event事件:
定时器事件(timerEventDefinition) - Gateway网关:
排他网关(exclusive gateway)
- Task任务:
- 目标:实现以下简化版的请假流程
请假流程图(简单版)
步骤1:定义相应的BPMN文件
步骤2:配置flowable.cfg.xml
步骤3:将流程定义添加到Repository仓储中
步骤4:Runtime开始一个流程实例
4. 下载安装
4.1. 下载
- 下载地址:https://github.com/flowable/flowable-engine/releases/download/flowable-6.4.2/flowable-6.4.2.zip
4.2. 解压
**目标结构**
4.3. 部署war包
4.3.1. 部署
- 拷贝war包到Tomcat的wapapps目录:
- 部署
4.3.2. 启动Tomcat
- 用户名 / 密码: admin/test
- 地址:
应用权限
4.3.3.数据库分成两套
- 项目数据库:项目所需表和flowable通过jar包生成的表(34张)
- Flowable数据库:用于流程图创建保存,测试,监控(74张)
- 项目部署只需项目数据库
5. Flowable的用户权限体系
在接入Flowable的用户权限体系的时候,有四种方式:
- 使用Flowable提供的默认IdmEngine进行用户体系管理,该引擎包含了用户、组的概念。
- 集成LDAP,实现轻量级用户权限管理。通过IdentityService进行认证,用于由IdentityService处理所有认证业务的场景。
- 实现IdmIdentityService接口,自定义实现用户、组的查询
- 接入自定义的权限体系
用户id => 获取到租户id、角色id集、部门id集- 单用户(assignee=”用户id”)、多用户(candidateUsers=”用户id1,用户id2”)
- 单角色、多角色(candidateGroups=”:角色id1,:角色id2”)
- 单部门、多部门(candidateGroups=”部门id1:,部门id2:”)
- 角色或部门(candidateGroups=”角色id1:, :部门id1”)
- 角色且部门
6. 数据表结构
ACT_RE_ *:RE代表repository。具有此前缀的表包含静态信息,例如流程定义和流程资源(图像,规则等)。
ACT_RU_ *:RU代表runtime。这些是包含运行时的流程实例,用户任务,变量,作业等的运行时数据的运行时表。
Flowable仅在流程实例执行期间存储运行时数据,并在流程实例结束时删除记录。这使运行时表保持小而快。
ACT_HI_ *:HI代表history。这些是包含历史数据的表,例如过去的流程实例,变量,任务等。
ACT_GE_ *:general数据,用于各种用例。
ACT_ID_*:Idm的用户、组
数据表: ACT_HI_ACTINST 流程实例的历史运行节点表 ACT_HI_TASKINST 流程实例的历史任务表 ACT_HI_VARINST 流程实例的历史运行节点的变量表 ACT_HI_PROCINST 流程历史部署记录 ACT_HI_IDENTITYLINK 对应ACT_RU_IDENTITYLINK的历史记录表 ACT_RE_DEPLOYMENT 流程部署 ACT_RE_PROCDEF 流程定义表 ACT_RU_EXECUTION 流程实例执行过程的所有节点记录 ACT_RU_IDENTITYLINK 流程实例运行过程中,各节点对应的用户 ACT_RU_TASK 流程实例运行时的任务表 ACT_RU_VARIABLE 流程实例运行时节点的变量表 ACT_GE_BYTEARRAY 资源文件表
7. Docker环境运行
7.1. 前置条件
Docker 环境
7.2. 运行 Flowable6.4.2
Docker Hub上提供了所有工作流的UI应用程序。
要启动 Flowable REST 应用需要H2内存数据库:
[root@localhost ~]# docker run -p8080:8080 flowable/flowable-rest
这个API文档的访问地址为:http://localhost:8080/flowable-rest/docs/
用户名:rest-admin 密码:test
要运行完整的Flowable 工作流,可以运行run the ‘All-in-One’ Docker 镜像,这个镜像包含Flowable IDM、Modeler、Task 、Admin UI 应用,运行容器为Tomcat,数据为内存数据库H2。
[root@localhost ~]# docker run -p8080:8080 flowable/all-in-one
Flowable Modeler; http://localhost:8080/flowable-modeler
Flowable Task; http://localhost:8080/flowable-task
Flowable Admin; http://localhost:8080/flowable-admin
Flowable IDM; http://localhost:8080/flowable-idm
7.3. 运行测试
1) 用户组权限管理
(用户名/密码: admin/test)
http://192.168.247.130:8080/flowable-idm/#/login
用户管理
2) 流程定义管理
http://192.168.247.130:8080/flowable-modeler
流程图
流程图
3) 用户任务管理
http://192.168.247.130:8080/flowable-task/#/
任务管理
4) 后台管理
http://192.168.247.130:8080/flowable-admin
二、Flowable开发—主要引擎
1. Flowable五大引擎
- flowable包含五个引擎,分别是:
1、内容引擎 ContentEngine
2、身份识别引擎 IdmEngine
3、表单引擎 FormEngine
4、决策引擎 DmnEngine
5、流程核心引擎 ProcessEngine
- Flowable有五大引擎,每个之间都是相互独立互不影响。
- ProcessEngine是里面最核心也是最重要的一个引擎,如果失去它那Flowable也就意义了。
2. 引擎包含的服务
每个引擎由相对应的 EngineConfiguration进行创建,在创建过程中对每个引擎使用的服务进行初始化。
2.1. 内容引擎 ContentEngine
内容引擎包含的服务有:
1) ContentManagementService
ContentManagementService提供对数据库表的管理操作,包括:
Map<String, Long> getTableCount() 获取每个表的记录数量;
String getTableName(Class<?> flowableEntityClass); 根据实体类获得对应的数据库表名;
TableMetaData getTableMetaData(String tableName); 根据数据库表名获得表的列名和列类型;
TablePageQuery createTablePageQuery(); 创建一个可以进行排序、根据条件分页的查询类。
2) ContentService
实现对内容的创建、删除、保存和获取的基本操作。
ContentItem newContentItem();
void saveContentItem(ContentItem contentItem);
void saveContentItem(ContentItem contentItem, InputStream inputStream);
InputStream getContentItemData(String contentItemId);
void deleteContentItem(String contentItemId);
void deleteContentItemsByProcessInstanceId(String processInstanceId);
void deleteContentItemsByTaskId(String taskId);
ContentItemQuery createContentItemQuery();
3) ContentEngineConfiguration
ContentEngineConfiguration最主要的作用是提供Mybatis的封装,实现数据库相关配置的获取。
同时,内容引擎配置还提供了操作系统级的文件操作的路径设置、文件读取、文件保存的功能。
2.2. 身份识别引擎 IdmEngine
身份识别引擎包含的服务有:
1) IdmIdentityService
提供用户的创建、修改、删除、密码修改、登录、用户头像设置等;
提供组Group的创建、删除、用户与组关系的关联、删除关联;
提供权限的创建、删除、关联等。
2) IdmManagementService
对身份识别相关的数据库表进行统计、获取表的列信息。
3) IdmEngineConfiguration
提供数据库配置信息。
2.3. 表单引擎 FormEngine
表单引擎包含的服务有:
FormManagementService
FormRepositoryService
FormService
FormEngineConfiguration
2.4. 决策引擎 DmnEngine
决策引擎包含的服务有:
DmnManagementService
DmnRepositoryService
DmnRuleService
DmnHistoryService
DmnEngineConfiguration
2.5. 流程引擎 ProcessEngine
流程引擎包含的服务有:
RepositoryService
RuntimeService
HistoryService
IdentityService
TaskService
FormService
ManagementService
DynamicBpmnService
三、Flowable开发—SpringBoot集成
1. Maven依赖
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库操作依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.6</version>
</dependency>
<!--mybatis-plus 插件-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
<!--工作流-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.4.2</version>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
2. application.properties配置
server.port=7001
# 数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/workflow_flowable?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2b8
spring.datasource.username=root
spring.datasource.password=1234
# 数据库连接池
spring.datasource.druid.filters=stat
#spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
# 配置初始化大小/最小/最大
spring.datasource.druid.initial-size=2
spring.datasource.druid.min-idle=2
spring.datasource.druid.max-active=30
# 获取连接等待超时时间
spring.datasource.druid.max-wait=60000
# 间隔多久进行一次检测,检测需要关闭的空闲连接
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 一个连接在池中最小生存的时间
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.validation-query=SELECT 'x'
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
# 打开PSCache,并指定每个连接上PSCache的大小。
# oracle设为true,mysql设为false。分库分表较多推荐设置为false
spring.datasource.druid.pool-prepared-statements=false
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
## mybatis-plus配置
mybatis-plus.mapper-locations=classpath*:/mappers/**/*.xml
# 实体扫描,多个package用逗号或者分号分隔
mybatis-plus.type-aliases-package=com.xtsz.workflow.entity
# 配置banner
mybatis-plus.global-config.banner=false
# #主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
mybatis-plus.global-config.db-config.id-type=id_worker
# 字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断"
#mybatis-plus.global-config.db-config.field-strategy=not_empty
# 逻辑删除全局值(1表示已删除,这也是Mybatis Plus的默认配置)
mybatis-plus.global-config.db-config.logic-delete-value=1
#逻辑未删除全局值(0表示未删除,这也是Mybatis Plus的默认配置)
mybatis-plus.global-config.db-config.logic-not-delete-value=0
# 配置返回数据库(column下划线命名&&返回java实体是驼峰命名),
# 自动匹配无需as(没开启这个,SQL需要写as: select user_id as userId)
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.cache-enabled=false
# 设置全局属性用于控制数据库的类型
mybatis-plus.configuration-properties.dbType=mysql
#在格式:logging.level.Mapper类的包=debug 会在控制台打印出sql语句
#logging.level.com.xtsz.admin.modules.system.mapper=debug
# 上传附件的目录设置以及系统一块模板word、excel的存放路径
# 这样做的原因是由于spring boot发布时打包成了jar,所以没有办法往jar中写文件
filepath.uploadpath=D:\\uploadfile\\
filepath.templatepath=D:\\templatefile\\
# flowable spring boot时自动部署resource/processes中的流程文件
flowable.check-process-definitions=true
# db-identity-used: true
# 自动生成flowable相关表 第一次生成后建议关闭提高运行速度
# 将databaseSchemaUpdate设置为true。
# 当Flowable发现库与数据库表结构不一致时,会自动将数据库表结构升级至新版本。
flowable.database-schema-update=true
# 保存历史数据级别设置为full最高级别,便于历史数据的追溯
flowable.history-level=full
# 关闭定时任务JOB
flowable.async-executor-activate=false
测试方便flowable配置为默认的即可。为了测试时方便看日志信息,我这里将flowable的定时job功能暂时关闭。
初次运行时flowable会将自动执行flowable中的初始化脚本完成工作流所需要的数据表的建立,如果指定的数据库中还未创建过flowable的相关数据表的话。
3. 定义流程文件
flowable建议采用业界标准BPMN2.0的XML来描述需要定义的工作流。
- 创建目录processes 在项目的resource目录下,创建目录processes。
创建目录processes - 添加流程文件 ExpenseProcess.bpmn20.xml
流程定义文件:文件的命名必须是XXXX.bpmn20.xml,注意命名规范,扩展名必须是bpmn20.xml。
流程定义图片:用BPMN2.0规范定义的各种图形描绘(BMPN2.0的符号及画布上的坐标信息),一般是PNG格式。
表单文件:把表单内容保存在一个文件中,扩展名为drl。
规则文件:扩展名为drl。
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">
<process id="Expense" name="ExpenseProcess" isExecutable="true">
<documentation>报销流程</documentation>
<startEvent id="start" name="开始"></startEvent>
<userTask id="fillTask" name="出差报销" flowable:assignee="${taskUser}">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler">
<![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<exclusiveGateway id="judgeTask"></exclusiveGateway>
<userTask id="directorTak" name="经理审批">
<extensionElements>
<flowable:taskListener event="create"
class="com.xtsz.workflow.handler.ManagerTaskHandler"></flowable:taskListener>
</extensionElements>
</userTask>
<userTask id="bossTask" name="老板审批">
<extensionElements>
<flowable:taskListener event="create"
class="com.xtsz.workflow.handler.BossTaskHandler"></flowable:taskListener>
</extensionElements>
</userTask>
<endEvent id="end" name="结束"></endEvent>
<sequenceFlow id="directorNotPassFlow" name="驳回" sourceRef="directorTak" targetRef="fillTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="bossNotPassFlow" name="驳回" sourceRef="bossTask" targetRef="fillTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='驳回'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow1" sourceRef="start" targetRef="fillTask"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="fillTask" targetRef="judgeTask"></sequenceFlow>
<sequenceFlow id="judgeMore" name="大于500元" sourceRef="judgeTask" targetRef="bossTask">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${money > 500}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="bossPassFlow" name="通过" sourceRef="bossTask" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="directorPassFlow" name="通过" sourceRef="directorTak" targetRef="end">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${outcome=='通过'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="judgeLess" name="小于500元" sourceRef="judgeTask" targetRef="directorTak">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${money <= 500}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_Expense">
<bpmndi:BPMNPlane bpmnElement="Expense" id="BPMNPlane_Expense">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="285.0" y="135.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fillTask" id="BPMNShape_fillTask">
<omgdc:Bounds height="80.0" width="100.0" x="405.0" y="110.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="judgeTask" id="BPMNShape_judgeTask">
<omgdc:Bounds height="40.0" width="40.0" x="585.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="directorTak" id="BPMNShape_directorTak">
<omgdc:Bounds height="80.0" width="100.0" x="735.0" y="110.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="bossTask" id="BPMNShape_bossTask">
<omgdc:Bounds height="80.0" width="100.0" x="555.0" y="255.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="28.0" width="28.0" x="771.0" y="281.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="315.0" y="150.0"></omgdi:waypoint>
<omgdi:waypoint x="405.0" y="150.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="505.0" y="150.16611295681062"></omgdi:waypoint>
<omgdi:waypoint x="585.4333333333333" y="150.43333333333334"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="judgeLess" id="BPMNEdge_judgeLess">
<omgdi:waypoint x="624.5530726256983" y="150.44692737430168"></omgdi:waypoint>
<omgdi:waypoint x="735.0" y="150.1392757660167"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="directorNotPassFlow" id="BPMNEdge_directorNotPassFlow">
<omgdi:waypoint x="785.0" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="785.0" y="37.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="37.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="110.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="bossPassFlow" id="BPMNEdge_bossPassFlow">
<omgdi:waypoint x="655.0" y="295.0"></omgdi:waypoint>
<omgdi:waypoint x="771.0" y="295.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="judgeMore" id="BPMNEdge_judgeMore">
<omgdi:waypoint x="605.4340277777778" y="169.56597222222223"></omgdi:waypoint>
<omgdi:waypoint x="605.1384083044983" y="255.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="directorPassFlow" id="BPMNEdge_directorPassFlow">
<omgdi:waypoint x="785.0" y="190.0"></omgdi:waypoint>
<omgdi:waypoint x="785.0" y="281.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="bossNotPassFlow" id="BPMNEdge_bossNotPassFlow">
<omgdi:waypoint x="555.0" y="295.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="295.0"></omgdi:waypoint>
<omgdi:waypoint x="455.0" y="190.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
这样当flowable框架启动的时候它会默认加载resource目录下的processes时就可以将此流程配置加载到数据库进行持久化了。
flowable:assignee=”${taskUser}” 与代码Map中的taskUser键对应。
flowable:taskListener 中的class指定处理器。
4. 代码编写
- 定义配置类
FlowableConfig.java
/**
* 解决flowable图片中的中文乱码
*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Override
public void configure(SpringProcessEngineConfiguration engineConfiguration) {
engineConfiguration.setActivityFontName("宋体");
engineConfiguration.setLabelFontName("宋体");
engineConfiguration.setAnnotationFontName("宋体");
}
}
- 处理器类
public class BossTaskHandler implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("老板");
}
}
public class ManagerTaskHandler implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("经理");
}
}
- 控制器类
@RestController
@RequestMapping(value = "expense")
public class ExpenseController {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
@Resource
private ProcessEngine processEngine;
/**
* 1. 添加报销
* 接收用户的一个请求传入用户的ID和金额以及描述信息来
* 开启一个报销流程,并返回给用户这个流程的Id
* @param userId 用户Id
* @param money 报销金额
* @param descption 描述
*/
@RequestMapping(value = "add")
public String addExpense(String userId, Integer money, String descption) {
// 启动流程
HashMap<String, Object> map = new HashMap<>();
map.put("taskUser", userId);
map.put("money", money);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("Expense", map);
return "提交成功.流程Id为:" + processInstance.getId();
}
/**
* 获取审批管理列表
* 获取出此用户需要处理的流程
*/
@RequestMapping(value = "/list")
public Object list(String userId) {
List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
for (Task task : tasks) {
System.out.println(task.toString());
}
return tasks.toArray().toString();
}
/**
* 批准
* 通过前端传入的任务ID来对此流程进行同意处理
* @param taskId 任务ID
*/
@RequestMapping(value = "apply")
public String apply(String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
throw new RuntimeException("流程不存在");
}
//通过审核
HashMap<String, Object> map = new HashMap<>();
map.put("outcome", "通过");
taskService.complete(taskId, map);
return "processed ok!";
}
/**
* 拒绝
*/
@RequestMapping(value = "reject")
public String reject(String taskId) {
HashMap<String, Object> map = new HashMap<>();
map.put("outcome", "驳回");
taskService.complete(taskId, map);
return "reject";
}
/**
* 生成流程图
*
* @param processId 任务ID
*/
@RequestMapping(value = "processDiagram")
public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) throws Exception {
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId).singleResult();
//流程走完的不显示图
if (pi == null) {
return;
}
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
//使用流程实例ID,查询正在执行的执行对象表,返回流程实例对象
String InstanceId = task.getProcessInstanceId();
List<Execution> executions = runtimeService
.createExecutionQuery()
.processInstanceId(InstanceId)
.list();
//得到正在执行的Activity的Id
List<String> activityIds = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (Execution exe : executions) {
List<String> ids = runtimeService.getActiveActivityIds(exe.getId());
activityIds.addAll(ids);
}
//获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.generateDiagram(bpmnModel,"png",activityIds,flows,engconf.getActivityFontName(), engconf.getLabelFontName(),engconf.getAnnotationFontName(), engconf.getClassLoader(),1.0,true);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = httpServletResponse.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
}
5. 运行测试
- 创建一个流程
访问:http://localhost:7001/expense/add?userId=123&money=123321
创建一个流程
返回:提交成功.流程Id为:1294e612-f289-11e9-8bad-005056c00008 - 查询待办列表
访问:http://localhost:7001/expense/list?userId=123
查询待办列表
输出:Task[id=129979fb-f289-11e9-8bad-005056c00008, name=出差报销]
查询待办列表 - 同意
访问:http://localhost:7001/expense/apply?taskId=129979fb-f289-11e9-8bad-005056c00008
同意
返回:processed ok! - 生成流程图
访问:http://localhost:7001/expense/processDiagram?processId=1294e612-f289-11e9-8bad-005056c00008
生成流程图
6. 常见问题
- 数据库版本问题
请使用:5.x.x 版本,不要太高。 - 自动布署问题
配置文件:
# flowable spring boot时自动部署resource/processes中的流程文件
flowable.check-process-definitions=true
四、Flowable开发—流程部署
1. 部署相关的表
- act_re_deployment:流程模型部署对象表
每部署一次生成一条记录,首先生成这条数据,它的id主键将会被act_re_procdef和act_ge_bytearray作为外键。 - actre_procdef:流程定义表
一次部署可能采用zip/bar进行部署,里面是有多份流程定义文件xml的,这时候act_re_deployment只有一条部署信息,但act_re_procdef有多个记录(一个流程定义对应一条),这个表有DEPLOYMENT_ID外键字段,用它关联act_re_deployment。 - act_ge_bytearray:资源文件表
流程模型资源文件的真正存放地方,它每部署一次就会产生2条记录,一条是关于bpmn规范的文件内容存放在BYTES字段中,另一条是图片信息,采用二进制格式存储。
提示:可以部署后解析bpmn文件的内容自动生成流程图,实现流程图的跟踪线路。 - act_re_model:这张表,在xml进行部署时,它没有内容(flowable放弃了此表改用act_de_model保存流程模型信息)
2. Maven依赖
针对Model部署需增加:
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-json-converter</artifactId>
<version>6.4.2</version>
</dependency>
<!--流程设计器-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-modeler-rest</artifactId>
<version>6.4.2</version>
<exclusions>
<exclusion>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</exclusion>
</exclusions>
</dependency>
3. 流程图部署
- 流程资源xml部署
@SpringBootTest
@Slf4j
class WorkflowFlowableApplicationTests {
@Autowired
private RepositoryService repositoryService;
@Resource
private ProcessEngine processEngine;
/**
* 流程资源xml部署
*/
@Test
void deployFlow() {
String filePath = "processes/OrderApproval.bpmn20.xml";
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment()
.addClasspathResource(filePath);
Deployment deployment = deploymentBuilder.deploy();
log.info("成功:部署工作流成:" + filePath);
log.info("部署ID:"+deployment.getId());
log.info("部署时间:"+deployment.getDeploymentTime());
}
}
- zip/bar打包,多个流程资源文件部署
/**
* zip/bar打包,多个流程资源文件部署
*/
@Test
void deployFlowZip() {
/**
* 需要将所有流程图进行打包
*/
String file = "diagrams/approve.zip";
InputStream in = this.getClass().getClassLoader().getResourceAsStream(file);
ZipInputStream zipInputStream = new ZipInputStream(in);
// 获取流程定义和部署对象相关的Service
Deployment deployment = processEngine.getRepositoryService()
// 创建部署对象
.createDeployment()
// 使用zip方式部署,将approve.bpmn和approve.png压缩成zip格式的文件
.addZipInputStream(zipInputStream)
.deploy(); // 完成部署
log.info("部署ID:" + deployment.getId());
log.info("部署时间:" + deployment.getDeploymentTime());
}
- Model部署
/**
* Model部署
*/
void deployFlowModel() {
String modelId = "modelId";
// 通过act_de_model中存放的Modeler内容来部署
Model modelData = repositoryService.getModel(modelId);
ObjectNode modelNode = null;
try {
// 获取模型
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
if (null == bytes) {
log.error("模型数据为空,请先设计流程并成功保存,再进行发布。");
}
modelNode = (ObjectNode) new ObjectMapper().readTree(bytes);
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
if (model.getProcesses().size() == 0) {
log.error("数据模型不符要求,请至少设计一条主线流程。");
}
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
// 发布流程
String processName = modelData.getName() + ".bpmn20.xml";
repositoryService.createDeployment().name(modelData.getName()).addString(processName, new String(bpmnBytes, "UTF-8")).deploy();
} catch (IOException e) {
e.printStackTrace();
}
}
4. 流程部署示例
- 创建流程图
流程图
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
<process id="test_bpmn" name="测试BPMN模型" isExecutable="true">
<documentation>测试BPMN模型</documentation>
<startEvent id="start" name="开始"></startEvent>
<endEvent id="end" name="结束"></endEvent>
<userTask id="testUser" name="用户任务测试"></userTask>
<sequenceFlow id="sid-8D834F3C-45A8-4C88-9AD1-1AC426CC9002" sourceRef="start" targetRef="testUser"></sequenceFlow>
<sequenceFlow id="sid-AB59612A-1B33-4FB8-8758-5D773EDF9C44" sourceRef="testUser" targetRef="end"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_test_bpmn">
<bpmndi:BPMNPlane bpmnElement="test_bpmn" id="BPMNPlane_test_bpmn">
<bpmndi:BPMNShape bpmnElement="start" id="BPMNShape_start">
<omgdc:Bounds height="30.0" width="30.0" x="210.0" y="60.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="end" id="BPMNShape_end">
<omgdc:Bounds height="28.0" width="28.0" x="525.0" y="61.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="testUser" id="BPMNShape_testUser">
<omgdc:Bounds height="80.0" width="100.0" x="315.0" y="35.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-8D834F3C-45A8-4C88-9AD1-1AC426CC9002" id="BPMNEdge_sid-8D834F3C-45A8-4C88-9AD1-1AC426CC9002">
<omgdi:waypoint x="239.94999779398907" y="75.0"></omgdi:waypoint>
<omgdi:waypoint x="315.0" y="75.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-AB59612A-1B33-4FB8-8758-5D773EDF9C44" id="BPMNEdge_sid-AB59612A-1B33-4FB8-8758-5D773EDF9C44">
<omgdi:waypoint x="414.9499999999903" y="75.0"></omgdi:waypoint>
<omgdi:waypoint x="525.0" y="75.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
- 创建业务方法
public interface IFlowService {
/**
* 部署工作流
*/
Deployment createFlow(String filePath);
}
@Primary
@Service
@Slf4j
public class FlowServiceImpl implements IFlowService {
/**
* Flowable运行时服务
*/
@Autowired
private RepositoryService repositoryService;
@Override
public Deployment createFlow(String filePath) {
//解析BPMN模型看是否成功
XMLStreamReader reader = null;
InputStream inputStream = null;
try {
BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
XMLInputFactory factory = XMLInputFactory.newInstance();
inputStream=new FileInputStream(new File(filePath));
reader = factory.createXMLStreamReader(inputStream);
BpmnModel model = bpmnXMLConverter.convertToBpmnModel(reader);
List<Process> processes = model.getProcesses();
Process curProcess = null;
if (CollectionUtils.isEmpty(processes)) {
log.error("BPMN模型没有配置流程");
return null;
}
curProcess = processes.get(0);
inputStream=new FileInputStream(new File(filePath));
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().name("TEST_FLOW")
.addInputStream(curProcess.getName(),inputStream);
Deployment deployment= deploymentBuilder.deploy();
log.warn("部署流程 name:"+curProcess.getName() + " "+deployment);
return deployment;
}
catch (Exception e){
log.error("BPMN模型创建流程异常",e);
return null;
}
finally {
try {
reader.close();
} catch (XMLStreamException e) {
log.error("关闭异常",e);
}
}
}
}
- 创建控制器
@RestController
@RequestMapping(value = "flow")
@Slf4j
public class FlowController {
@Autowired
private IFlowService flowService;
@RequestMapping("/create")
public Map<String, String> createFlow() throws FileNotFoundException {
Map<String, String> res = new HashMap<>();
String flowPath = "processes/test.bpmn20.xml";
File file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + flowPath);
if (null == flowService.createFlow(file.getAbsolutePath())) {
res.put("msg", "创建流程失败");
res.put("res", "0");
return res;
}
res.put("msg", "创建流程成功");
res.put("res", "1");
return res;
}
}
部署ID表:act_re_deployment
部署记录
布署内容表:act_ge_bytearray
五、Flowable开发—流程审批
1. 流程审批后涉及到的表
表名 | 描述 |
---|---|
act_fo_form_instance | 存储用户填充后表单实例信息,FORMDEFINITION_ID字段 2bb4ecac-cfb8-11e9-9f13-1a1dea14efe7 |
act_fo_form_resource | NAME_字段 form-2bb4ecac-cfb8-11e9-9f13-1a1dea14efe7 |
act_hi_actinst | 历史的流程实例 插入多条数据 |
act_hi_identitylink | 历史的流程运行过程中用户关系 插入多条数据 |
act_hi_procinst | 历史的流程实例 REV_ 1未完成 2已完成 |
act_hi_taskinst | 历史的任务实例 REV_ 1待认领 2等审批 3已审批 |
act_hi_varinst | 历史的流程运行中的变量信息 |
act_ru_actinst | 存储运行时节点信息 与act_hi_actinst同时存储 |
act_ru_execution | 执行实例表和actrun_task表,一起控制了用户任务的产生与完成等,当在并行网关和会签多实例时,它是会产生多个执行实例,IS_ACTIVE这个字段的值都是为1,即激活状态,当每完成一个执行实例时,它会把IS_ACTIVE设为0,非激活状态,当所有执行实例完成后,它才会转移到历史,把这个多实例自动删除 |
act_ru_identitylink | 运行时用户关系 |
act_ru_task | 运行时任务表 |
act_ru_variable | 运行的流程中的变量信息 |
当流程全部走完后,actru表的数据清空了,全部移到了acthi表
2. 流程示例
- 创建流程图
创建OrderApproval.bpmn20.xml文件
创建文件
创建文件
改更扩展名
流程图设计
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:flowable="http://flowable.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.flowable.org/processdef">
<!--订单审批,必须定义ID-->
<process id="OrderApproval" name="订单审批" isExecutable="true">
<!--开始事件-->
<startEvent id="startEvent" name="采购订单"></startEvent>
<!--流程-->
<sequenceFlow id="sequenceFlow-3" sourceRef="startEvent" targetRef="approveTask"></sequenceFlow>
<!--定义任务 flowable:assignee 指定用户ID-->
<userTask id="approveTask" name="订单审批" flowable:assignee="${userId}">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<!--定义网关-->
<exclusiveGateway id="decision"></exclusiveGateway>
<!--定义流程条件-->
<sequenceFlow id="sequenceFlow-9" sourceRef="decision" targetRef="fail">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${!approved}]]></conditionExpression>
</sequenceFlow>
<!--定义任务->同意-->
<serviceTask id="success" name="通过" flowable:class="com.xtsz.workflow.delegate.ReviewApprove"></serviceTask>
<!--定义结束事件-->
<endEvent id="approveEnd"></endEvent>
<endEvent id="rejectEnd"></endEvent>
<sequenceFlow id="sequenceFlow-e" sourceRef="success" targetRef="approveEnd"></sequenceFlow>
<sequenceFlow id="sequenceFlow-1" sourceRef="fail" targetRef="rejectEnd"></sequenceFlow>
<!--定义任务->拒绝-->
<serviceTask id="fail" name="拒绝" flowable:class="com.xtsz.workflow.delegate.ReviewNoApprove"></serviceTask>
<sequenceFlow id="sequenceFlow-5" sourceRef="approveTask" targetRef="decision"></sequenceFlow>
<sequenceFlow id="sequenceFlow-c" sourceRef="decision" targetRef="success">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${approved}]]></conditionExpression>
</sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_OrderApproval">
<bpmndi:BPMNPlane bpmnElement="OrderApproval" id="BPMNPlane_OrderApproval">
<bpmndi:BPMNShape bpmnElement="startEvent" id="BPMNShape_startEvent">
<omgdc:Bounds height="30.0" width="30.0" x="0.0" y="95.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="approveTask" id="BPMNShape_approveTask">
<omgdc:Bounds height="60.0" width="100.0" x="75.0" y="75.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="decision" id="BPMNShape_decision">
<omgdc:Bounds height="40.0" width="40.0" x="230.0" y="90.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="success" id="BPMNShape_success">
<omgdc:Bounds height="60.0" width="100.0" x="320.0" y="0.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="approveEnd" id="BPMNShape_approveEnd">
<omgdc:Bounds height="28.0" width="28.0" x="620.0" y="16.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="rejectEnd" id="BPMNShape_rejectEnd">
<omgdc:Bounds height="28.0" width="28.0" x="570.0" y="175.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="fail" id="BPMNShape_false">
<omgdc:Bounds height="60.0" width="100.0" x="315.0" y="150.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow-5" id="BPMNEdge_sequenceFlow-5">
<omgdi:waypoint x="174.95" y="105.0"></omgdi:waypoint>
<omgdi:waypoint x="202.5" y="105.0"></omgdi:waypoint>
<omgdi:waypoint x="202.5" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="230.0" y="110.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow-c" id="BPMNEdge_sequenceFlow-c">
<omgdi:waypoint x="269.9189252336448" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="282.0" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="282.0" y="30.000000000000004"></omgdi:waypoint>
<omgdi:waypoint x="319.999999999994" y="30.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow-1" id="BPMNEdge_sequenceFlow-1">
<omgdi:waypoint x="414.95000000000005" y="180.0"></omgdi:waypoint>
<omgdi:waypoint x="460.0" y="180.0"></omgdi:waypoint>
<omgdi:waypoint x="460.0" y="189.0"></omgdi:waypoint>
<omgdi:waypoint x="570.0" y="189.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow-e" id="BPMNEdge_sequenceFlow-e">
<omgdi:waypoint x="419.94999999998697" y="30.0"></omgdi:waypoint>
<omgdi:waypoint x="620.0" y="30.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow-3" id="BPMNEdge_sequenceFlow-3">
<omgdi:waypoint x="29.949987029268733" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="52.5" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="52.5" y="105.0"></omgdi:waypoint>
<omgdi:waypoint x="74.99999999999241" y="105.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sequenceFlow-9" id="BPMNEdge_sequenceFlow-9">
<omgdi:waypoint x="269.9189252336448" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="282.0" y="110.0"></omgdi:waypoint>
<omgdi:waypoint x="282.0" y="180.0"></omgdi:waypoint>
<omgdi:waypoint x="314.9999999999916" y="180.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
流程图
- 创建委托
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
/**
* 批准委托类
*/
@Slf4j
public class ReviewApprove implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
//可以发送消息给某人
log.info("通过,userId是:{}",delegateExecution.getVariable("userId"));
}
}
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
/**
* 驳回委托类
*/
@Slf4j
public class ReviewNoApprove implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
//可以发送消息给某人
log.info("拒绝,userId是:{}",delegateExecution.getVariable("userId"));
}
}
- 创建控制器
@RestController
@RequestMapping("/orderFlow")
@Slf4j
public class OrderFlowController {
@Autowired
private RepositoryService repositoryService;
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private HistoryService historyService;
@Resource
private ProcessEngine processEngine;
/**
* 1.提交采购订单的审批请求
*
* @param userId 用户id
*/
@PostMapping("/start/{userId}/{purchaseOrderId}")
public Result startFlow(@PathVariable String userId, @PathVariable String purchaseOrderId) {
HashMap<String, Object> map = new HashMap<>();
map.put("userId", userId);
map.put("purchaseOrderId", purchaseOrderId);
// 流程ID->OrderApproval
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("OrderApproval", map);
String processId = processInstance.getId();
// 名称由布署时指定
String name = processInstance.getName();
log.info(processId + ":" + name);
return Result.ok(processId + ":" + name);
}
/**
* 2.获取用户的任务
*
* @param userId 用户id
*/
@GetMapping("/getTasks/{userId}")
public Result getTasks(@PathVariable String userId) {
List<Task> tasks = taskService.createTaskQuery().taskAssignee(userId).orderByTaskCreateTime().desc().list();
return Result.ok(tasks.toString());
}
/**
* 3.审批通过
*/
@PostMapping("/success/{taskId}")
public Result success(@PathVariable String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
return Result.error("流程不存在");
}
//通过审核
HashMap<String, Object> map = new HashMap<>();
// 在流程图中获取进行处理
map.put("approved", true);
taskService.complete(taskId, map);
return Result.ok("流程审核通过!");
}
/**
* 4.审批不通过
*/
@PostMapping("/fail/{taskId}")
public Result fail(@PathVariable String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
return Result.error("流程不存在");
}
//通过审核
HashMap<String, Object> map = new HashMap<>();
// 在流程图中获取进行处理
map.put("approved", false);
taskService.complete(taskId, map);
return Result.ok();
}
/**
* 5. 生成流程图
*
* @param httpServletResponse
* @param processId
* @throws Exception
*/
@PostMapping(value = "processDiagram")
public void genProcessDiagram(HttpServletResponse httpServletResponse, String processId) {
/**
* 获得当前活动的节点
*/
String processDefinitionId = "";
boolean isFinish = historyService.createHistoricProcessInstanceQuery().finished()
.processInstanceId(processId).count() > 0;
if (isFinish) {// 如果流程已经结束,则得到结束节点
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
} else {// 如果流程没有结束,则取当前活动节点
// 根据流程实例ID获得当前处于活动状态的ActivityId合集
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processInstanceId(processId).singleResult();
processDefinitionId = pi.getProcessDefinitionId();
}
List<String> highLightedActivitis = new ArrayList<String>();
/**
* 获得活动的节点
*/
List<HistoricActivityInstance> highLightedActivitList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list();
for (HistoricActivityInstance tempActivity : highLightedActivitList) {
String activityId = tempActivity.getActivityId();
highLightedActivitis.add(activityId);
}
List<String> flows = new ArrayList<>();
//获取流程图
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
// 注意:如果是PNG格式那么输出的是背景色是黑色,如果连接线上有字不容易看清楚。可以使用bmp
InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivitis, flows, engconf.getActivityFontName(),
engconf.getLabelFontName(), engconf.getAnnotationFontName(), engconf.getClassLoader(), 1.0, true);
OutputStream out = null;
byte[] buf = new byte[1024];
int legth = 0;
try {
out = httpServletResponse.getOutputStream();
while ((legth = in.read(buf)) != -1) {
out.write(buf, 0, legth);
}
} catch (IOException e) {
log.error("操作异常", e);
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(in);
}
}
}
3. 运行测试
- 提交采购订单的审批请求
http://localhost:7001/orderFlow/start/1/1
提交采购订单的审批请求
返回:
{
"msg": "3c6dc4b2-f30a-11e9-bc68-005056c00008:null",
"code": 0
}
- 获取用户的任务
http://localhost:7001/orderFlow/getTasks/1
获取用户的任务
返回:
{
"msg": "[Task[id=3c6dc4b2-f30a-11e9-bc68-005056c00008, name=订单审批]]",
"code": 0
}
{
"msg": "流程审核通过!",
"code": 0
}
- 审批驳回
http://localhost:7001/orderFlow/fail/fac2256e-f30e-11e9-a987-005056c00008
任务ID: fac2256e-f30e-11e9-a987-005056c00008
审批驳回
返回:
{
"msg": "success",
"code": 0
}
- 生成流程图
http://localhost:7001/orderFlow/processDiagram?processId=4fdcbade-f311-11e9-acac-005056c00008
processId: 为流程实例ID
生成流程图
4. 事件监听器实现
事件监听器的唯一要求是实现org.flowable.engine.delegate.event.FlowableEventListener。
一个事件侦听器基类,可用于侦听特定类型的实体或所有实体的实体相关事件。它隐藏掉类型检查,并提供4种方法应覆盖:onCreate(..),onUpdate(..)并onDelete(..)创建实体时,更新或删除。
5. 常见问题
- “code”:401,”error”:”Unauthorized.”
排除配置
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
- 流程文档部署时没生成流程图片
如果流程文档部署时没生成流程图片,且流程定义中包含必要的“图形交换(diagram interchange)”信息,Flowable引擎会生成流程图。
如果由于某种原因,不需要或不希望在部署时生成流程图,可以在流程引擎配置中设置isCreateDiagramOnDeploy参数:
<property name="createDiagramOnDeploy" value="false" />
- Waiting for changelog lock….
数据库中执行:
SELECT `LOCKED` FROM workflow_flowable.ACT_DMN_DATABASECHANGELOGLOCK WHERE ID=1
UPDATE workflow_flowable.ACT_DMN_DATABASECHANGELOGLOCK
SET locked=0 WHERE ID=1
- 没有生成流程图
bpmn 任务中没有加入:
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
六、Flowable开发—Modeler集成
1. Flowable-Modeler功能
https://gitee.com/lwj/flow-modeler-sduty/tree/master/src/main
提供可视化编辑器,编辑BPMN流程,编辑CASE模型,编辑Form表单,编辑App应用,编辑决策表
提供可视化参数配置:每个流程可以配置详细的参数设置,按照流程对应的规范来设计。
提供导入导出功能:方便将流程结果导入到其他应用程序
在我们实际项目中,我们的流程配置和表单都是在一个系统中操作的,不可能在flowable的war包上做流程配置。
所以集成modeler是flowable使用的开端。
2. 源码下载与编译
- 源码下载
下载地址:
https://github.com/flowable/flowable-engine
源码 - 导入idea编译
导入idea
编译
编译
编译结果:
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Flowable 6.5.0-SNAPSHOT:
[INFO]
[INFO] Flowable ........................................... SUCCESS [ 0.326 s]
[INFO] Flowable - BPMN Model .............................. SUCCESS [ 0.767 s]
[INFO] Flowable - Process Validation ...................... SUCCESS [ 0.070 s]
[INFO] Flowable - BPMN Layout ............................. SUCCESS [ 0.106 s]
[INFO] Flowable - Image Generator ......................... SUCCESS [ 0.102 s]
[INFO] Flowable - Engine Common API ....................... SUCCESS [ 0.059 s]
[INFO] Flowable - Variable Service API .................... SUCCESS [ 0.036 s]
[INFO] Flowable - Engine Common ........................... SUCCESS [ 0.455 s]
[INFO] Flowable - BPMN Converter .......................... SUCCESS [ 0.143 s]
[INFO] Flowable - Entity Link Service API ................. SUCCESS [ 0.021 s]
[INFO] Flowable - Entity Link Service ..................... SUCCESS [ 0.163 s]
[INFO] Flowable - Variable Service ........................ SUCCESS [ 0.198 s]
[INFO] Flowable - Identity Link Service API ............... SUCCESS [ 0.028 s]
[INFO] Flowable - Identity Link Service ................... SUCCESS [ 0.118 s]
[INFO] Flowable - Event Subscription Service API .......... SUCCESS [ 0.028 s]
[INFO] Flowable - Event Subscription Service .............. SUCCESS [ 0.127 s]
[INFO] Flowable - Task Service API ........................ SUCCESS [ 0.030 s]
[INFO] Flowable - IDM API ................................. SUCCESS [ 0.023 s]
[INFO] Flowable - Task Service ............................ SUCCESS [ 0.169 s]
[INFO] Flowable - Job Service API ......................... SUCCESS [ 0.023 s]
[INFO] Flowable - Job Service ............................. SUCCESS [ 0.179 s]
[INFO] Flowable Job Spring Service ........................ SUCCESS [ 0.141 s]
[INFO] Flowable - Batch Service API ....................... SUCCESS [ 0.017 s]
[INFO] Flowable - Batch Service ........................... SUCCESS [ 0.124 s]
[INFO] Flowable - IDM Engine .............................. SUCCESS [ 0.229 s]
[INFO] flowable-idm-engine-configurator ................... SUCCESS [ 0.108 s]
[INFO] Flowable - Form API ................................ SUCCESS [ 0.023 s]
[INFO] Flowable - Form Model .............................. SUCCESS [ 0.024 s]
[INFO] flowable-form-json-converter ....................... SUCCESS [ 0.037 s]
[INFO] Flowable - Form Engine ............................. SUCCESS [ 0.211 s]
[INFO] Flowable - CMMN Model .............................. SUCCESS [ 0.062 s]
[INFO] Flowable - DMN Model ............................... SUCCESS [ 0.024 s]
[INFO] Flowable - DMN API ................................. SUCCESS [ 0.058 s]
[INFO] Flowable - CMMN API ................................ SUCCESS [ 0.118 s]
[INFO] Flowable - Content API ............................. SUCCESS [ 0.041 s]
[INFO] Flowable - Engine .................................. SUCCESS [ 1.188 s]
[INFO] Flowable - Form Engine Configurator ................ SUCCESS [ 0.384 s]
[INFO] Flowable - CMMN Converter .......................... SUCCESS [ 0.060 s]
[INFO] Flowable - CMMN Image Generator .................... SUCCESS [ 0.041 s]
[INFO] Flowable - CMMN Engine ............................. SUCCESS [ 0.415 s]
[INFO] Flowable - CMMN Engine Configurator ................ SUCCESS [ 0.477 s]
[INFO] Flowable - App Engine API .......................... SUCCESS [ 0.020 s]
[INFO] Flowable - App Engine .............................. SUCCESS [ 0.168 s]
[INFO] flowable-spring-security ........................... SUCCESS [ 0.045 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.645 s
[INFO] Finished at: 2019-10-20T23:21:14+08:00
[INFO] ------------------------------------------------------------------------
3. Modeler集成
3.1. 拷贝全都静态资源到项目中resources
源码文件
3.2. Maven依赖
由于flowable-modeler的流程设计器页面很多操作会访问后台接口,所以在这里导入依赖文件。
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Springboot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--log4j-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!--健康检查-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--数据库操作依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!--mybatis-plus 插件-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.2</version>
</dependency>
<!--工作流-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-common</artifactId>
<version>6.4.2</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-modeler-conf</artifactId>
<version>6.4.2</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-modeler-rest</artifactId>
<version>6.4.2</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-modeler-logic</artifactId>
<version>6.4.2</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
3.3. 配置文件
配置文件:application.properties
server.port=8002
server.servlet.context-path=/flowable-modeler
management.endpoints.jmx.unique-names=true
# 这是强制使用JDK代理而不是使用CGLIB所必需的。
spring.aop.proxy-target-class=false
spring.aop.auto=false
spring.application.name=flowable-ui-modeler
# 安全配置
spring.security.filter.dispatcher-types=REQUEST,FORWARD,ASYNC
spring.liquibase.enabled=false
# 必须指定用于生成对象名的默认域。否则,当多个spring引导应用程序在同一个servlet容器中启动时
# 所有这些都将使用相同的名称创建(例如com.zaxxer.hikari:name=datasource,type=hikaridatasource)
spring.jmx.default-domain=${spring.application.name}
# 健康检查
# 将所有执行器端点暴露在Web上它们是公开的,但只有经过身份验证的用户才能看到/info和/health
# abd具有access admin的用户才能看到其他用户
management.endpoints.web.exposure.include=*
# 只有在授权用户时才应显示完整的运行状况详细信息
management.endpoint.health.show-details=when_authorized
# 只有具有角色access admin的用户才能访问完整的运行状况详细信息
management.endpoint.health.roles=access-admin
# 数据库 默认H2数据库
spring.datasource.username=flowable
spring.datasource.password=flowable
# 数据库连接池
spring.datasource.hikari.maxLifetime=600000
# 5 minutes
spring.datasource.hikari.idleTimeout=300000
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=50
# 大文件上传限制。设置为-1可设置为“无限制”。以字节表示
spring.servlet.multipart.max-file-size=10MB
配置文件:flowable-default.properties
# spring在角色前面加上role。然而,flowable还没有这个概念,所以我们需要用空字符串覆盖它。
flowable.common.app.role-prefix=
flowable.common.app.idm-url=http://localhost:8002/flowable-idm
flowable.common.app.idm-admin.user=admin
flowable.common.app.idm-admin.password=test
flowable.modeler.app.deployment-api-url=http://localhost:8002/flowable-task/app-api
# Rest API
flowable.modeler.app.rest-enabled=true
flowable.rest.app.authentication-mode=verify-privilege
配置文件:version.properties
type=modeler
version.major=6
version.minor=4
version.revision=2
version.edition=Flowable
3.4. 创建流程模型包
创建包:org.flowable.ui
在java中创建包名:org.flowable.ui
创建包
3.5. 去除认证
1)创建org.flowable.ui.common.rest.idm.remote包,添加类:
import org.flowable.ui.common.model.UserRepresentation;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.common.service.exception.NotFoundException;
import org.flowable.ui.common.service.idm.RemoteIdmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/app")
public class RemoteAccountResource {
@Autowired
private RemoteIdmService remoteIdmService;
/**
* GET /rest/account -> get the current user.
*/
@RequestMapping(value = "/rest/account", method = RequestMethod.GET, produces = "application/json")
public UserRepresentation getAccount() {
// UserRepresentation userRepresentation = null;
// String currentUserId = SecurityUtils.getCurrentUserId();
// if (currentUserId != null) {
// RemoteUser remoteUser = remoteIdmService.getUser(currentUserId);
// if (remoteUser != null) {
// userRepresentation = new UserRepresentation(remoteUser);
//
// if (remoteUser.getGroups() != null && remoteUser.getGroups().size() > 0) {
// List<GroupRepresentation> groups = new ArrayList<>();
// for (RemoteGroup remoteGroup : remoteUser.getGroups()) {
// groups.add(new GroupRepresentation(remoteGroup));
// }
// userRepresentation.setGroups(groups);
// }
//
// if (remoteUser.getPrivileges() != null && remoteUser.getPrivileges().size() > 0) {
// userRepresentation.setPrivileges(remoteUser.getPrivileges());
// }
//
// }
// }
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setFirstName("admin");
userRepresentation.setLastName("admin");
userRepresentation.setFullName("admin");
userRepresentation.setId("admin");
List<String> pris = new ArrayList<>();
pris.add(DefaultPrivileges.ACCESS_MODELER);
pris.add(DefaultPrivileges.ACCESS_IDM);
pris.add(DefaultPrivileges.ACCESS_ADMIN);
pris.add(DefaultPrivileges.ACCESS_TASK);
pris.add(DefaultPrivileges.ACCESS_REST_API);
userRepresentation.setPrivileges(pris);
if (userRepresentation != null) {
return userRepresentation;
} else {
throw new NotFoundException();
}
}
}
2)创建包:org.flowable.ui.common.security 包添加以下类:
import org.flowable.idm.api.User;
import org.flowable.ui.common.model.RemoteUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.ArrayList;
import java.util.List;
public class SecurityUtils {
private static User assumeUser;
private SecurityUtils() {
}
/**
* Get the login of the current user.
*/
public static String getCurrentUserId() {
User user = getCurrentUserObject();
if (user != null) {
return user.getId();
}
return null;
}
/**
* @return the {@link User} object associated with the current logged in user.
*/
public static User getCurrentUserObject() {
if (assumeUser != null) {
return assumeUser;
}
// User user = null;
// FlowableAppUser appUser = getCurrentFlowableAppUser();
// if (appUser != null) {
// user = appUser.getUserObject();
// }
RemoteUser user = new RemoteUser();
// FlowableAppUser appUser = getCurrentFlowableAppUser();
// if (appUser != null) {
// user = appUser.getUserObject();
// }
user.setId("admin");
user.setDisplayName("admin");
user.setFirstName("admin");
user.setLastName("admin");
user.setEmail("admin@admin.com");
user.setPassword("test");
List<String> pris = new ArrayList<>();
pris.add(DefaultPrivileges.ACCESS_MODELER);
pris.add(DefaultPrivileges.ACCESS_IDM);
pris.add(DefaultPrivileges.ACCESS_ADMIN);
pris.add(DefaultPrivileges.ACCESS_TASK);
pris.add(DefaultPrivileges.ACCESS_REST_API);
user.setPrivileges(pris);
return user;
}
public static FlowableAppUser getCurrentFlowableAppUser() {
FlowableAppUser user = null;
SecurityContext securityContext = SecurityContextHolder.getContext();
if (securityContext != null && securityContext.getAuthentication() != null) {
Object principal = securityContext.getAuthentication().getPrincipal();
if (principal instanceof FlowableAppUser) {
user = (FlowableAppUser) principal;
}
}
return user;
}
public static boolean currentUserHasCapability(String capability) {
FlowableAppUser user = getCurrentFlowableAppUser();
for (GrantedAuthority grantedAuthority : user.getAuthorities()) {
if (capability.equals(grantedAuthority.getAuthority())) {
return true;
}
}
return false;
}
public static void assumeUser(User user) {
assumeUser = user;
}
public static void clearAssumeUser() {
assumeUser = null;
}
}
3)创建包:org.flowable.ui.modeler.conf 添加安全配置类:
import org.flowable.ui.common.properties.FlowableRestAppProperties;
import org.flowable.ui.common.security.ActuatorRequestMatcher;
import org.flowable.ui.common.security.ClearFlowableCookieLogoutHandler;
import org.flowable.ui.common.security.DefaultPrivileges;
import org.flowable.ui.modeler.properties.FlowableModelerAppProperties;
import org.flowable.ui.modeler.security.AjaxLogoutSuccessHandler;
import org.flowable.ui.modeler.security.RemoteIdmAuthenticationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
/**
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(org.flowable.ui.modeler.conf.SecurityConfiguration.class);
public static final String REST_ENDPOINTS_PREFIX = "/app/rest";
@Autowired
protected RemoteIdmAuthenticationProvider authenticationProvider;
// @Bean
// public FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean(RemoteIdmService remoteIdmService, FlowableCommonAppProperties properties) {
// FlowableCookieFilterRegistrationBean filter = new FlowableCookieFilterRegistrationBean(remoteIdmService, properties);
// filter.addUrlPatterns("/app/*");
// filter.setRequiredPrivileges(Collections.singletonList(DefaultPrivileges.ACCESS_MODELER));
// return filter;
// }
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
// Default auth (database backed)
try {
auth.authenticationProvider(authenticationProvider);
} catch (Exception e) {
LOGGER.error("Could not configure authentication mechanism:", e);
}
}
@Configuration
@Order(10)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
// @Autowired
// protected FlowableCookieFilterRegistrationBean flowableCookieFilterRegistrationBean;
@Autowired
protected AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// .addFilterBefore(flowableCookieFilterRegistrationBean.getFilter(), UsernamePasswordAuthenticationFilter.class)
.logout()
.logoutUrl("/app/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.addLogoutHandler(new ClearFlowableCookieLogoutHandler())
.and()
.csrf()
.disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
// .antMatchers(REST_ENDPOINTS_PREFIX + "/**").hasAuthority(DefaultPrivileges.ACCESS_MODELER);
.antMatchers(REST_ENDPOINTS_PREFIX + "/**").permitAll();
}
}
//
// BASIC AUTH
//
@Configuration
@Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected final FlowableRestAppProperties restAppProperties;
protected final FlowableModelerAppProperties modelerAppProperties;
public ApiWebSecurityConfigurationAdapter(FlowableRestAppProperties restAppProperties,
FlowableModelerAppProperties modelerAppProperties) {
this.restAppProperties = restAppProperties;
this.modelerAppProperties = modelerAppProperties;
}
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable();
http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").permitAll();
// if (modelerAppProperties.isRestEnabled()) {
//
// if (restAppProperties.isVerifyRestApiPrivilege()) {
// http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").hasAuthority(DefaultPrivileges.ACCESS_REST_API).and().httpBasic();
// } else {
// http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").authenticated().and().httpBasic();
//
// }
//
// } else {
// http.antMatcher("/api/**").authorizeRequests().antMatchers("/api/**").denyAll();
//
// }
}
}
//
// Actuator
//
@ConditionalOnClass(EndpointRequest.class)
@Configuration
@Order(5) // Actuator configuration should kick in before the Form Login there should always be http basic for the endpoints
public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable();
http
.requestMatcher(new ActuatorRequestMatcher())
.authorizeRequests()
.requestMatchers(EndpointRequest.to(InfoEndpoint.class, HealthEndpoint.class)).authenticated()
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasAnyAuthority(DefaultPrivileges.ACCESS_ADMIN)
.and().httpBasic();
}
}
}
3.6. 启动类配置
修改原有启动类:包名com.xtsz.modeler
package com.xtsz.modeler;
import org.flowable.ui.modeler.conf.ApplicationConfiguration;
import org.flowable.ui.modeler.servlet.AppDispatcherServletConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
/**
* 导入配置
*/
@Import({
ApplicationConfiguration.class,
AppDispatcherServletConfiguration.class
})
@SpringBootApplication
public class ModelerApplication {
public static void main(String[] args) {
SpringApplication.run(ModelerApplication.class, args);
}
}
3.7. 流程模型汉化
拷贝文件到resources目录:
源汉化文件
目标汉化文件
4. 启动测试
- 请求地址:http://localhost:8002/flowable-modeler/
模型流程 - 创建流程
创建流程
创建流程
设计流程
5. 常见问题
- Cannot convert value ‘2019-10-22 08:09:24.000000’ from column 6 to TIMESTAMP
原因:MySql数据库必须使用8.0.0+。
依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
数据库配置:
# 数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/workflow_flowable?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2b8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=1234
七、Flowable开发—核心数据库表
1. 数据模型设计
- 清单 | 数据表分类 | 描述 | | :—- | :—- | | ACTGE | 通用数据表 | | ACTRE | 流程定义存储表 | | ACTID | 身份信息表 | | ACTRU | 运行时数据库表 | | ACTHI* | 历史数据库表 |
- 通用数据库 | 数据表 | 描述 | | :—- | :—- | | ACT_GE_PROPERTY | 属性表(保存流程引擎的kv键值属性)—PropertyEntityImpl | | ACT_GE_BTYEARRAY | 资源表(存储流程定义相关的资源)—ByteArrayEntityImpl |
- 流程定义存储表 | 数据表 | 描述 | | :—- | :—- | | ACT_RE_DEPLOYMENT | 流程部署表—DeploymentEntityImpl | | ACT_RE_PROCDEF | 流程定义信息表—ProcessDefinitionEntityImpl | | ACT_RE_MODEL | 模型信息表(用于Web设计器)—ModelEntityImpl | | ACT_PROCDEF_INFO | 流程定义动态改变信息表—ProcessDefinitionInfoEntityImpl |
- 身份数据表 | 数据表 | 描述 | | :—- | :—- | | ACT_ID_USER | 用户基本信息表—UserEntityImpl | | ACT_ID_INFO | 用户扩展表—IdentityInfoEntityImpl | | ACT_ID_GROUP | 群组表(用于Web设计器)—GroupEntityImpl | | ACT_ID_MEMBERSHIP | 户与群主关系表—MemberShipEntityImpl | | ACT_ID_BYTEARRAY | 二进制数据表(flowable)— | | ACT_ID_PRIV | 权限表(flowable)— | | ACT_ID_PRIV_MAPPING | 用户或组权限关系表(flowable)— | | ACT_ID_PROPERTY | 属性表(flowable)— | | ACT_ID_TOKEN | 系统登录日志表(flowable)— |
- 运行时流程数据表 | 数据表 | 描述 | | :—- | :—- | | ACT_RU_EXECUTION | 流程实例与分支执行表—ExecutionEntityImpl | | ACT_RU_TASK | 用户任务表—TaskEntityImpl | | ACT_RU_VARIABLE | 变量信息—VariableInstanceEntityImpl | | ACT_RU_IDENTITYLINK | 参与者相关信息表—IdentityLinkEntityImpl | | ACT_RU_EVENT_SUBSCR | 事件订阅表—EventSubscriptionEntityImpl | | ACT_RU_JOB | 作业表—JobEntityImpl | | ACT_RU_TIMER_JOB | 定时器表—TimerJobEntityImpl | | ACT_RU_SUSPENDED_JOB | 暂停作业表—SuspendedJobEntityImpl | | ACT_RU_DEADLETTER_JOB | 死信表—DeadLetterJobEntityImpl | | ACT_RU_HISTORY_JOB | 历史作业表(flowable)— |
- 历史流程数据表 | 数据表 | 描述 | | :—- | :—- | | ACT_HI_PROCINST | 历史流程实例表—HistoricProcessInstanceEntityImpl | | ACT_HI_ACTINST | 历史节点信息表—HistoricActivityInstanceEntityImpl | | ACT_HI_TASKINST | 历史任务表—HistoricTaskInstanceEntityImpl | | ACT_HI_VARINST | 历史变量—HistoricVariableInstanceEntityImpl | | ACT_HI_IDENTITYLINK | 历史参与者表—HistoricIdentityLinkEntityImpl | | ACT_HI_DETAIL | 历史的流程运行中的细节信息—HistoricDetailEntityImpl | | ACT_HI_ATTACHMENT | 附件表—AttachmentEntityImpl | | ACT_HI_COMMENT | 评论表—CommentEntityImpl | | ACT_EVT_LOG | 事件日志表—EventLogEntryEntityImpl |
2. 表结构
- 通用类表
act_ge_property(全局配置文件)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
NAME_ | 名称 | NO | varchar | 64 | PRI | schema.version schema.history next.dbid | |
VALUE_ | 值 | NULL | YES | varchar | 300 | 5. create(5.) | |
REV_ | 版本号 | NULL | YES | int | NULL | version |
注:
1.全局参数, 默认三个参数next.dbid, IdGenerator区间, schema.history, 自动执行sql历史, schema.version, 当
前sql版本。
2.属性数据表。存储整个流程引擎级别的数据。
act_ge_bytearray(二进制文件)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
NAME_ | 名称 | NULL | YES | varchar | 255 | 部署的文件名称,如:mail.bpmn、mail.png 、mail.bpmn20.xml | |
DEPLOYMENTID | 部署ID | NULL | YES | varchar | 64 | ACT_RE_DEPLOYMENT | |
BYTES_ | 字节(二进制数据) | NULL | YES | longblob | 4294967295 | ||
GENERATED_ | 是否系统生成 | NULL | YES | tinyint | NULL | 0为用户上传,1为系统自动生成, 比如系统会自动根据xml生成png |
注:
1.用来保存部署文件的大文本数据
2.所有二进制内容都会保存在这个表里, 比如部署的process.bpmn20.xml, process.png, user.form, 附件, bean序列
化为二进制的流程变量。
act_ge_property属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录。
- 历史类表
act_hi_actinst(历史节点表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
PROCDEF_ID | 流程定义ID | NULL | NO | varchar | 64 | ||
PROCINST_ID | 流程实例ID | NULL | NO | varchar | 64 | MUL | |
ACTID | 节点ID | NULL | NO | varchar | 255 | ||
TASKID | 任务ID | NULL | YES | varchar | 64 | 任务实例ID 其他节点类型实例ID在这里为空 | |
CALLPROC_INST_ID | 调用外部的流程实例ID | NULL | YES | varchar | 64 | ||
ACTNAME | 节点名称 | NULL | YES | varchar | 255 | ||
ACTTYPE | 节点类型 | NULL | NO | varchar | 255 | 如startEvent、userTask | |
ASSIGNEE_ | 签收人 | NULL | YES | varchar | 255 | 经办人 | |
STARTTIME | 开始时间 | NULL | NO | datetime | NULL | MUL | |
ENDTIME | 结束时间 | NULL | YES | datetime | NULL | MUL | |
DURATION_ | 耗时 | NULL | YES | bigint | NULL | 毫秒值 | |
TENANTID | 多租户 | YES | varchar | 255 |
注:
1. 历史活动信息。这里记录流程流转过的所有节点,与HI_TASKINST不同的是,taskinst只记录usertask内容。
2. TENANT_ID 是后续才加入的多租户
act_hi_attachment(历史附件表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键ID | NULL | NO | varchar | 64 | PRI | |
REV_ | 版本号 | NULL | YES | int | NULL | version | |
USERID | 用户ID | NULL | YES | varchar | 255 | ||
NAME_ | 名称 | NULL | YES | varchar | 255 | ||
DESCRIPTION_ | 描述 | NULL | YES | varchar | 4000 | ||
TYPE_ | 类型 | NULL | YES | varchar | 255 | ||
TASKID | 任务ID | NULL | YES | varchar | 64 | ||
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | ||
URL_ | 附件地址 | NULL | YES | varchar | 4000 | 附件的URL地址 | |
CONTENTID | 字节表ID | NULL | YES | varchar | 64 | ACT_GE_BYTEARRAY的ID | |
TIME_ | 时间 | NULL | YES | datetime | NULL |
注:
1.存放历史流程相关的附件。
2.时间是后续版本加入
act_hi_comment(历史审批意见表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
TYPE_ | 类型 | NULL | YES | varchar | 255 | 类型:event(事件) comment(意见) | |
TIME_ | 时间 | NULL | NO | datetime | NULL | ||
USERID | 用户ID | NULL | YES | varchar | 255 | ||
TASKID | 任务ID | NULL | YES | varchar | 64 | ||
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | ||
ACTION_ | 行为类型 | NULL | YES | varchar | 255 | ||
MESSAGE_ | 基本内容 | NULL | YES | varchar | 4000 | 用于存放流程产生的信息,比如审批意见 | |
FULLMSG | 全部内容 | NULL | YES | longblob | 4294967295 | 附件 |
注:
- 存放历史流程的审批意见。
- 行为类型。值为下列内容中的一种:AddUserLink、DeleteUserLink、AddGroupLink、DeleteGroupLink、AddComment、AddAttachment、DeleteAttachment
act_hi_detail(历史详情信息表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
TYPE_ | 类型 | NULL | NO | varchar | 255 | 类型: FormProperty, //表单 VariableUpdate //参数 | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL | |
EXECUTIONID | 执行实例 | NULL | YES | varchar | 64 | ||
TASKID | 任务ID | NULL | YES | varchar | 64 | MUL | |
ACTINST_ID | 节点实例ID | NULL | YES | varchar | 64 | ACT_HI_ACTINST | |
NAME_ | 名称 | NULL | NO | varchar | 255 | MUL | |
VARTYPE | 参数类型 | NULL | YES | varchar | 255 | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
TIME_ | 时间戳 | NULL | NO | datetime | NULL | MUL | 创建时间 |
BYTEARRAYID | 字节表ID | NULL | YES | varchar | 64 | ACT_GE_BYTEARRAY | |
DOUBLE_ | 浮点值 | NULL | YES | double | NULL | 存储变量类型为Double | |
LONG_ | 长整型 | NULL | YES | bigint | NULL | 存储变量类型为long | |
TEXT_ | 文本值 | NULL | YES | varchar | 4000 | 存储变量值类型为String | |
TEXT2_ | 字符串 | NULL | YES | varchar | 4000 | 此处存储的是JPA持久化对象时,才会有值。此值为对象ID,jpa变量text存className,text2存id |
注:
1.历史详情表:流程中产生的变量详细,包括控制流程流转的变量,业务表单中填写的流程需要用到的变量等。
2.参数类型: jpa-entity、boolean、bytes、serializable(可序列化)、自定义type(根据你自身配置)、CustomVariableType、date、double、integer、long、null、short、string
act_hi_identitylink(历史流程人员表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
NO | varchar | 64 | PRI | ||||
GROUPID | 用户组ID | NULL | YES | varchar | 255 | ||
TYPE_ | 类型 | NULL | YES | varchar | 255 | 类型,主要分为以下几种:assignee、candidate、owner、starter 、participant | |
USERID | 用户ID | NULL | YES | varchar | 255 | MUL | |
TASKID | 任务ID | NULL | YES | varchar | 64 | MUL | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL |
注:
- 任务参与者数据表。主要存储当前节点参与者的信息。
act_hi_procinst(流程实例历史*核心表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
PROCINST_ID | 流程实例ID | NULL | NO | varchar | 64 | UNI | |
BUSINESSKEY | 业务标识 | NULL | YES | varchar | 255 | MUL | 业务主键,业务表单的ID |
PROCDEF_ID | 流程实例ID | NULL | NO | varchar | 64 | ||
STARTTIME | 开始时间 | NULL | NO | datetime | NULL | ||
ENDTIME | 结束时间 | NULL | YES | datetime | NULL | MUL | |
DURATION_ | 耗时 | NULL | YES | bigint | NULL | ||
STARTUSER_ID | 流程发起人ID | NULL | YES | varchar | 255 | ||
STARTACT_ID | 开始节点ID | NULL | YES | varchar | 255 | ||
ENDACT_ID | 结束节点ID | NULL | YES | varchar | 255 | ||
SUPERPROCESS_INSTANCE_ID | 父流程实例ID | NULL | YES | varchar | 64 | ||
DELETEREASON | 删除原因 | NULL | YES | varchar | 4000 | ||
TENANTID | 租户ID | YES | varchar | 255 | |||
NAME_ | 名称 | NULL | YES | varchar | 255 |
注:
1.核心表之一。
2.存放历史的流程实例。
3.设计历史流程实例表的初衷之一就是为了使得运行时库数据量尽可能小,效率最优。
act_hi_taskinst(历史任务流程实例信息*核心表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
PROCDEF_ID | 流程实例ID | NULL | YES | varchar | 64 | ||
TASKDEF_KEY | 任务节点定义ID | NULL | YES | varchar | 255 | 任务定义标识(环节ID) | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL | |
EXECUTIONID | 执行实例ID | NULL | YES | varchar | 64 | ||
NAME_ | 任务名称 | NULL | YES | varchar | 255 | ||
PARENTTASK_ID | 父任务节点ID | NULL | YES | varchar | 64 | ||
DESCRIPTION_ | 描述 | NULL | YES | varchar | 4000 | ||
OWNER_ | 被代理人 | NULL | YES | varchar | 255 | 委托人(默认为空,只有在委托时才有值) | |
ASSIGNEE_ | 经办人 | NULL | YES | varchar | 255 | ||
STARTTIME | 开始时间 | NULL | NO | datetime | NULL | ||
CLAIMTIME | 签收时间 | NULL | YES | datetime | NULL | ||
ENDTIME | 结束时间 | NULL | YES | datetime | NULL | ||
DURATION_ | 耗时 | NULL | YES | bigint | NULL | ||
DELETEREASON | 删除原因 | NULL | YES | varchar | 4000 | 删除原因(completed,deleted) | |
PRIORITY_ | 优先级 | NULL | YES | int | NULL | ||
DUEDATE | 截止时间 | NULL | YES | datetime | NULL | 过期时间,表明任务应在多长时间内完成 | |
FORMKEY | FORM表单的KEY | NULL | YES | varchar | 255 | desinger节点定义的 form_key属性 | |
CATEGORY_ | 分类 | NULL | YES | varchar | 255 | ||
TENANTID | 租户ID | YES | varchar | 255 |
注:
1. 历史任务实例表。
2. 存放已经办理的任务。
3. CATEGORY和TNANT_ID是后续版本才加进来的。
act_hi_varinst(历史变量表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL | |
EXECUTIONID | 执行实例ID | NULL | YES | varchar | 64 | ||
TASKID | 任务ID | NULL | YES | varchar | 64 | MUL | |
NAME_ | 名称 | NULL | NO | varchar | 255 | MUL | |
VARTYPE | 变量类型 | NULL | YES | varchar | 100 | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
BYTEARRAYID | 字节流ID | NULL | YES | varchar | 64 | ACT_GE_BYTEARRAY | |
DOUBLE_ | 浮点值 | NULL | YES | double | NULL | 存储DoubleType类型的数据 | |
LONG_ | 长整型 | NULL | YES | bigint | NULL | 存储LongType类型的数据 | |
TEXT_ | 文本值 | NULL | YES | varchar | 4000 | 存储变量值类型为String,如此处存储持久化对象时,值jpa对象的class | |
TEXT2_ | 文本值 | NULL | YES | varchar | 4000 | ||
CREATETIME | 创建时间 | NULL | YES | datetime | NULL | ||
LASTUPDATED_TIME | 最后更新时间 | NULL | YES | datetime | NULL |
注:
- 主要存放历史变量数据。
act_evt_log(事件日志)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
LOGNR | 主键 | NULL | NO | bigint | NULL | PRI | |
TYPE_ | 类型 | NULL | YES | varchar | 64 | ||
PROCDEF_ID | 流程定义ID | NULL | YES | varchar | 64 | ||
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | ||
EXECUTIONID | 执行ID | NULL | YES | varchar | 64 | ||
TASKID | 任务ID | NULL | YES | varchar | 64 | ||
TIMESTAMP | 时间 | CURRENT_TIMESTAMP(3) | NO | timestamp | NULL | ||
USERID | 用户ID | NULL | YES | varchar | 255 | ||
DATA_ | 数据 | NULL | YES | longblob | 4294967295 | ||
LOCKOWNER | 锁定节点 | NULL | YES | varchar | 255 | ||
LOCKTIME | 锁定时间 | NULL | YES | timestamp | NULL | ||
ISPROCESSED | 是否正在执行 | 0 | YES | tinyint | NULL |
注:
1.事件日志表
2.事件日志, 默认不开启。
3.从Activiti 5.16开始,引入了(试验性)的事件记录机制。记录机制基于Activiti引擎的事件机制的一般用途,并默认禁用。其思想是,来源于引擎的事件会被捕获,并创建一个包含了所有事件数据(甚至更多)的映射,提供给
org.activiti.engine.impl.event.logger.EventFlusher,由它将这些数据刷入其他地方。默认情况下,使用简单的基于数据库的事件处理/刷入,会使用Jackson将上述映射序列化为JSON,并将其作为EventLogEntryEntity接口存入数据库。如果不使用事件记录,可以删除这个表。
4.配置启用事件日志:
processEngineConfiguration.setEnableDatabaseEventLogging(true);
5.运行时启用事件日志:
databaseEventLogger = new EventLogger(processEngineConfiguration.getClock());
runtimeService.addEventListener(databaseEventLogger);
6.可以扩展EventLogger类。如果默认的数据库记录不符合要求,需要覆盖createEventFlusher()方法返回一个org.activiti.engine.impl.event.logger.EventFlusher接口的实例。可以通过Activiti的
managementService.getEventLogEntries(startLogNr, size)?获取EventLogEntryEntity实例。
容易看出这个表中的数据可以通过JSON放入大数据NoSQL存储,例如MongoDB,Elastic Search,等等。
也容易看出这里使用的类
(org.activiti.engine.impl.event.logger.EventLogger/EventFlusher与许多其他 EventHandler类)是可插入的,可以按你的使用场景调整(例如不将JSON存入数据库,而是将其直接发送给一个队列或大数据存储)。
请注意这个事件记录机制是额外于Activiti的“传统”历史管理器的。尽管所有数据都在数据库表中,但并未对查询或快速恢复做优化。实际使用场景是末端审计并将其存入大数据存储。
用户身份类
act_id_group(用户组)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
NAME_ | 名称 | NULL | YES | varchar | 255 | ||
TYPE_ | 类型 | NULL | YES | varchar | 255 |
注:
1.Activiti自带的用户组表,用于组任务。
act_id_info(用户扩展信息表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
USERID | 用户ID | NULL | YES | varchar | 64 | ||
TYPE_ | 类型 | NULL | YES | varchar | 64 | ||
KEY_ | 属性名 | NULL | YES | varchar | 255 | ||
VALUE_ | 属性值 | NULL | YES | varchar | 255 | ||
PASSWORD_ | 密码 | NULL | YES | longblob | 4294967295 | ||
PARENTID | 父级ID | NULL | YES | varchar | 255 |
注:
act_id_membership( 用户与分组对应信息表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
USERID | 用户ID | NO | varchar | 64 | PRI(ACT_ID_USER) | ||
GROUPID | 用户组ID | NO | varchar | 64 | PRI(ACT_ID_GROUP) |
注:
1.用来保存用户的分组信息。
act_id_user(用户信息表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
FIRST_ | 姓 | NULL | YES | varchar | 255 | FIRST_NAME | |
LAST_ | 名 | NULL | YES | varchar | 255 | LAST_NAME | |
EMAIL_ | 邮箱 | NULL | YES | varchar | 255 | ||
PWD_ | 密码 | NULL | YES | varchar | 255 | ||
PICTUREID | 头像ID | NULL | YES | varchar | 64 | ACT_GE_BYTEARRAY |
注:
1.Activiti用户信息表。
流程定义存储表
act_procdef_info(流程定义更新信息)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
PROCDEF_ID | 流程定义ID | NULL | NO | varchar | 64 | UNI(ACT_RE_PROCDEF) | |
REV_ | 版本号 | NULL | YES | int | NULL | version | |
INFOJSON_ID | 内容 | NULL | YES | varchar | 64 | MUL(ACT_GE_BYTEARRAY) |
注:
1.流程版本升级的数据。
act_re_deployment( 部署信息表*核心表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
NAME_ | 名称 | NULL | YES | varchar | 255 | ||
CATEGORY_ | 分类 | NULL | YES | varchar | 255 | ||
TENANTID | 租户ID | YES | varchar | 255 | |||
DEPLOYTIME | 部署时间 | NULL | YES | timestamp | NULL |
注:
1. 部署流程定义时需要被持久化保存下来的信息。
act_re_model( 流程设计模型部署表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
REV_ | 版本号 | NULL | YES | int | NULL | version | |
NAME_ | 名称 | NULL | YES | varchar | 255 | ||
KEY_ | 标识 | NULL | YES | varchar | 255 | ||
CATEGORY_ | 分类 | NULL | YES | varchar | 255 | ||
CREATETIME | 创建时间 | NULL | YES | imestamp | NULL | ||
LASTUPDATE_TIME | 最后更新时间 | NULL | YES | timestamp | NULL | ||
VERSION_ | 版本 | NULL | YES | int | NULL | ||
METAINFO | 元数据 | NULL | YES | varchar | 4000 | 以json格式保存流程定义的信息 | |
DEPLOYMENTID | 部署ID | NULL | YES | varchar | 64 | MUL(ACT_RE_DEPLOYMENT) | |
EDITORSOURCE_VALUE_ID | 二进制文件ID | NULL | YES | varchar | 64 | MUL(ACT_GE_BYTEARRAY) | 设计器原始信息 |
EDITORSOURCE_EXTRA_VALUE_ID | 二进制文件ID | NULL | YES | varchar | 64 | MUL(ACT_GE_BYTEARRAY) | 设计器扩展信息 |
TENANTID | 租户ID | YES | varchar | 255 |
注:
1.该表是流程设计器设计流程模型保存的数据。
act_re_procdef(流程定义数据表*核心表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
REV_ | 版本号 | NULL | YES | int | NULL | version | |
CATEGORY_ | 分类 | NULL | YES | varchar | 255 | 流程定义的Namespace就是类别 | |
NAME_ | 名称 | NULL | YES | varchar | 255 | ||
KEY_ | 标识 | NULL | NO | varchar | 255 | MUL | |
VERSION_ | 版本 | NULL | NO | int | NULL | ||
DEPLOYMENTID | 部署ID | NULL | YES | varchar | 64 | ||
RESOURCENAME | 资源名称 | NULL | YES | varchar | 4000 | 流程bpmn文件名称 | |
DGRMRESOURCE_NAME | 图片资源名称 | NULL | YES | varchar | 4000 | ||
DESCRIPTION_ | 描述 | NULL | YES | varchar | 4000 | ||
HASSTART_FORM_KEY | 拥有开始表单标识 | NULL | YES | tinyint | NULL | start节点是否存在formKey 0否 1是 | |
HASGRAPHICAL_NOTATION | 拥有图形信息 | NULL | YES | tinyint | NULL | ||
SUSPENSIONSTATE | 挂起状态 | NULL | YES | int | NULL | 暂停状态 1激活 2暂停 | |
TENANTID | 租户ID | YES | varchar | 255 |
注:
1. 业务流程定义数据表。此表和ACTRE_DEPLOYMENT是多对一的关系,即,一个部署的bar包里可能包含多个流程定义文件,每个流程定义文件都会有一条记录在ACT_REPROCDEF表内,每个流程定义的数据,都会对于ACT_GE_BYTEARRAY表内的一个资源文件和PNG图片文件。和ACT_GE_BYTEARRAY的关联是通过程序用ACT_GE_BYTEARRAY.NAME与ACT_RE_PROCDEF.NAME完成的,在数据库表结构中没有体现。
运行时流程数据表
act_ru_event_subscr(事件订阅)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
REV_ | 版本号 | NULL | YES | int | NULL | varsion | |
EVENTTYPE | 事件类型 | NULL | NO | varchar | 255 | ||
EVENTNAME | 事件名称 | NULL | YES | varchar | 255 | ||
EXECUTIONID | 执行实例ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | ||
ACTIVITYID | 节点ID | NULL | YES | varchar | 64 | ||
CONFIGURATION_ | 配置 | NULL | YES | varchar | 255 | MUL | |
CREATED_ | 创建时间 | CURRENT_TIMESTAMP(3) | NO | timestamp | NULL | ||
PROCDEF_ID | 流程定义ID | NULL | YES | varchar | 64 | ||
TENANTID | 租户ID | YES | varchar | 255 |
注:
1.该表是后续版本加进来的。
act_ru_execution(运行时流程执行实例表*核心表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
REV_ | 版本号 | NULL | YES | int | NULL | ||
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
BUSINESSKEY | 业务标识 | NULL | YES | varchar | 255 | MUL | |
PARENTID | 父级ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
PROCDEF_ID | 流程定义ID | NULL | YES | varchar | 64 | MUL(ACT_RE_PROCDEF) | |
SUPEREXEC | 父流程实例中对应的执行 | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
ACTID | 节点ID | NULL | YES | varchar | 255 | ||
ISACTIVE | 是否激活 | NULL | YES | tinyint | NULL | ||
ISCONCURRENT | 是否分支(并行) | NULL | YES | tinyint | NULL | 是否为并行(true/false) | |
ISSCOPE | 是否处于多实例或环 节嵌套状态 | NULL | YES | tinyint | NULL | ||
ISEVENT_SCOPE | 是否激活状态 | NULL | YES | tinyint | NULL | ||
SUSPENSIONSTATE | 挂起状态 | NULL | YES | int | NULL | 暂停状态 1激活 2暂停 | |
CACHEDENT_STATE | 缓存状态 | NULL | YES | int | NULL | 缓存的状态, 1 事件 监听 2 人工任务 3 异步作业 | |
TENANTID | 租户ID | YES | varchar | 255 | |||
NAME_ | 名称 | NULL | YES | varchar | 255 | ||
LOCKTIME | 锁定时间 | NULL | YES | timestamp | NULL |
注:
1.TENANT_ID、NAME、LOCK_TIME是后续版本加入的。
act_ru_identitylink( 运行时流程人员表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
GROUPID | 用户组ID | NULL | YES | varchar | 255 | MUL | |
TYPE_ | 类型 | NULL | YES | varchar | 255 | ||
USERID | 用户ID | NULL | YES | varchar | 255 | MUL | |
TASKID | 任务ID | NULL | YES | varchar | 64 | MUL(ACT_RU_TASK) | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
PROCDEF_ID | 流程定义ID | NULL | YES | varchar | 64 | MUL(ACT_RE_PROCDEF) |
注:
1.任务参与者数据表。主要存储当前节点参与者的信息。
act_ru_job(运行时定时任务数据表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
REV_ | 版本号 | NULL | YES | int | NULL | ||
TYPE_ | 类型 | NULL | NO | varchar | 255 | ||
LOCKEXP_TIME | 锁定过期时间 | NULL | YES | timestamp | NULL | ||
LOCKOWNER | 挂起者 | NULL | YES | varchar | 255 | ||
EXCLUSIVE_ | 是否唯一 | NULL | YES | tinyint | NULL | ||
EXECUTIONID | 执行实例ID | NULL | YES | varchar | 64 | ||
PROCESSINSTANCE_ID | 流程实例ID | NULL | YES | varchar | 64 | ||
PROCDEF_ID | 流程定义ID | NULL | YES | varchar | 64 | ||
RETRIES_ | 重试次数 | NULL | YES | int | NULL | ||
EXCEPTIONSTACK_ID | 异常堆栈 | NULL | YES | varchar | 64 | MUL(ACT_GE_BYTEARRAY) | |
EXCEPTIONMSG | 异常信息 | NULL | YES | varchar | 4000 | ||
DUEDATE_ | 截止时间 | NULL | YES | timestamp | NULL | ||
REPEAT_ | 重复 | NULL | YES | varchar | 255 | ||
HANDLERTYPE | 处理器类型 | NULL | YES | varchar | 255 | ||
HANDLERCFG | 处理器配置 | NULL | YES | varchar | 4000 | ||
TENANTID | 租户ID | YES | varchar | 255 |
注:
1.作业执行器数据。
2.需要启用JOB组件:JobExecutor 是管理一组线程的组件,这些线程用于触发定时器(包括后续的异步消息)。在单元测试场景下,使用多线程会很笨重。
因此API提供 ManagementService.createJobQuery 用于查询,以及 ManagementService.executeJob 用于执行作业。这样作业的执
行就可以在单元测试内部控制。为了避免作业执行器的干扰,可以将它关闭。
默认情况下, JobExecutor 在流程引擎启动时激活。当你不希望 JobExecutor 随流程引擎启动时,设置:
3.11. 启用异步执行器 Async executor activation
AsyncExecutor 是管理线程池的组件,这个线程池用于触发定时器与异步任务。
默认情况下,由于历史原因,当使用 JobExecutor 时, AsyncExecutor 不生效。然而我们建议使用新的 AsyncExecutor 代替
JobExecutor ,通过定义两个参数实现
asyncExecutorEnabled参数用于启用异步执行器,代替老的作业执行器。 第二个参数asyncExecutorActivate命令Activiti引擎在启动时
启动异步执行器线程池。
act_ru_task( 运行时任务节点表*核心表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NO | varchar | 64 | PRI | ||
REV_ | 版本号 | NULL | YES | int | NULL | version | |
EXECUTIONID | 执行实例ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
PROCDEF_ID | 流程定义ID | NULL | YES | varchar | 64 | MUL(ACT_RE_PROCDEF) | |
NAME_ | 名称 | NULL | YES | varchar | 255 | ||
PARENTTASK_ID | 父任务ID | NULL | YES | varchar | 64 | ||
DESCRIPTION_ | 描述 | NULL | YES | varchar | 4000 | ||
TASKDEF_KEY | 人物定义标识 | NULL | YES | varchar | 255 | ||
OWNER_ | 被代理人 | NULL | YES | varchar | 255 | (一般情况下为空,只有在委托时才有值) | |
ASSIGNEE_ | 经办人 | NULL | YES | varchar | 255 | 签收人或者委托人 | |
DELEGATION_ | 委托状态 | NULL | YES | varchar | 64 | 委托状态 PENDING委托中,RESOLVED已处理 | |
PRIORITY_ | 优先级 | NULL | YES | int | NULL | ||
CREATETIME | 创建时间 | NULL | YES | timestamp | NULL | MUL | |
DUEDATE | 截止时间 | NULL | YES | datetime | NULL | ||
CATEGORY_ | 分类 | NULL | YES | varchar | 255 | ||
SUSPENSIONSTATE | 挂起状态 | NULL | YES | int | NULL | 暂停状态 1激活 2暂停 | |
TENANTID | 租户ID | YES | varchar | 255 | |||
FORMKEY | 表单标识 | NULL | YES | varchar | 255 |
注:
1.运行时任务数据表
act_ru_variable( 运行时流程变量数据表*核心表)
字段 | 字段名称 | 字段默认值 | 是否允许为空 | 数据类型 | 字段长度 | 键 | 备注 |
---|---|---|---|---|---|---|---|
ID_ | 主键 | NULL | NO | varchar | 64 | PRI | |
REV_ | 版本号 | NULL | YES | int | NULL | version | |
TYPE_ | 类型 | NULL | NO | varchar | 255 | 见备注 | |
NAME_ | 名称 | NULL | NO | varchar | 255 | ||
EXECUTIONID | 执行实例ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
PROCINST_ID | 流程实例ID | NULL | YES | varchar | 64 | MUL(ACT_RU_EXECUTION) | |
TASKID | 任务ID | NULL | YES | varchar | 64 | MUL(ACT_RU_TASK) | |
BYTEARRAYID | 资源ID | NULL | YES | varchar | 64 | MUL(ACT_GE_BYTEARRAY) | |
DOUBLE_ | 浮点值 | NULL | YES | double | NULL | 存储变量类型为Double | |
LONG_ | 长整型 | NULL | YES | bigint | NULL | 存储变量类型为long | |
TEXT_ | 文本值 | NULL | YES | varchar | 4000 | 存储变量值类型为String 如此处存储持久化对象时,值jpa对象的class | |
TEXT2_ | 文本值 | NULL | YES | varchar | 4000 | 此处存储的是JPA持久化对象时,才会有值。此值为对象ID |
注:
1.运行时流程变量数据表。
2.类型:jpa-entity、boolean、bytes、serializable(可序列化)、自定义type(根据你自身配置)、
CustomVariableType、date、double、integer、long、null、short、string