Activiti
Activiti项目是一项新的基于Apache许可的开源BPM平台,从基础开始构建,旨在提供支持新的BPMN 2.0标准,包括支持对象管理组(OMG),面对新技术的机遇,诸如互操作性和云架构,提供技术实现。 创始人Tom Baeyens是JBoss jBPM的项目架构师,以及另一位架构师Joram Barrez,一起加入到创建Alfresco这项首次实现Apache开源许可的BPMN 2.0引擎开发中来。 Activiti is a light-weight workflow and Business Process Management (BPM) Platform targeted at business people, developers and system admins. Its core is a super-fast and rock-solid BPMN 2 process engine for Java. It's open-source and distributed under the Apache license. Activiti runs in any Java application, on a server, on a cluster or in the cloud. It integrates perfectly with Spring, it is extremely lightweight and based on simple concepts. Homepage: http://activiti.org/ https://github.com/Activiti/Activiti
- Activiti 基础知识
- 什么是 Activiti7
- Activiti 框架原理
- Activiti 核心 API
- Activiti 用户手册
- Activiti 数据库表结构
- Activiti 依赖事务监听器(上)
- Activiti 依赖事务监听器(下)
- Activiti6 特性
- Activiti 6.0 工作流入门
- Activiti 6.0 源码分析 helloword
- Activiti 框架原理
- Activiti 学习笔记
- 使用IDEA开发activiti的配置
- AgileBPM 敏捷工作流开发平台
- Actviti7 数据库环境
- Activiti 流程定义及部署测试
- Activiti 启动流程实例及分析
- Activiti 流程任务的查询 处理及分析
- Activiti 进阶学习——网关
- Activiti 转载文章
- Activiti7的核心详解
- Liferay-Activiti 功能介绍 (新版Liferay7基本特性)
- Liferay-Activiti 企业特性功能介绍(新版Liferay7)
- 比较Activiti中三种不同的表单及其应用
- [Activiti 6.x] Springboot 1.5x MySQL
- [Activiti 6.x] 基本流程讲解与开发前奏
- [Activiti 6.x] 核心API基础
- [Activiti 6.x] 基础流程DEMO
- [Activiti 6.x] 剩下的核心API
- Flowable 学习章节
Activiti 基础知识
1、基本概念 工作流:业务过程的部分或整体在计算机应用环境下的自动化 工作流管理系统:工作流的定义和管理,按照在系统中预定义好的工作流规则进行工作流实例的执行。 工作流管理系统的目标:管理工作流程以确保工作在正确的时间被期望的人员执行–在自动化进行的业务过程中插入人工的执行和干预。 Activiy是什么 Activiti是一个针对企业用户、开发人员、系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快速、稳定的BPMN2.0流程引擎。 Activiti的特点 数据持久化,底层使用MyBatis 引擎Service接口 流程设计器 原生支持Spring 分离运行时与历史数据
什么是 Activiti7
Actviti7 简介
简介
Activiti项目是一项新的基于Apache许可的开源BPM平台,从基础开始构建,旨在提供支持新的BPMN 2.0标准,包括支持对象管理组(OMG),面对新技术的机遇,诸如互操作性和云架构,提供技术实现。
创始人Tom Baeyens是JBoss jBPM的项目架构师,以及另一位架构师Joram Barrez,一起加入到创建Alfresco这项首次实现Apache开源许可的BPMN 2.0引擎开发中来。
Activiti是一个独立运作和经营的开源项目品牌,并将独立于Alfresco开源ECM系统运行。 Activiti将是一种轻量级,可嵌入的BPM引擎,而且还设计适用于可扩展的云架构。 Activiti将提供宽松的Apache许可2.0,以便这个项目可以广泛被使用,同时促进Activiti BPM引擎和BPMN 2.0的匹配,该项目现正由OMG通过标准审定。 加入Alfresco Activiti项目的是VMware的SpringSource分支,Alfresco的计划把该项目提交给Apache基础架构,希望吸引更多方面的BPM专家和促进BPM的创新。
特色
架构师Tom Baeyens说:“ Activiti有非常大的影响力来改变目前BPM的生态。Activiti的Apache授权,完整的功能,将使Activiti到达一个新的水平。Activiti将推动业界的创新,因为BPM技术可以广泛而自由地被应用。通过实现这些想法以及开源社区的努力,也让Activiti成为事实上的 BPM和BPMN标准执行“。
SpringSource的首席技术官Adrian Coyler说道:”这是一个对Spring开发人员和Java社区总体的发展非常令人兴奋的事情,长期以来一直需要一个Apache许可的流程引擎,这对许多应用系统非常实用的需求。我们认为,Activiti作为新的应用领域扩展到的Java和开源的发展,特别是在云架构上”。
Alfresco软件公司的首席技术官John Newton表示"我们发起这个项目,使内容和过程技术的使用可以更广泛和普及,我们这样做是因为,像其他开源项目,我们需要一个更宽松授权的流程引擎。我们相信,这可以改变业务流程处理领域,就像Alfresco公司已经为企业内容管理ECM领域所做的那样。”
Activiti将成为Alfresco的默认的业务流程引擎,Alfresco公司将继续支持jBPM,以及目前与其他业务流程的企业内容管理软件集成的引擎。 Alfresco公司也将与Alfresco企业版一起,提供对Activiti的支持,维护和技术保证。
1.工作流:工作的一个流程,事物发展的一个业务过程
流程: 请假流程:员工申请----部门经理-----总经理-----人事存档 传统方式下? 请假条的传递来实现 无纸化办公? 线上申请----线上审批----一条请假记录
在计算机的帮助下,能够实现流程的自动化控制,就称为工作流.
2.工作流引擎
为了实现自动化控制,Acitviti引擎就产生了。
作用:实现流程自动化控制
3.工作流系统:具有工作流的系统。
如果一个系统具备流程的自动化管理功能,这个系统就可以称为工作流系统
工作流系统,有哪些手段可以实现?
工作流系统,如何来实现流程的自动化管理? 流程自动化管理:程序员编码来实现 请假:员工申请----部门经理-----总经理-----人事存档
1,工号,部门号,姓名,日期,天数,原因,状态3
员工:0未提交1提交
部门经理:部门号=部门经理的部门编号相同,状态=1
2同意 3不同意
总经理 状态=2
4同意 5不同意
人事存档 状态=4
6同意 7不同意
问题:业务流程变更后,程序不能使用
以不变应万变
如何解决,以不变应万变?
-----Activiti就可以实现业务流程变化后,程序代码不需要改动。
使用场景,及相关业务.
SaaS-人力资源管理系统 行政审批(调薪)
为什么Activiti就可以解决业务需求变更时,源代码不需要更新,更新的是业务流程图?
原理?
图片附件
Activiti 框架原理
本文基于一个简单的Demo流程介绍了Activiti框架启动、部署、运行过程。
Demo准备
流程图文件:
<?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:activiti="http://activiti.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.activiti.org/test">
<process id="hello" name="hello" isExecutable="true">
<!-- 流程开始节点 -->
<startEvent id="start" name="Start" ></startEvent>
<!-- serviceTask:执行me.likeyao.activiti.demo.HelloWorld的execute方法,打印hello world -->
<serviceTask id="helloworld" name="helloworld" activiti:class="me.likeyao.activiti.demo.HelloWorld"/>
<!-- 流程结束节点 -->
<endEvent id="end" name="End"></endEvent>
<!-- 流程迁移线:开始节点到serviceTask节点 -->
<sequenceFlow id="sid-1" sourceRef="start" targetRef="helloworld"></sequenceFlow>
<!-- 流程迁移线:serviceTask节点到结束节点 -->
<sequenceFlow id="sid-3" sourceRef="helloworld" targetRef="end"></sequenceFlow>
</process>
</definitions>
代码:
public class App {
public static void main(String[] args) {
//创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//部署流程图
processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();
//发起流程
processEngine.getRuntimeService().startProcessInstanceByKey("hello");
}
}
public class HelloWorld implements JavaDelegate{
public void execute(DelegateExecution execution) throws Exception {
System.out.println("Hello world!");
}
}
Demo实现的功能是发起一个流程,执行到流程的serviceTask节点时,打印Hello world!,然后流程结束。
源码版本:5.22.0
框架初始化
ProcessEngine类图
ProcessEngine
ProcessEngine是Activiti框架的门面,ProcessEngine本身不提供任何功能,通过getXXXService方法可以获取到对应的Service对象执行操作。Demo中涉及到的两个Service:
- RepositoryService:流程定义和流程部署相关功能。
- RuntimeService:流程实例相关功能(发起流程、获取流程实例变量)。
ProcessEngineConfiguration
ProcessEngineConfiguration负责Activiti框架的属性配置、初始化工作,初始化入口是buildProcessEngine方法,所有Activiti框架运行时需要用到的组件基本都在这里初始化:
public ProcessEngine buildProcessEngine() {
init();
return new ProcessEngineImpl(this);
}
protected void init() {
initConfigurators();
configuratorsBeforeInit();
initProcessDiagramGenerator();
initHistoryLevel();
initExpressionManager();
initDataSource();
initVariableTypes();
initBeans();
initFormEngines();
initFormTypes();
initScriptingEngines();
initClock();
initBusinessCalendarManager();
initCommandContextFactory();
initTransactionContextFactory();
initCommandExecutors();
initServices();
initIdGenerator();
initDeployers();
initJobHandlers();
initJobExecutor();
initAsyncExecutor();
initTransactionFactory();
initSqlSessionFactory();
initSessionFactories();
initJpa();
initDelegateInterceptor();
initEventHandlers();
initFailedJobCommandFactory();
initEventDispatcher();
initProcessValidator();
initDatabaseEventLogging();
configuratorsAfterInit();
}
这里有一个扩展点:ProcessEngineConfigurator。
public interface ProcessEngineConfigurator {
//组件初始化前
void beforeInit(ProcessEngineConfigurationImpl processEngineConfiguration);
//组件初始化后
void configure(ProcessEngineConfigurationImpl processEngineConfiguration);
//优先级
int getPriority();
}
在init初始化方法中,initConfigurators方法通过ServiceLoader加载ProcessEngineConfigurator。随后在configuratorsBeforeInit和configuratorsAfterInit方法中分别调用ProcessEngineConfigurator的beforeInit和configure方法,使用户可以在ProcessEngineConfiguration初始化前后编程式的修改属性,替换Activiti默认组件。
流程部署
流程部署实现的功能是将xml格式的流程图,转化为Activiti框架运行时依赖的流程定义对象。
RepositoryService
Demo中通过以下代码部署了一个流程:
processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();
createDeployment方法中创建了DeploymentBuilder对象,DeploymentBuilder对象负责读取指定路径的流程图xml文件的内容(byte数组),并缓存在DeploymentEntity对象中:
public DeploymentBuilder addInputStream(String resourceName, InputStream inputStream) {
...
byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
ResourceEntity resource = new ResourceEntity();
resource.setName(resourceName);
resource.setBytes(bytes);
deployment.addResource(resource);
return this;
}
最终DeploymentBuilder的deploy方法会调用RepositoryService的deploy方法,完成流程部署:
public Deployment deploy() {
return repositoryService.deploy(this);
}
CommandExecutor
在RepositoryService的deploy方法中,使用了CommandExecutor对象:
public Deployment deploy(DeploymentBuilderImpl deploymentBuilder) {
return commandExecutor.execute(new DeployCmd<Deployment>(deploymentBuilder));
}
在Activiti中,大部分操作都以Command模式实现,例如部署流程图的DeployCmd。CommandExecutor封装了一系列的CommandInterceptor,在内部形成CommandInterceptor链,在命令执行前后做了拦截。Activiti框架提供了一些
CommandInterceptor实现:
名称 | 作用 |
---|---|
CommandContextInterceptor | 用于生成命令执行的上下文(CommandContext)。 |
LogInterceptor | 开启日志Debug级别后,打印日志。 |
JtaTransactionInterceptor | 开启Jta事务 |
引入activiti-spring包,通过SpringTransactionInterceptor引入Spring的事务支持。
CommandExecutor在ProcessEngineConfigurationImpl的initCommandExecutors方法中初始化:
protected void initCommandExecutors() {
initDefaultCommandConfig();
initSchemaCommandConfig();
initCommandInvoker();
initCommandInterceptors();
initCommandExecutor();
}
可以设置ProcessEngineConfigurationImpl的customPreCommandInterceptors和customPostCommandInterceptors属性,添加自定义的CommandInterceptor:
protected void initCommandInterceptors() {
if (commandInterceptors==null) {
commandInterceptors = new ArrayList<CommandInterceptor>();
if (customPreCommandInterceptors!=null) {
commandInterceptors.addAll(customPreCommandInterceptors);
}
commandInterceptors.addAll(getDefaultCommandInterceptors());
if (customPostCommandInterceptors!=null) {
commandInterceptors.addAll(customPostCommandInterceptors);
}
commandInterceptors.add(commandInvoker);
}
}
这里的pre和post是指Activiti框架getDefaultCommandInterceptors()的前后。
CommandInvoker是CommandInterceptor链的最后一个对象,负责调用Command:
public class CommandInvoker extends AbstractCommandInterceptor {
@Override
public <T> T execute(CommandConfig config, Command<T> command) {
return command.execute(Context.getCommandContext());
}
}
CommandContext
CommandContext是Activit框架Command执行的上下文,主要包含各种SessionFactory:
sessionFactories = processEngineConfiguration.getSessionFactories();
SessionFactory负责生成Session,Session是Activiti操作持久化对象的统一接口:
名称 | 作用 |
---|---|
ProcessDefinitionEntityManager | 流程定义相关读写操作。 |
ExecutionEntityManager | 流程实例相关读写操作。 |
DefaultHistoryManager | 历史记录相关读写操作 |
CommandContext的生命周期
CommandConext在CommandContextInterceptor中创建,在finally代码块中销毁:
public <T> T execute(CommandConfig config, Command<T> command) {
//首先尝试从线程上下文的栈中获取CommandContext
CommandContext context = Context.getCommandContext();
boolean contextReused = false;
//什么时候创建新的CommandContext?
//1、CommandConfig中指定了不复用CommandContext
//2、当前线程上下文中不存在CommandConext
//3、当前线程上下文中的CommandConext已经抛出异常
if (!config.isContextReusePossible() || context == null || context.getException() != null) {
context = commandContextFactory.createCommandContext(command); }
else {
contextReused = true;
}
try {
//将前面获取到的CommandContext入栈
Context.setCommandContext(context);
Context.setProcessEngineConfiguration(processEngineConfiguration);
//执行下一个interceptor,在CommandInvoker中可以通过Context.getCommandContext()获取线程上下文中的CommandContext
return next.execute(config, command);
} catch (Exception e) {
//记录异常信息
context.exception(e);
} finally {
try {
//如果CommandContext不可复用,用完直接关闭
if (!contextReused) {
context.close();
}
} finally {
//出栈操作
Context.removeCommandContext();
Context.removeProcessEngineConfiguration();
Context.removeBpmnOverrideContext();
}
}
return null;
}
Activiti的框架可以在一个Command的执行过程中,调用另外一个Command,所以会出现是否需要复用CommandContext的选项,默认值为true。
流程的解析
在DeployCmd中,首先调用DeploymentEntityManager持久化存储DeploymentEntity对象:
commandContext.getDeploymentEntityManager().insertDeployment(deployment);
然后调用DeploymentManager部署流程(流程解析):
commandContext.getProcessEngineConfiguration().getDeploymentManager().deploy(deployment, deploymentSettings);
DeploymentEntityManager
DeploymentEntityManager的deploy方法中循环调用Deployer对象的deploy方法,Activiti默认的Deployer是BpmnDeployer。
另外DeploymentEntityManager中还缓存了解析好的流程定义对象和Bpmn模型对象。
Activiti持久化的是流程图xml文件,每次系统重新启动都要执行一次“deploy”操作,生成ProcessDefinitionEntity对象。
BpmnDeployer
BpmnDeployer的deploy方法中包含几个操作(代码缩略版):
public void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings) {
...
BpmnParse bpmnParse = bpmnParser.createParse().sourceInputStream(inputStream).setSourceSystemId(resourceName).deployment(deployment).name(resourceName);
bpmnParse.execute();
for (ProcessDefinitionEntity processDefinition: bpmnParse.getProcessDefinitions()) {
if (deployment.isNew()) {
ProcessDefinitionEntity latestProcessDefinition = ...
if (latestProcessDefinition != null) {
processDefinitionVersion = latestProcessDefinition.getVersion() + 1;
}else{
processDefinitionVersion = 1;
}
processDefinition.setId(idGenerator.getNextId());
dbSqlSession.insert(processDefinition);
}
...
}
}
- 通过BpmnParser对象创建BpmnParse。
- 调用BpmnParse的execute方法,将inputStream中的流程图转化为ProcessDefinitionEntity。
- 持久化ProcessDefinitionEntity对象。
BpmnParse
在BpmnParse的execute中完成了xml文件到ProcessDefinitionEntity对象的转化:
public BpmnParse execute() {
//xml->bpmnModel
bpmnModel = converter.convertToBpmnModel(streamSource, validateSchema, enableSafeBpmnXml, encoding);
//bpmnModel-> ProcessDefinitionEntity
transformProcessDefinitions();
}
protected void transformProcessDefinitions() {
for (Process process : bpmnModel.getProcesses()) {
bpmnParserHandlers.parseElement(this, process);
}
}
在流程定义解析过程中,会涉及到两套模型:
- Bpmn模型(由BpmnXMLConverter完成转换)
- PVM模型(由BpmnParseHandlers完成转换)
Bpmn模型
PVM模型
Bpmn模型更偏向于xml节点的描述,PVM模型是运行时模型。Bpmn模型中的ServiceTask、StartEvent等会统一映射转换为PVM的ActivityImpl对象,ServiceTask和StartEvent等节点行为上的差别,体现在ActivityImpl对象持有的不同的ActivityBehavior上。
运行流程
创建流程实例
在demo中通过RuntimeService发起流程实例:
processEngine.getRuntimeService().startProcessInstanceByKey("hello");
在startProcessInstanceByKey方法中执行StartProcessInstanceCmd命令:
public class StartProcessInstanceCmd<T> implements Command<ProcessInstance>, Serializable {
...
public ProcessInstance execute(CommandContext commandContext) {
//获取流程定义
ProcessDefinitionEntity processDefinition = ...
//创建流程实例
ExecutionEntity processInstance = processDefinition.createProcessInstance(businessKey);
//开始流程
processInstance.start();
return processInstance;
}
...
}
在StartProcessInstanceCmd方中通过流程定义ProcessDefinitionEntity创建了流程实例 ExecutionEntity:
ExecutionEntity实现了一些重要接口:
- PVM相关的接口,赋予了ExecutionEntity流程驱动的能力,例如single、start方法。
- 实现VariableScope接口让ExecutionEntity可以持久上下文变量。
- ProcessInstance接口暴露了ExecutionEntity关联的ProcessDefinitionEntity的信息。
- PersistentObject接口代表ExecutionEntity对象是需要持久化。
在ExecutionEntity中维护类一个属性:activity。activity属性代表当前执行到哪个节点,在创建ExecutionEntity过程中会设置activity,使流程从某一个节点开始,默认是开始节点。
最后StartProcessInstanceCmd还调用ExecutionEntity的start方法开始驱动流程:
public void start() {
performOperation(AtomicOperation.PROCESS_START);
}
驱动流程
Activiti框架的流程运行于PVM模型之上,在流程运行时主要涉及到PVM中几个对象:ActivityImpl、TransitionImpl和ActivityBehavior。
- ActivityImpl:ActivityImpl是流程节点的抽象,ActivityImpl维护流程图中节点的连线,包括有哪些进线,有哪些出线。另外还包含节点同步/异步执行等信息。
- TransitionImpl:TransitionImpl包含source和target两个属性,连接了两个流程节点。
- ActivityBehavior:每一个ActivityImpl对象都拥有一个ActivityBehavior对象,ActivityBehavior代表节点的行为。
ActivityImpl、TransitionImpl和ActivityBehavior只是描述了流程的节点、迁移线和节点行为,真正要让ExecutionEntity流转起来,还需要AtomicOperation的驱动:
AtomicOperation PROCESS_START = new AtomicOperationProcessStart();
AtomicOperation PROCESS_START_INITIAL = new AtomicOperationProcessStartInitial();
AtomicOperation PROCESS_END = new AtomicOperationProcessEnd();
AtomicOperation ACTIVITY_START = new AtomicOperationActivityStart();
AtomicOperation ACTIVITY_EXECUTE = new AtomicOperationActivityExecute();
AtomicOperation ACTIVITY_END = new AtomicOperationActivityEnd();
AtomicOperation TRANSITION_NOTIFY_LISTENER_END = new AtomicOperationTransitionNotifyListenerEnd();
AtomicOperation TRANSITION_DESTROY_SCOPE = new AtomicOperationTransitionDestroyScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_TAKE = new AtomicOperationTransitionNotifyListenerTake();
AtomicOperation TRANSITION_CREATE_SCOPE = new AtomicOperationTransitionCreateScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_START = new AtomicOperationTransitionNotifyListenerStart();
AtomicOperation DELETE_CASCADE = new AtomicOperationDeleteCascade();
AtomicOperation DELETE_CASCADE_FIRE_ACTIVITY_END = new AtomicOperationDeleteCascadeFireActivityEnd();
在ExecutionEntity的start方法中,调用了PROCESS_START,PROCESS_START做了几件事:
- 获取流程定义级别定义的监听start事件的ExecutionListener,调用notify方法。
- 如果开启了事件功能,发布ActivitiEntityWithVariablesEvent和ActivitiProcessStartedEvent。
- 调用PROCESS_START_INITIAL。
PROCESS_START_INITIAL也实现了类似的功能:
- 获取初始节点上定义的监听start事件的ExecutionListener,调用notify方法。
- 调用ACTIVITY_EXECUTE。
在Demo流程执行中涉及的AtomicOperation的链路主要包括:
- ACTIVITY_EXECUTE:调用当前activity的behavior。
- TRANSITION_NOTIFY_LISTENER_END:某个activity节点执行完毕,调用节点上声明的监听end事件的ExecutionListener。
- TRANSITION_NOTIFY_LISTENER_TAKE:触发线上的ExecutionListener。
- TRANSITION_NOTIFY_LISTENER_START:某个activity节点即将开始执行,调用节点上的监听start事件的ExecutionListener。
以Demo流程中的ServiceTask节点helloworld为例,在执行ACTIVITY_EXECUTE时,会获取activity关联的behavior:
public class AtomicOperationActivityExecute implements AtomicOperation {
public void execute(InterpretableExecution execution) {
...
ActivityImpl activity = (ActivityImpl) execution.getActivity();
ActivityBehavior activityBehavior = activity.getActivityBehavior();
activityBehavior.execute(execution);
...
}
}
ServiceTask解析时关联的是ServiceTaskJavaDelegateActivityBehavior,execution方法:
public void execute(ActivityExecution execution) throws Exception {
//execution中调用了me.likeyao.activiti.demo.HelloWorld
execute((DelegateExecution) execution);
//离开当前节点
leave(execution);
}
在leave方法中调用了:
bpmnActivityBehavior.performDefaultOutgoingBehavior(execution);
performDefaultOutgoingBehavior方法会在当前activity的 出线中选择一条,使流程流向下一个节点。在Demo中只有一条线存在:
protected void performOutgoingBehavior(ActivityExecution execution,
boolean checkConditions, boolean throwExceptionIfExecutionStuck, List<ActivityExecution> reusableExecutions) {
if (transitionsToTake.size() == 1) {
execution.take(transitionsToTake.get(0));
}
}
最终take方法会将流程驱动权交还到AtomicOperation中:
public class ExecutionEntity{
...
public void take(PvmTransition transition, boolean fireActivityCompletionEvent) {
...
setActivity((ActivityImpl)transition.getSource());
setTransition((TransitionImpl) transition);
performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_END);
...
}
...
}
AtomicOperation的问题
按照AtomicOperation的驱动模式,只有当遇到UserTask等需要等待single信号的节点,调用才会返回。这意味着当调用RuntimeService启动一个流程实例时,要一直等到流程运行到一个UserTask节点调用才会返回,如果流程比较长耗时非常验证。
另一个问题是当流程图比较复杂,ExecutionListener数量比较多时,AtomicOperation之间的互相调用会导致调用栈非常深。
AtomicOperation驱动模式与ExecutionEntity、Behavior等绑定的比较紧密,暂时没有特别好的办法替换掉。
小结
本文主要介绍了Activiti框架的启动、部署、运行的主链路,并没有深入BPMN规范和Activit功能的具体实现,后续打算根据Activiti的用户手册,详细分析每个功能的使用和实现。
Activiti 核心 API
Activiti 核心 API
7大接口
- RepositoryService:提供一系列管理流程部署和流程定义的API。
- RuntimeService:在流程运行时对流程实例进行管理与控制。
- TaskService:对流程任务进行管理,例如任务提醒、任务完成和创建任务等。
- IdentityService:提供对流程角色数据进行管理的API,这些角色数据包括用户组、用户及它们之间的关系。
- ManagementService:提供对流程引擎进行管理和维护的服务。
- HistoryService:对流程的历史数据进行操作,包括查询、删除这些历史数据。
- FormService:表单服务。
核心API
.1: ProcessEngine
说明:
-
在Activiti中最核心的类,其他的类都是由他而来。
-
产生方式:
在前面看到了两种创建ProcessEngine(流程引擎)的方式,而这里要简化很多,调用ProcessEngines的getDefaultProceeEngine方法时会自动加载classpath下名为activiti.cfg.xml文件。
-
可以产生RepositoryService
-
可以产生RuntimeService
-
可以产生TaskService
各个Service的作用:
RepositoryService | 管理流程定义 |
---|---|
RuntimeService | 执行管理,包括启动、推进、删除流程实例等操作 |
TaskService | 任务管理 |
HistoryService | 历史管理(执行完的数据的管理) |
IdentityService | 组织机构管理 |
FormService | 一个可选服务,任务表单管理 |
ManagerService | 使用Activiti的定制环境中基本上不会用到。 它可以查询数据库的表和表的元数据。另外,它提供了查询和管理异步操作的功能。 |
.2:RepositoryService
是Activiti的仓库服务类。所谓的仓库指流程定义文档的两个文件:bpmn文件和流程图片。
-
产生方式
-
可以产生DeploymentBuilder,用来定义流程部署的相关参数
-
删除流程定义
.3:RuntimeService
是activiti的流程执行服务类。可以从这个服务类中获取很多关于流程执行相关的信息。
.4:TaskService
是activiti的任务服务类。可以从这个类中获取任务的信息。
.5:HistoryService
是activiti的查询历史信息的类。在一个流程执行完成后,这个对象为我们提供查询历史信息。
.6:ProcessDefinition
流程定义类。可以从这里获得资源文件等。
.7:ProcessInstance
代表流程定义的执行实例。如范冰冰请了一天的假,她就必须发出一个流程实例的申请。一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大的流程分支,即一个流程中流程实例只有一个。
.8:Execution
Activiti用这个对象去描述流程执行的每一个节点。在没有并发的情况下,Execution就是同ProcessInstance。流程按照流程定义的规则执行一次的过程,就可以表示执行对象Execution。
从源代码中可以看出ProcessInstance就是Execution。但在现实意义上有所区别:
在单线流程中,如上图的贷款流程,ProcessInstance与Execution是一致的。
这个例子有一个特点:wire money(汇钱)和archive(存档)是并发执行的。 这个时候,总线路代表ProcessInstance,而分线路中每个活动代表Execution。
总结:
-
一个流程中,执行对象可以存在多个,但是流程实例只能有一个。
-
当流程按照规则只执行一次的时候,那么流程实例就是执行对象。
Activiti 用户手册
Activiti 数据库表结构
——文档适用于 Activiti 5-6
Activiti 数据库表结构设计说明
Activiti 工作流总共包含 23
张数据表(现在是25张,新增了 ACT_EVT_LOG
和 ACT_PROCDEF_INFO
)
Activiti 7 的数据库表结构设计
# Activiti 7 的数据库表结构设计
ACT_EVT_LOG, ACT_GE_BYTEARRAY, ACT_GE_PROPERTY, ACT_HI_ACTINST, ACT_HI_ATTACHMENT, ACT_HI_COMMENT, ACT_HI_DETAIL, ACT_HI_IDENTITYLINK, ACT_HI_PROCINST, ACT_HI_TASKINST, ACT_HI_VARINST, ACT_PROCDEF_INFO, ACT_RE_DEPLOYMENT, ACT_RE_MODEL, ACT_RE_PROCDEF, ACT_RU_DEADLETTER_JOB, ACT_RU_EVENT_SUBSCR, ACT_RU_EXECUTION, ACT_RU_IDENTITYLINK, ACT_RU_INTEGRATION, ACT_RU_JOB, ACT_RU_SUSPENDED_JOB, ACT_RU_TASK, ACT_RU_TIMER_JOB, ACT_RU_VARIABLE
Activiti 各大版本表对比
以下只是对表名做了简单的对比,并没有涉及到数据库表字段及数据类型
编号 | 表名 | Activiti 5.23.0 | Acticiti 6.0.0 | Activiti 7.1.0.M6 |
---|---|---|---|---|
1 | ACT_EVT_LOG | ☑ | ☑ | ☑ |
2 | ACT_GE_BYTEARRAY | ☑ | ☑ | ☑ |
3 | ACT_GE_PROPERTY | ☑ | ☑ | ☑ |
4 | ACT_HI_ACTINST | ☑ | ☑ | ☑ |
5 | ACT_HI_ATTACHMENT | ☑ | ☑ | ☑ |
6 | ACT_HI_COMMENT | ☑ | ☑ | ☑ |
7 | ACT_HI_DETAIL | ☑ | ☑ | ☑ |
8 | ACT_HI_IDENTITYLINK | ☑ | ☑ | ☑ |
9 | ACT_HI_PROCINST | ☑ | ☑ | ☑ |
10 | ACT_HI_TASKINST | ☑ | ☑ | ☑ |
11 | ACT_HI_VARINST | ☑ | ☑ | ☑ |
12 | ACT_ID_GROUP | ☑ | ☑ | |
13 | ACT_ID_INFO | ☑ | ☑ | |
14 | ACT_ID_MEMBERSHIP | ☑ | ☑ | |
15 | ACT_ID_USER | ☑ | ☑ | |
16 | ACT_PROCDEF_INFO | ☑ | ☑ | ☑ |
17 | ACT_RE_DEPLOYMENT | ☑ | ☑ | ☑ |
18 | ACT_RE_MODEL | ☑ | ☑ | ☑ |
19 | ACT_RE_PROCDEF | ☑ | ☑ | ☑ |
20 | ACT_RU_DEADLETTER_JOB | ☑ | ☑ | |
21 | ACT_RU_EVENT_SUBSCR | ☑ | ☑ | ☑ |
22 | ACT_RU_EXECUTION | ☑ | ☑ | ☑ |
23 | ACT_RU_IDENTITYLINK | ☑ | ☑ | ☑ |
24 | ACT_RU_INTEGRATION | ☑ | ||
25 | ACT_RU_JOB | ☑ | ☑ | ☑ |
26 | ACT_RU_SUSPENDED_JOB | ☑ | ☑ | |
27 | ACT_RU_TASK | ☑ | ☑ | ☑ |
28 | ACT_RU_TIMER_JOB | ☑ | ☑ | |
29 | ACT_RU_VARIABLE | ☑ | ☑ | ☑ |
表名规则
Activiti 使用到的表都是 ACT_ 开头的。表名的第二部分用两个字母表明表的用途。
- ACT_GE_ (
GE
) 表示 general 全局通用数据及设置,各种情况都使用的数据。 - ACT_HI_ (
HI
) 表示 history 历史数据表,包含着程执行的历史相关数据,如结束的流程实例,变量,任务,等等 - ACT_ID_ (
ID
) 表示 identity 组织机构,用户记录,流程中使用到的用户和组。这些表包含标识的信息,如用户,用户组,等等。 - ACT_RE_ (
RE
) 表示 repository 存储,包含的是静态信息,如,流程定义,流程的资源(图片,规则等)。 - ACT_RU_ (
RU
) 表示 runtime 运行时,运行时的流程变量,用户任务,变量,职责(job)等运行时的数据。Activiti 只存储实例执行期间的运行时数据,当流程实例结束时,将删除这些记录。这就保证了这些运行时的表小且快。
25 张表详情
一般数据 (ACT_GE_)
通用数据表
表名 | 解释 |
---|---|
ACT_GE_BYTEARRAY | 二进制数据表,存储通用的流程定义和流程资源。 |
ACT_GE_PROPERTY | 系统相关属性,属性数据表存储整个流程引擎级别的数据,初始化表结构时,会默认插入三条记录。 |
流程历史记录 (ACT_HI_)
表名 | 解释 |
---|---|
ACT_HI_ACTINST | 历史节点表 |
ACT_HI_ATTACHMENT | 历史附件表 |
ACT_HI_COMMENT | 历史意见表 |
ACT_HI_DETAIL | 历史详情表,提供历史变量的查询 |
ACT_HI_IDENTITYLINK | 历史流程人员表 |
ACT_HI_PROCINST | 历史流程实例表 |
ACT_HI_TASKINST | 历史任务实例表 |
ACT_HI_VARINST | 历史变量表 |
用户用户组表 (ACT_ID_)
身份数据表
表名 | 解释 |
---|---|
ACT_ID_GROUP | 用户组信息表 |
ACT_ID_INFO | 用户扩展信息表 |
ACT_ID_MEMBERSHIP | 用户与用户组对应信息表 |
ACT_ID_USER | 用户信息表 |
流程定义表 (ACT_RE_)
流程存储数据表
表名 | 解释 |
---|---|
ACT_RE_DEPLOYMENT | 部署信息表 |
ACT_RE_MODEL | 流程设计模型部署表 |
ACT_RE_PROCDEF | 流程定义数据表 |
运行实例表 (ACT_RU_)
表名 | 解释 |
---|---|
ACT_RU_EVENT_SUBSCR | 运行时事件 throwEvent、catchEvent 时间监听信息表 |
ACT_RU_EXECUTION | 运行时流程执行实例 |
ACT_RU_IDENTITYLINK | 运行时流程人员表,主要存储任务节点与参与者的相关信息 |
ACT_RU_JOB | 运行时定时任务数据表 |
ACT_RU_TASK | 运行时任务节点表 |
ACT_RU_VARIABLE | 运行时流程变量数据表 |
其它
表名 | 解释 |
---|---|
ACT_EVT_LOG | 事件日志 |
ACT_PROCDEF_INFO | 流程定义的动态变更信息 |
一般数据 (ACT_GE_)
ACT_GE_BYTEARRAY 二进制数据表
二进制数据表,存储通用的流程定义和流程资源。(act_ge_bytearray)
保存流程定义图片和xml、Serializable(序列化)的变量,即保存所有二进制数据,特别注意类路径部署时候,不要把svn等隐藏文件或者其他与流程无关的文件也一起部署到该表中,会造成一些错误(可能导致流程定义无法删除)
用于保存与流程引擎相关的资源,只要调用了 Activiti 存储服务的 API ,涉及的资源均会被转换为 byte 数组保存到这个表中。在资源表中设计了一个 BYTES宇段,用来保存资源的内容,因此理论上其可以用于保存任何类型的资源(文件或者其他来源的输入流)。一般情况下, Activiti 使用这个表来保存字符串、流程文件的内容、流程图片内容
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | Y | 主键ID | |
REV_ | 乐观锁 | int | Y | Version(版本) | |
NAME_ | 名称 | nvarchar(255) | Y | 部署的文件名称,如:leave.bpmn.png,leave.bpmn20.xml | |
DEPLOYMENT_ID_ | 部署ID | nvarchar(64) | Y | 部署表ID | |
BYTES_ | 字节 | varbinary(max) | Y | 部署文件 | |
GENERATED_ | 是否是引擎生成 | tinyint | Y | 0为用户生成,1为activiti生成 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | 数据版本, Activiti为一些有可能会被频繁修改的数据表,加入该字段,用来表示该数据被操作的次数 |
NAME_ | varchar(255) | YES | 资源名称 |
DEPLOYMENT_ID_ | varchar(64) | YES | 一次部署可以添加多个资源,该字段与部署表ACT_RE_DEPLOYMENT的主键相关联 |
BYTES_ | longblob | YES | 资源内容,最大可存储4G数据 |
GENERATED_ | tinyint(4) | YES | 是否由Activiti自动产生的资源,0 |
ACT_GE_PROPERTY 属性数据表
属性数据表(act_ge_property)
属性数据表。存储整个流程引擎级别的数据。
Activiti 将全部的属性抽象为 key-value 对,每个属性都有名称和值, 使用
ACT GE PROPERTY 来保存这些属性
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
NAME_ | 名称 | nvarchar(64) | √ | schema.versionschema.historynext.dbid | |
VALUE_ | 值 | nvarchar(300) | √ | 5.create(5.) | |
REV_ | 乐观锁 | int | √ | version |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
NAME_ | varchar(64) | NO | 名称 |
VALUE_ | varchar(300) | YES | 值 |
REV_ | int(11) | YES | 数据的版本号 |
流程历史记录 (ACT_HI_)
历史数据表就好像流程引擎的日志表,操作过的流程元素将会被记录到历史表中。
ACT_HI_ACTINST 历史节点表
历史节点表(act_hi_actinst)
历史活动信息。这里记录流程流转过的所有节点,与HI_TASKINST不同的是,taskinst只记录usertask内容
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ||
PROC_DEF_ID_ | 流程定义ID | nvarchar(64) | |||
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | |||
EXECUTION_ID_ | 执行实例ID | nvarchar(64) | |||
ACT_ID_ | 节点ID | nvarchar(225) | 节点定义ID | ||
TASK_ID_ | 任务实例ID | nvarchar(64) | √ | 任务实例ID 其他节点类型实例ID在这里为空 | |
CALL_PROC_INST_ID_ | 调用外部的流程实例ID | nvarchar(64) | √ | 调用外部流程的流程实例ID’ | |
ACT_NAME_ | 节点名称 | nvarchar(225) | √ | 节点定义名称 | |
ACT_TYPE_ | 节点类型 | nvarchar(225) | 如startEvent、userTask | ||
ASSIGNEE_ | 签收人 | nvarchar(64) | √ | 节点签收人 | |
START_TIME_ | 开始时间 | datetime | 2013-09-15 11:30:00 | ||
END_TIME_ | 结束时间 | datetime | √ | 2013-09-15 11:30:00 | |
DURATION_ | 耗时 | numeric(19,0) | √ | 毫秒值 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
PROC_DEF_ID_ | varchar(64) | NO | |
PROC_INST_ID_ | varchar(64) | NO | |
EXECUTION_ID_ | varchar(64) | NO | |
ACT_ID_ | varchar(255) | NO | |
TASK_ID_ | varchar(64) | YES | |
CALL_PROC_INST_ID_ | varchar(64) | YES | |
ACT_NAME_ | varchar(255) | YES | |
ACT_TYPE_ | varchar(255) | NO | |
ASSIGNEE_ | varchar(255) | YES | |
START_TIME_ | datetime | NO | |
END_TIME_ | datetime | YES | |
DURATION_ | bigint(20) | YES | |
DELETE_REASON_ | varchar(4000) | YES | |
TENANT_ID_ | varchar(255) | YES |
ACT_HI_ATTACHMENTA 历史附件表
历史附件表( act_hi_attachment )
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
REV_ | 乐观锁 | integer | √ | Version | |
USER_ID_ | 用户ID | nvarchar(255) | √ | 用户ID | |
NAME_ | 名称 | nvarchar(255) | √ | 附件名称 | |
DESCRIPTION_ | 描述 | nvarchar(4000) | √ | 描述 | |
TYPE_ | 类型 | nvarchar(255) | √ | 附件类型 | |
TASK_ID_ | 任务实例ID | nvarchar(64) | √ | 节点实例ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
URL_ | URL_ | nvarchar(4000) | √ | 附件地址 | |
CONTENT_ID_ | 字节表的ID | nvarchar(64) | √ | ACT_GE_BYTEARRAY的ID |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
USER_ID_ | varchar(255) | YES | 附件对应的用户ID |
NAME_ | varchar(255) | YES | 附件名称 |
DESCRIPTION_ | varchar(4000) | YES | 附件描述 |
TYPE_ | varchar(255) | YES | 附件类型 |
TASK_ID_ | varchar(64) | YES | 附件对应的任务ID |
PROC_INST_ID_ | varchar(64) | YES | 对应的流程实例ID |
URL_ | varchar(4000) | YES | 链接到该附件的URL |
CONTENT_ID_ | varchar(64) | YES | 附件内容ID,附件内容会保存到资源表中,该字段记录资源数据ID |
TIME_ | datetime | YES | 数据产生的时间 |
ACT_HI_COMMENT 历史意见表
历史意见表( act_hi_comment )
表实际不只保存评论数据,它还会保存某些事件数据
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
TYPE_ | 类型 | nvarchar(255) | √ | 类型:event(事件)comment(意见) | |
TIME_ | 时间 | datetime | 填写时间’ | ||
USER_ID_ | 用户ID | nvarchar(64) | √ | 填写人 | |
TASK_ID_ | 节点任务ID | nvarchar(64) | √ | 节点实例ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(255) | √ | 流程实例ID | |
ACTION_ | 行为类型 | nvarchar(64) | √ | 见备注1 | |
MESSAGE_ | 基本内容 | nvarchar(4000) | √ | 用于存放流程产生的信息,比如审批意见 | |
FULL_MSG_ | 全部内容 | varbinary(max) | √ | 附件地址 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
TYPE_ | varchar(255) | YES | 评论的类型可以设值为event或comment表示事件记录数据或者评论数据 |
TIME_ | datetime | NO | 数据产生的时间 |
USER_ID_ | varchar(255) | YES | 产生评论数据的用户ID |
TASK_ID_ | varchar(64) | YES | 评论数据的任务ID |
PROC_INST_ID_ | varchar(64) | YES | 评论数据对应的流程实例ID |
ACTION_ | varchar(255) | YES | 该评论数据的操作标识 |
MESSAGE_ | varchar(4000) | YES | 该评论数据的信息 |
FULL_MSG_ | longblob | YES | 该字段同样记录评论数据的信息 |
ACT_HI_DETAIL 流程明细表
历史详情表( act_hi_detail )
流程中产生的变量详细,包括控制流程流转的变量,业务表单中填写的流程需要用到的变量等。
流程明细表(ACT HI DETAlL 会记录流程执行过程中的参数或者表单数据,由于在流程执行过程中,会产生大量这类数据,因此默认情况下, Activiti 不会保存流程明细数据,除非将流程引擎的历史数据( hist。可〉配置为 full
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键 | |
TYPE_ | 类型 | nvarchar(255) | 见备注2 | ||
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
EXECUTION_ID_ | 执行实例ID | nvarchar(64) | √ | 执行实例ID | |
TASK_ID_ | 任务实例ID | nvarchar(64) | √ | 任务实例ID | |
ACT_INST_ID_ | 节点实例ID | nvarchar(64) | √ | ACT_HI_ACTINST表的ID | |
NAME_ | 名称 | nvarchar(255) | 名称 | ||
VAR_TYPE_ | 参数类型 | nvarchar(255) | √ | 见备注3 | |
REV_ | 乐观锁 | int | √ | Version | |
TIME_ | 时间戳 | datetime | 创建时间 | ||
BYTEARRAY_ID_ | 字节表ID | nvarchar | √ | ACT_GE_BYTEARRAY表的ID | |
DOUBLE_ | DOUBLE_ | double precision | √ | 存储变量类型为Double | |
LONG_ | LONG_ | numeric | √ | 存储变量类型为long | |
TEXT_ | TEXT_ | nvarchar | √ | 存储变量值类型为String | |
TEXT2_ | TEXT2_ | nvarchar | √ | 此处存储的是JPA持久化对象时,才会有值。此值为对象ID |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
TYPE_ | varchar(255) | NO | |
PROC_INST_ID_ | varchar(64) | YES | |
EXECUTION_ID_ | varchar(64) | YES | |
TASK_ID_ | varchar(64) | YES | |
ACT_INST_ID_ | varchar(64) | YES | |
NAME_ | varchar(255) | NO | |
VAR_TYPE_ | varchar(255) | YES | |
REV_ | int(11) | YES | |
TIME_ | datetime | NO | |
BYTEARRAY_ID_ | varchar(64) | YES | |
DOUBLE_ | double | YES | |
LONG_ | bigint(20) | YES | |
TEXT_ | varchar(4000) | YES | |
TEXT2_ | varchar(4000) | YES |
ACT_HI_IDENTITYLINK 历史流程人员表
历史流程人员表( act_ru_identitylink )
任务参与者数据表。主要存储历史节点参与者的信息
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ID_ | |
GROUP_ID_ | 组ID | nvarchar(255) | √ | 组ID | |
TYPE_ | 类型 | nvarchar(255) | √ | 备注4 | |
USER_ID_ | 用户ID | nvarchar(255) | √ | 用户ID | |
TASK_ID_ | 节点实例ID | nvarchar(64) | √ | 节点实例ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID |
ACT_HI_PROCINST 流程实例表
历史流程实例表(act_hi_procinst)
只要流程被启动,就会将流程实例的数据写入 ACT HI PROC ST 表中 。除了基本的流程字段外,与运行时数据表不同的是,历史流程实例表还会记录流程的开始活动ID,结束活动ID等信息
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | 流程实例ID | ||
BUSINESS_KEY_ | 业务主键 | nvarchar(255) | √ | 业务主键,业务表单的ID | |
PROC_DEF_ID_ | 流程定义ID | nvarchar(64) | 流程定义ID | ||
START_TIME_ | 开始时间 | datetime | 开始时间 | ||
END_TIME_ | 结束时间 | datetime | √ | 结束时间 | |
DURATION_ | 耗时 | Numeric(19) | √ | 耗时 | |
START_USER_ID_ | 起草人 | nvarchar(255) | √ | 起草人 | |
START_ACT_ID_ | 开始节点ID | nvarchar(255) | √ | 起草环节ID | |
END_ACT_ID_ | 结束节点ID | nvarchar(255) | √ | 结束环节ID | |
SUPER_PROCESS_INSTANCE_ID_ | 父流程实例ID | nvarchar(64) | √ | 父流程实例ID | |
DELETE_REASON_ | 删除原因 | nvarchar(4000) | √ | 删除原因 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
PROC_INST_ID_ | varchar(64) | NO | |
BUSINESS_KEY_ | varchar(255) | YES | |
PROC_DEF_ID_ | varchar(64) | NO | |
START_TIME_ | datetime | NO | |
END_TIME_ | datetime | YES | |
DURATION_ | bigint(20) | YES | |
START_USER_ID_ | varchar(255) | YES | |
START_ACT_ID_ | varchar(255) | YES | 开始活动的 ID 一般是流程开始事件的ID,在流程文件中定义 |
END_ACT_ID_ | varchar(255) | YES | 流程最后一个活动的 ID一般是流程结束事件的ID,在流程文件中定义。 |
SUPER_PROCESS_INSTANCE_ID_ | varchar(64) | YES | |
DELETE_REASON_ | varchar(4000) | YES | 该流程实例被删除的原因 |
TENANT_ID_ | varchar(255) | YES | |
NAME_ | varchar(255) | YES |
ACT_HI_TASKINST 历史任务表
历史任务实例表( act_hi_taskinst )
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
PROC_DEF_ID_ | 流程定义ID | nvarchar(64) | √ | 流程定义ID | |
TASK_DEF_KEY_ | 节点定义ID | nvarchar(255) | √ | 节点定义ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
EXECUTION_ID_ | 执行实例ID | nvarchar(64) | √ | 执行实例ID | |
NAME_ | 名称 | varchar(255) | √ | 名称 | |
PARENT_TASK_ID_ | 父节点实例ID | nvarchar(64) | √ | 父节点实例ID | |
DESCRIPTION_ | 描述 | nvarchar(400) | √ | 描述 | |
OWNER_ | 实际签收人 任务的拥有者 | nvarchar(255) | √ | 签收人(默认为空,只有在委托时才有值) | |
ASSIGNEE_ | 签收人或被委托 | nvarchar(255) | √ | 签收人或被委托 | |
START_TIME_ | 开始时间 | datetime | 开始时间 | ||
CLAIM_TIME_ | 提醒时间 | datetime | √ | 提醒时间 | |
END_TIME_ | 结束时间 | datetime | √ | 结束时间 | |
DURATION_ | 耗时 | numeric(19) | √ | 耗时 | |
DELETE_REASON_ | 删除原因 | nvarchar(4000) | √ | 删除原因(completed,deleted) | |
PRIORITY_ | 优先级别 | int | √ | 优先级别 | |
DUE_DATE_ | 过期时间 | datetime | √ | 过期时间,表明任务应在多长时间内完成 | |
FORM_KEY_ | 节点定义的formkey | nvarchar(255) | √ | desinger节点定义的form_key属性 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
PROC_DEF_ID_ | varchar(64) | YES | |
TASK_DEF_KEY_ | varchar(255) | YES | |
PROC_INST_ID_ | varchar(64) | YES | |
EXECUTION_ID_ | varchar(64) | YES | |
NAME_ | varchar(255) | YES | |
PARENT_TASK_ID_ | varchar(64) | YES | |
DESCRIPTION_ | varchar(4000) | YES | |
OWNER_ | varchar(255) | YES | |
ASSIGNEE_ | varchar(255) | YES | |
START_TIME_ | datetime | NO | |
CLAIM_TIME_ | datetime | YES | |
END_TIME_ | datetime | YES | |
DURATION_ | bigint(20) | YES | |
DELETE_REASON_ | varchar(4000) | YES | |
PRIORITY_ | int(11) | YES | |
DUE_DATE_ | datetime | YES | |
FORM_KEY_ | varchar(255) | YES | |
CATEGORY_ | varchar(255) | YES | |
TENANT_ID_ | varchar(255) | YES |
ACT_HI_VARINST 历史变量表
历史变量表( act_hi_varinst )
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ID_ | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
EXECUTION_ID_ | 执行实例ID | nvarchar(255) | √ | 执行实例ID | |
TASK_ID_ | 任务实例ID | nvarchar(64) | √ | 任务实例ID | |
NAME_ | 名称 | nvarchar(64) | 参数名称(英文) | ||
VAR_TYPE_ | 参数类型 | varchar(255) | √ | 备注5 | |
REV_ | 乐观锁 | nvarchar(64) | √ | 乐观锁 Version | |
BYTEARRAY_ID_ | 字节表ID | nvarchar(400) | √ | ACT_GE_BYTEARRAY表的主键 | |
DOUBLE_ | DOUBLE_ | nvarchar(255) | √ | 存储DoubleType类型的数据 | |
LONG_ | LONG_ | nvarchar(255) | √ | 存储LongType类型的数据 | |
TEXT_ | TEXT_ | datetime | √ | 备注6 | |
TEXT2_ | TEXT2_ | datetime | √ | 此处存储的是JPA持久化对象时,才会有值。此值为对象ID |
用户用户组表 (ACT_ID_)
Activiti 整个身份数据模块,可以独立于流程引擎而存在 有关身份数据的几张表没有保存与流程相关的数据及关联。身份表名称以 ACT_ID 开头,表名中的 是单词 identity的缩写。
ACT_ID_GROUP 用户组信息表
用户组信息表( act_id_group )
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
REV_ | 乐观锁 | int | √ | 乐观锁Version | |
NAME_ | 名称 | nvarchar(255) | √ | 组名称 | |
TYPE_ | 类型 | nvarchar(255) | √ | 类型 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
NAME_ | varchar(255) | YES | 用户组名称 |
TYPE_ | varchar(255) | YES | 用户组类型,类型不由activiti提供,但是在某些业务中, Activiti 会根据该字段的值进行查询,字段值由 Activiti 定如 Activiti webService |
ACT_ID_INFO 用户扩展信息表
用户扩展信息表( act_id_info )
Activiti 将用户、用户账号和用户信息分为三种数据,其中用户表保存用户的数据,而用
户账号和用户信息,则被保存到 ACT_ID_INFO 表中
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
REV_ | 乐观锁 | int | √ | 乐观锁Version | |
USER_ID_ | 用户ID | nvarchar(64) | √ | ||
TYPE_ | 类型 | nvarchar(64) | √ | ||
KEY_ | nvarchar(255) | √ | |||
VALUE_ | nvarchar(255) | √ | |||
PASSWORD_ | Image | √ | |||
PARENT_ID_ | nvarchar(255) | √ |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
USER_ID_ | varchar(64) | YES | 对应用户表的数据ID(没有强制做外键关联) |
TYPE_ | varchar(64) | YES | 信息类型,当前可以设置用户账号(account)、用户信息(userinfo)和NULL三种值 |
KEY_ | varchar(255) | YES | 数据的键。可以根据改键来查找用户信息的值 |
VALUE_ | varchar(255) | YES | 数据的值 |
PASSWORD_ | longblob | YES | 用户账号的密码字段 |
PARENT_ID_ | varchar(255) | YES | 该信息的父信息ID,如果一条数据设置了父信息ID,则表示该数据是用户账号(信息)的明细数据,例如一个账号有激活日期,那么激活日期就是该账号的明细数据,此处使用了自关联来实现。 |
ACT_ID_MEMBERSHIP 用户与分组对应信息表
用户与分组对应信息表( act_id_membership )
用来保存用户的分组信息。
一个用户组下有多个用户, 一个用户可以属于不同的用户组,那么这种多对多的关系,就
使用关系表来进行描述,关系表为 ACT ID ME IBERSHIP ,只有两个字段。需要注意的是, ACT_ID_MEMBERSHI的两个字段均做了外键约束 写入该表的数据,必须要有用户和用户组数据与之关联。
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
USER_ID | 用户ID | nvarchar(64) | √ | ||
GROUP_ID | 用户组ID | nvarchar(64) | √ |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
USER_ID_ | varchar(64) | NO | |
GROUP_ID_ | varchar(64) | NO |
ACT_ID_USER 用户信息表
用户信息表( act_id_user )
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
REV_ | 乐观锁 | int | √ | 乐观锁Version | |
FIRST_ | 姓 | nvarchar(255) | √ | ||
LAST_ | 名 | nvarchar(255) | √ | ||
EMAIL_ | EMAIL_ | nvarchar(255) | √ | ||
PWD_ | 密码 | nvarchar(255) | √ | ||
PICTURE_ID_ | 图片ID | nvarchar(64) | √ |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
FIRST_ | varchar(255) | YES | 人名 |
LAST_ | varchar(255) | YES | 姓式 |
EMAIL_ | varchar(255) | YES | 用户邮箱 |
PWD_ | varchar(255) | YES | 用户密码 |
PICTURE_ID_ | varchar(64) | YES | 用户图片,对应资源中的数据ID |
流程定义表 (ACT_RE_)
ACT_RE_DEPLOYMENT 流程部署表
部署信息表( act_re_deployment )
部署流程定义时需要被持久化保存下来的信息。
在 Activiti 中,一次部署可以添加多个资源,资源会被保存到资源表中(act_ge_bytearray),而对于部署,则部署信息会被保存到部署表中
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键ID | |
NAME_ | 部署名称 | nvarchar(255) | √ | 部署文件名 | |
CATEGORY_ | 分类 | nvarchar(255) | √ | 类别 | |
DEPLOY_TIME_ | 部署时间 | datetime | √ | 部署时间 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
NAME_ | varchar(255) | YES | 部署的名称 |
CATEGORY_ | varchar(255) | YES | |
KEY_ | varchar(255) | YES | |
TENANT_ID_ | varchar(255) | YES | |
DEPLOY_TIME_ | timestamp | YES | 部署的时间 |
ENGINE_VERSION_ | varchar(255) | YES |
ACT_RE_MODEL 流程设计模型部署表
流程设计模型部署表( act_re_model )
流程设计器设计流程后,保存数据到该表。
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ID_ | |
REV_ | 乐观锁 | int | √ | 乐观锁 | |
NAME_ | 名称 | nvarchar(255) | √ | 名称 | |
KEY_ | KEY_ | nvarchar(255) | √ | 分类 | |
CATEGORY_ | 分类 | nvarchar(255) | √ | 分类 | |
CREATE_TIME_ | 创建时间 | datetime | √ | 创建时间 | |
LAST_UPDATE_TIME_ | 最新修改时间 | datetime | √ | 最新修改时间 | |
VERSION_ | 版本 | int | √ | 版本 | |
META_INFO_ | META_INFO_ | nvarchar(255) | √ | 以json格式保存流程定义的信息 | |
DEPLOYMENT_ID_ | 部署ID | nvarchar(255) | √ | 部署ID | |
EDITOR_SOURCE_VALUE_ID_ | datetime | √ | |||
EDITOR_SOURCE_EXTRA_VALUE_ID_ | datetime | √ |
ACT_RE_PROCDEF 流程定义表
流程定义数据表( act_re_procdef )
业务流程定义数据表。此表和 ACT_RE_DEPLOYMENT 是多对一的关系,即,一个部署的bar包里可能包含多个流程定义文件,每个流程定义文件都会有一条记录在 ACT_REPROCDEF 表内,每个流程定义的数据,都会对于 ACT_GE_BYTEARRAY 表内的一个资源文件和 PNG 图片文件。和 ACT_GE_BYTEARRAY 的关联是通过程序用ACT_GE_BYTEARRAY.NAME 与 ACT_RE_PROCDEF.NAME 完成的,在数据库表结构中没有体现。
Activiti 在部署添加资源时,如果发布部署的文件是流程文件( .bpnn 或者.BPMN 20.xml),
则除了会解析这些流程文件,将内容保存到资源表外,还会解析流程文件的内容,形成特定的流程定义数据,写入流程定义表( ACT_RE_PROCDEF )中
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ID_ | |
REV_ | 乐观锁 | int | √ | 乐观锁 | |
CATEGORY_ | 分类 | nvarchar(255) | √ | 流程定义的Namespace就是类别 | |
NAME_ | 名称 | nvarchar(255) | √ | 名称 | |
KEY_ | 定义的KEY | nvarchar(255) | 流程定义ID | ||
VERSION_ | 版本 | int | 版本 | ||
DEPLOYMENT_ID_ | 部署表ID | nvarchar(64) | √ | 部署表ID | |
RESOURCE_NAME_ | bpmn文件名称 | nvarchar(4000) | √ | 流程bpmn文件名称 | |
DGRM_RESOURCE_NAME_ | png图片名称 | nvarchar(4000) | √ | 流程图片名称 | |
DESCRIPTION_ | 描述 | nvarchar(4000) | √ | 描述 | |
HAS_START_FORM_KEY_ | 是否存在开始节点formKey | tinyint | √ | start节点是否存在formKey 0否 1是 | |
SUSPENSION_STATE_ | 是否挂起 | tinyint | √ | 1 激活 2挂起 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
CATEGORY_ | varchar(255) | YES | 流程定义的分类 |
NAME_ | varchar(255) | YES | 流程定义名称 |
KEY_ | varchar(255) | NO | 流程定义的key |
VERSION_ | int(11) | NO | |
DEPLOYMENT_ID_ | varchar(64) | YES | 流程定义对应的部署数据ID |
RESOURCE_NAME_ | varchar(4000) | YES | 流程定义对应的资源名称 |
DGRM_RESOURCE_NAME_ | varchar(4000) | YES | 流程定义对应的流程图资源名称 |
DESCRIPTION_ | varchar(4000) | YES | |
HAS_START_FORM_KEY_ | tinyint(4) | YES | |
HAS_GRAPHICAL_NOTATION_ | tinyint(4) | YES | |
SUSPENSION_STATE_ | int(11) | YES | 表示流程定义的状态是激活还是终止,1:激活,2:终止,如果流程定义状态是终止状态则不能启动该流程 |
TENANT_ID_ | varchar(255) | YES | |
ENGINE_VERSION_ | varchar(255) | YES |
运行实例表 (ACT_RU_)
运行时数据表用来保存流程在运行过程中所产生的数据,例如流程实例、执行流、任务等
运行时数据表的名称以 ACT_RU 开头,“RU ”是单词 runtime 的缩写。
ACT_RU_EVENT_SUBSCR 事件描述表
事件描述表( act_ru_event_subscr )
如果流程到达某类事件节点, Activiti 会往ACT_RUN_EVENT_SUBSCR表中加入事件描述数据,这些事件描述数据将会决定流程事件的触发。
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | 事件ID | nvarchar(64) | √ | 事件ID | |
REV_ | 版本 | int | √ | 乐观锁Version | |
EVENT_TYPE_ | 事件类型 | nvarchar(255) | 事件类型 | ||
EVENT_NAME_ | 事件名称 | nvarchar(255) | √ | 事件名称 | |
EXECUTION_ID_ | 执行实例ID | nvarchar(64) | √ | 执行实例ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
ACTIVITY_ID_ | 活动实例ID | nvarchar(64) | √ | 活动实例ID | |
CONFIGURATION_ | 配置 | nvarchar(255) | √ | 配置 | |
CREATED_ | 是否创建 | datetime | 默认值 当前系统时间戳CURRENT_TIMESTAMP |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
EVENT_TYPE_ | varchar(255) | NO | 事件类型,不同事件会产生不同类型的事件描述,并不是所有的事件都会产生时间描述 |
EVENT_NAME_ | varchar(255) | YES | 事件名称,在流程文件中定义 |
EXECUTION_ID_ | varchar(64) | YES | 事件所在的执行流ID |
PROC_INST_ID_ | varchar(64) | YES | 时间所在的流程实例ID |
ACTIVITY_ID_ | varchar(64) | YES | 具体事件的ID,在流程文件中定义 |
CONFIGURATION_ | varchar(255) | YES | 事件的配置属性,该字段中有可能存放流程定义ID,执行流ID,或者其他数据 |
CREATED_ | timestamp | NO | |
PROC_DEF_ID_ | varchar(64) | YES | |
TENANT_ID_ | varchar(255) | YES |
ACT_RU_EXECUTION 运行时流程执行实例表
运行时流程执行实例表( act_ru_execution )
流程启动后,会产生一个流程实例,同时会产生相应的执行流,流程实例和执行流数据均
被保存在 ACT_RU_EXECUTION 表中,如果一个流程实例只有一条执行流,那么该表中只产
生一条数据,该数据既表示执行流,也表示流程实例。
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ID_ | |
REV_ | 乐观锁 | int | √ | 乐观锁 | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | 流程实例ID | ||
BUSINESS_KEY_ | 业务主键ID | nvarchar(255) | √ | 业务主键ID | |
PARENT_ID_ | 父节点实例ID | nvarchar(64) | √ | 父节点实例ID | |
PROC_DEF_ID_ | 流程定义ID | nvarchar(64) | √ | 流程定义ID | |
SUPER_EXEC_ | SUPER_EXEC_ | nvarchar(64) | √ | SUPER_EXEC_ | |
ACT_ID_ | 节点实例ID | nvarchar(255) | √ | 节点实例ID即ACT_HI_ACTINST中ID | |
IS_ACTIVE_ | 是否存活 | tinyint | √ | 是否存活 | |
IS_CONCURRENT_ | 是否并行 | tinyint | √ | 是否为并行(true/false) | |
IS_SCOPE_ | IS_SCOPE_ | tinyint | √ | IS_SCOPE_ | |
IS_EVENT_SCOPE_ | IS_EVENT_SCOPE_ | tinyint | √ | IS_EVENT_SCOPE_ | |
SUSPENSION_STATE_ | 是否挂起 | tinyint | √ | 挂起状态 1激活 2挂起 | |
CACHED_ENT_STATE_ | int | √ |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
PROC_INST_ID_ | varchar(64) | YES | 流程实例ID,一个流程实例可能产生多个执行流,该字段表示执行流所属的流程实例 |
BUSINESS_KEY_ | varchar(255) | YES | 启动流程时指定的业务主键 |
PARENT_ID_ | varchar(64) | YES | 父执行流的ID,一个流程实例可能会产生多个执行流,该字段表示父执行ID |
PROC_DEF_ID_ | varchar(64) | YES | 流程定义数据ID |
SUPER_EXEC_ | varchar(64) | YES | |
ROOT_PROC_INST_ID_ | varchar(64) | YES | |
ACT_ID_ | varchar(255) | YES | 当前执行流行为的ID,ID在流程文件中定义 |
IS_ACTIVE_ | tinyint(4) | YES | 该执行流是否活跃的标识 |
IS_CONCURRENT_ | tinyint(4) | YES | 执行流是否正在并行 |
IS_SCOPE_ | tinyint(4) | YES | |
IS_EVENT_SCOPE_ | tinyint(4) | YES | |
IS_MI_ROOT_ | tinyint(4) | YES | |
SUSPENSION_STATE_ | int(11) | YES | 标识流程的中断状态 |
CACHED_ENT_STATE_ | int(11) | YES | |
TENANT_ID_ | varchar(255) | YES | |
NAME_ | varchar(255) | YES | |
START_TIME_ | datetime | YES | |
START_USER_ID_ | varchar(255) | YES | |
LOCK_TIME_ | timestamp | YES | |
IS_COUNT_ENABLED_ | tinyint(4) | YES | |
EVT_SUBSCR_COUNT_ | int(11) | YES | |
TASK_COUNT_ | int(11) | YES | |
JOB_COUNT_ | int(11) | YES | |
TIMER_JOB_COUNT_ | int(11) | YES | |
SUSP_JOB_COUNT_ | int(11) | YES | |
DEADLETTER_JOB_COUNT_ | int(11) | YES | |
VAR_COUNT_ | int(11) | YES | |
ID_LINK_COUNT_ | int(11) | YES |
ACT_RU_IDENTITYLINK 运行时流程人员表
运行时流程人员表( act_ru_identitylink )
任务参与者数据表。主要存储当前节点参与者的信息。
流程与身份关系,用户或者用户组与流程数据之间的关系
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ID_ | |
REV_ | 乐观锁 | int | √ | 乐观锁 | |
GROUP_ID_ | 组ID | nvarchar(64) | √ | 组ID | |
TYPE_ | 类型 | nvarchar(255) | √ | 备注7 | |
USER_ID_ | 用户ID | nvarchar(64) | √ | 用户ID | |
TASK_ID_ | 节点实例ID | nvarchar(64) | √ | 节点实例ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
PROC_DEF_ID_ | 流程定义ID | nvarchar(255) | √ | 流程定义ID |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
GROUP_ID_ | varchar(255) | YES | 该关系数据中的用户组ID |
TYPE_ | varchar(255) | YES | 该关系数据的类型,assignee(指派人或组),candidate(候选人或组),owner(拥有人) |
USER_ID_ | varchar(255) | YES | 关系数据中的用户ID |
TASK_ID_ | varchar(64) | YES | 关系数据中的任务ID |
PROC_INST_ID_ | varchar(64) | YES | 关系数据中的流程实例ID |
PROC_DEF_ID_ | varchar(64) | YES | 关系数据中的流程定义ID |
ACT_RU_JOB 运行时定时任务数据表
运行时定时任务数据表( act_ru_job )
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | 标识 | nvarchar(64) | √ | 标识 | |
REV_ | 版本 | int | √ | 版本 | |
TYPE_ | 类型 | nvarchar(255) | 类型 | ||
LOCK_EXP_TIME_ | 锁定释放时间 | datetime | √ | 锁定释放时间 | |
LOCK_OWNER_ | 挂起者 | nvarchar(255) | √ | 挂起者 | |
EXCLUSIVE_ | bit | √ | |||
EXECUTION_ID_ | 执行实例ID | nvarchar(64) | √ | 执行实例ID | |
PROCESS_INSTANCE_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
PROC_DEF_ID_ | 流程定义ID | nvarchar(64) | √ | 流程定义ID | |
RETRIES_ | int | √ | |||
EXCEPTION_STACK_ID_ | 异常信息ID | nvarchar(64) | √ | 异常信息ID | |
EXCEPTION_MSG_ | 异常信息 | nvarchar(4000) | √ | 异常信息 | |
DUEDATE_ | 到期时间 | datetime | √ | 到期时间 | |
REPEAT_ | 重复 | nvarchar(255) | √ | 重复 | |
HANDLER_TYPE_ | 处理类型 | nvarchar(255) | √ | 处理类型 | |
HANDLER_CFG_ | nvarchar(4000) | √ | 标识 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
TYPE_ | varchar(255) | NO | |
LOCK_EXP_TIME_ | timestamp | YES | |
LOCK_OWNER_ | varchar(255) | YES | |
EXCLUSIVE_ | tinyint(1) | YES | |
EXECUTION_ID_ | varchar(64) | YES | |
PROCESS_INSTANCE_ID_ | varchar(64) | YES | |
PROC_DEF_ID_ | varchar(64) | YES | |
RETRIES_ | int(11) | YES | |
EXCEPTION_STACK_ID_ | varchar(64) | YES | |
EXCEPTION_MSG_ | varchar(4000) | YES | |
DUEDATE_ | timestamp | YES | |
REPEAT_ | varchar(255) | YES | |
HANDLER_TYPE_ | varchar(255) | YES | |
HANDLER_CFG_ | varchar(4000) | YES | |
TENANT_ID_ | varchar(255) | YES |
ACT_RU_TASK 运行时任务节点表
运行时任务节点表( act_ru_task )
流程在运行过程中所产生的任务数据保存在 ACT_RU_TASK 表中
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | ID_ | |
REV_ | 乐观锁 | int | √ | 乐观锁 | |
EXECUTION_ID_ | 执行实例ID | nvarchar(64) | √ | 执行实例ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
PROC_DEF_ID_ | 流程定义ID | nvarchar(64) | √ | 流程定义ID | |
NAME_ | 节点定义名称 | nvarchar(255) | √ | 节点定义名称 | |
PARENT_TASK_ID_ | 父节点实例ID | nvarchar(64) | √ | 父节点实例ID | |
DESCRIPTION_ | 节点定义描述 | nvarchar(4000) | √ | 节点定义描述 | |
TASK_DEF_KEY_ | 节点定义的KEY | nvarchar(255) | √ | 任务定义的ID | |
OWNER_ | 实际签收人 | nvarchar(255) | √ | 拥有者(一般情况下为空,只有在委托时才有值) | |
ASSIGNEE_ | 签收人或委托人 | nvarchar(255) | √ | 签收人或委托人 | |
DELEGATION_ | 委托类型 | nvarchar(64) | √ | 备注8 | |
PRIORITY_ | 优先级别 | int | √ | 优先级别,默认为:50 | |
CREATE_TIME_ | 创建时间 | datetime | √ | 创建时间 | |
DUE_DATE_ | 过期时间 | datetime | √ | 耗时 | |
SUSPENSION_STATE_ | 是否挂起 | int | √ | 1代表激活 2代表挂起 |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
EXECUTION_ID_ | varchar(64) | YES | 任务所在的执行流ID |
PROC_INST_ID_ | varchar(64) | YES | 对应的流程实例ID |
PROC_DEF_ID_ | varchar(64) | YES | 对应流程定义数据ID |
NAME_ | varchar(255) | YES | 任务名称,在流程文件中定义 |
PARENT_TASK_ID_ | varchar(64) | YES | |
DESCRIPTION_ | varchar(4000) | YES | 任务描述,在流程文件中配置 |
TASK_DEF_KEY_ | varchar(255) | YES | 任务定义的ID值,在流程文件中定义 |
OWNER_ | varchar(255) | YES | 任务拥有人,没有做外键关联 |
ASSIGNEE_ | varchar(255) | YES | 被指派执行该任务的人,没有做外键关联 |
DELEGATION_ | varchar(64) | YES | |
PRIORITY_ | int(11) | YES | 任务优先级数值 |
CREATE_TIME_ | timestamp | YES | |
DUE_DATE_ | datetime | YES | 任务预定日期 |
CATEGORY_ | varchar(255) | YES | |
SUSPENSION_STATE_ | int(11) | YES | |
TENANT_ID_ | varchar(255) | YES | |
FORM_KEY_ | varchar(255) | YES | |
CLAIM_TIME_ | datetime | YES |
ACT_RU_VARIABLE 运行时流程变量数据表
运行时流程变量数据表( act_ru_variable )
Activiti 提供了ACT_RU_VAEIABLE表来存放流程中的参数,这类参数包括流程实例参数、执行流参数和任务参数,参数有可能会有多种类型
字段名称 | 字段描述 | 数据类型 | 主键 | 为空 | 取值说明 |
---|---|---|---|---|---|
ID_ | ID_ | nvarchar(64) | √ | 主键标识 | |
REV_ | 乐观锁 | int | √ | 乐观锁 | |
TYPE_ | 类型 | nvarchar(255) | 备注9 | ||
NAME_ | 名称 | nvarchar(255) | 变量名称 | ||
EXECUTION_ID_ | 执行实例ID | nvarchar(64) | √ | 执行的ID | |
PROC_INST_ID_ | 流程实例ID | nvarchar(64) | √ | 流程实例ID | |
TASK_ID_ | 节点实例ID | nvarchar(64) | √ | 节点实例ID(Local) | |
BYTEARRAY_ID_ | 字节表ID | nvarchar(64) | √ | 字节表的ID(ACT_GE_BYTEARRAY) | |
DOUBLE_ | DOUBLE_ | float | √ | 存储变量类型为Double | |
LONG_ | LONG_ | numeric(19) | √ | 存储变量类型为long | |
TEXT_ | TEXT_ | nvarchar(4000) | √ | ‘存储变量值类型为String 如此处存储持久化对象时,值jpa对象的class | |
TEXT2_ | TEXT2_ | nvarchar(4000) | √ | 此处存储的是JPA持久化对象时,才会有值。此值为对象ID |
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
TYPE_ | varchar(255) | NO | 参数类型,该字段值可以为:boolean、bytes、serializable、date、double、integer、jpa-entity、long、null、short、string,这些字段值均为activiti提供,还可以通过扩展来自定义参数类型 |
NAME_ | varchar(255) | NO | 参数名称 |
EXECUTION_ID_ | varchar(64) | YES | 该参数对应的执行ID,NULL |
PROC_INST_ID_ | varchar(64) | YES | 该参数对应的流程实例ID,NULL |
TASK_ID_ | varchar(64) | YES | 如果该参数是任务参数,就需要设置任务ID |
BYTEARRAY_ID_ | varchar(64) | YES | 如果参数值是序列化对象,那么可以将该对象作为资源保存到资源表中,该字段保存资源表中的数据ID |
DOUBLE_ | double | YES | 参数类型为double的话则值会保存在该字段中 |
LONG_ | bigint(20) | YES | 参数类型为long的话则值会保存在该字段中 |
TEXT_ | varchar(4000) | YES | 保存文本类型的参数 |
TEXT2_ | varchar(4000) | YES |
工作数据表(以下表结构暂时未整理)
ACT_RU_DEADLETTER 无法执行工作表
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
TYPE_ | varchar(255) | NO | |
EXCLUSIVE_ | tinyint(1) | YES | |
EXECUTION_ID_ | varchar(64) | YES | |
PROCESS_INSTANCE_ID_ | varchar(64) | YES | |
PROC_DEF_ID_ | varchar(64) | YES | |
EXCEPTION_STACK_ID_ | varchar(64) | YES | |
EXCEPTION_MSG_ | varchar(4000) | YES | |
DUEDATE_ | timestamp | YES | |
REPEAT_ | varchar(255) | YES | |
HANDLER_TYPE_ | varchar(255) | YES | |
HANDLER_CFG_ | varchar(4000) | YES | |
TENANT_ID_ | varchar(255) | YES |
ACT_RU_SUSPENDED 中断工作表
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
TYPE_ | varchar(255) | NO | |
EXCLUSIVE_ | tinyint(1) | YES | |
EXECUTION_ID_ | varchar(64) | YES | |
PROCESS_INSTANCE_ID_ | varchar(64) | YES | |
PROC_DEF_ID_ | varchar(64) | YES | |
RETRIES_ | int(11) | YES | |
EXCEPTION_STACK_ID_ | varchar(64) | YES | |
EXCEPTION_MSG_ | varchar(4000) | YES | |
DUEDATE_ | timestamp | YES | |
REPEAT_ | varchar(255) | YES | |
HANDLER_TYPE_ | varchar(255) | YES | |
HANDLER_CFG_ | varchar(4000) | YES | |
TENANT_ID_ | varchar(255) | YES |
ACT_RU_TIMER_JOB 定时器工作表
字段 | 类型 | NULL | 备注 |
---|---|---|---|
ID_ | varchar(64) | NO | |
REV_ | int(11) | YES | |
TYPE_ | varchar(255) | NO | |
LOCK_EXP_TIME_ | timestamp | YES | |
LOCK_OWNER_ | varchar(255) | YES | |
EXCLUSIVE_ | tinyint(1) | YES | |
EXECUTION_ID_ | varchar(64) | YES | |
PROCESS_INSTANCE_ID_ | varchar(64) | YES | |
PROC_DEF_ID_ | varchar(64) | YES | |
RETRIES_ | int(11) | YES | |
EXCEPTION_STACK_ID_ | varchar(64) | YES | |
EXCEPTION_MSG_ | varchar(4000) | YES | |
DUEDATE_ | timestamp | YES | |
REPEAT_ | varchar(255) | YES | |
HANDLER_TYPE_ | varchar(255) | YES | |
HANDLER_CFG_ | varchar(4000) | YES | |
TENANT_ID_ | varchar(255) | YES |
其他
ACT_EVT_LOG 事件日志表
Activiti 事件日志表( act_evt_log )
字段名称 | 字段描述 | 数据类型 | 为空 | 取值说明 |
---|---|---|---|---|
LOG_NR_ | 主键 | bigint(20) | NO | 自增长 |
TYPE_ | 类型 | varchar(64) | YES | 类型 |
PROC_DEF_ID_ | 流程定义ID | varchar(64) | YES | 流程定义ID |
PROC_INST_ID_ | 流程实例ID | varchar(64) | YES | 流程实例ID |
EXECUTION_ID_ | 执行实例ID | varchar(64) | YES | 执行实例ID |
TASK_ID_ | 节点实例ID | varchar(64) | YES | 节点实例ID |
TIME_STAMP_ | 时间戳 | timestamp(3) | NO | 时间戳 |
USER_ID_ | 用户ID | varchar(255) | YES | 用户ID |
DATA_ | 一些额外参数 | longblob | YES | 例如 IP地址 等。 |
LOCK_OWNER_ | 挂起者 | varchar(255) | YES | 暂时没有用到 |
LOCK_TIME_ | 挂起时间 | timestamp(3) | YES | 暂时没有用到 |
IS_PROCESSED_ | 是否处理过了 | tinyint(4) | YES | 暂时没有用到 |
ACT_PROCDEF_INFO 流程定义扩展表
关联ACTGE_BYTEARRAY与PROC_DEF_ID表。
字段名称 | 字段描述 | 数据类型 | 为空 | 取值说明 |
---|---|---|---|---|
ID_ | 主键 | varchar(64) | NO | 主键ID |
PROC_DEF_ID_ | 流程定义ID | varchar(64) | NO | 流程定义ID |
REV_ | 乐观锁 | int(11) | YES | 默认值 NULL,version版本 |
INFO_JSON_ID_ | 主键 | varchar(64) | YES | ACT_GE_BYTEARRAY的ID |
DMN规则引擎数据表
决策部署表,act_dmn_deployment
保存决策数据,类似于流程定义部署,每一次部署,可以添加多份决策文件,向部署表中写入一条部署数据。
只启动流程引擎,并不会创建规则引擎表。
字段 | 注释 |
---|---|
NAME_ | 部署名称 |
CATEGORY_ | 部署的目录名称 |
PARENT_DEPLOYMENT_ID_ | 父部署ID |
决策表,act_dmn_decision_table
可以先将决策看做流程定义,决策文件中保存着决策表,部署时会解析决策文件中的决策模型并将其保存到act_dmn_decision_table中。
字段 | 注释 |
---|---|
KEY_ | 决策业务主键 |
DEPLOYMENT_ID_ | 所属的部署数据ID |
部署资源表,act_dmn_deployment_resource
规则引擎相关的资源,例如决策文件、图片等,被保存在act_dmn_deployment_resource表中,该表类似于流程引擎的资源表。
字段 | 注释 |
---|---|
NAME_ | 资源名称 |
DEPLOYMENT_ID_ | 所属的部署数据ID |
RESOURCE_BYTES_ | 资源内容,longblob类型。 |
结语
Activiti6.0的表相较于Activiti5有所不同,但核心的内容实质上还是一样,数据库这一块没有太大差别,对于这么多表,大体过一遍有个印象即可,Activiti6.0的重点仍在核心API。
Activiti 依赖事务监听器(上)
Activiti 依赖事务监听器
Activiti5.x
Activiti5.x版本中可以通过监听器辅助自身的业务操作,比如自定义一个执行监听器,则只需要定义一个类,然后实现org.activiti.engine.delegate.ExecutionListener接口即可。任务监听器需要实现org.activiti.engine.delegate.TaskListene。当然也可以自定义表达式以及委托表达式的方式实现。
当监听器被引擎触发的时候,会自动触发所有不同类型的听众(自定义监听器、内置监听器、历史监听器等)。但是自定义的监听器如果出现异常或者错误,那么这些监听器的结果并不依赖事务,通俗一点的描述就是事务回滚之后监听器会重复的执行,这就完全不对了。用下面的例子对其进行说明:
上图的流程文档如下所示:
<process id="bookflight" name="Book Flight" isExecutable="true">
<startEvent id="start-event-1" />
<sequenceFlow id="flow1" sourceRef="start-event-1" targetRef="book-flight" />
<serviceTask id="book-flight" name="Book flight" activiti:delegateExpression="${bookFlightBean}" activiti:async="true">
<extensionElements>
<activiti:executionListener event="end" delegateExpression="${emailBean}"/>
</extensionElements>
</serviceTask>
<sequenceFlow id="flow2" sourceRef="book-flight" targetRef="charge-credit-card">
</sequenceFlow>
<serviceTask id="charge-credit-card" name="Charge credit card" activiti:delegateExpression="${chargeCCBean}">
</serviceTask>
<sequenceFlow id="flow3" sourceRef="charge-credit-card" targetRef="do-something-else" />
<serviceTask id="do-something-else" name="Do something else" activiti:delegateExpression="${doSomethingBean}" activiti:async="true">
</serviceTask>
<sequenceFlow id="flow4" sourceRef="do-something-else" targetRef="end-event-1" />
<endEvent id="end-event-1" />
</process>
假设我们想发送电子邮件预订航班时,信用卡被指控成功。这当然可以以不同的方式来完成的。但是对于本例的缘故我们会通过实现执行侦听器和配置它的‘结束’事件的书飞行活动。
部署并启动上述的流程文档,活动成功。配置的执行侦听器会实现触发和发送电子邮件。
然后收取信用卡的活动执行。这导致一个例外。事务回滚和“书飞行”和“收取信用卡”将再次执行。这意味着我们执行侦听器也将再次执行。
这只是一个简单的例子。并且有许多用例中,您想要侦听器每次执行。但在有些情况下,比如在上面的示例中需要有可能让监听器基于整个事务的结果。比如事务成功,则监听器中的业务逻辑触发,事务失败则监听器中的业务逻辑不应该被触发。
Activiti 6.x
Activiti6中已经提供了事务监听器,以下代码片段显示了一个适应上面的例子,现在与事务相关的执行侦听器配置。
<serviceTask id="book-flight" name="Book flight" activiti:delegateExpression="${bookFlightBean}" activiti:async="true">
<extensionElements>
<activiti:executionListener event="end" delegateExpression="${emailBean}" onTransaction="committed" />
</extensionElements>
</serviceTask>
上述代码中,可以通过设置executionListener元素中的onTransaction属性进行事务状态的的定义,即事务的状态监听器的执行以来事务的状态。onTransaction属性可以有如下三个值:
· Committed(提交)
· rolled-back(回滚)
· before-commit(提交前)
注意:Activiti5.X设计器不支持设置onTransaction属性的设置。
新接口
依赖事务执行监听器
依赖事务监听器必须实现一个不同的接口而不是“普通”执行侦听器。TransactionDependentExecutionListener接口提供了一种方法,需要自行实现。该接口的定义如下所示:
public interface TransactionDependentExecutionListener extends BaseExecutionListener {
String ON_TRANSACTION_BEFORE_COMMIT = "before-commit";
String ON_TRANSACTION_COMMITTED = "committed";
String ON_TRANSACTION_ROLLED_BACK = "rolled-back";
void notify(String processInstanceId, String executionId, FlowElement flowElement,
MapexecutionVariables, MapcustomPropertiesMap);
}
依赖事务任务监听器
对于依赖事务任务监听器TransactionDependentTaskListener接口的定义如下:
public interface TransactionDependentTaskListener extends BaseTaskListener {
String ON_TRANSACTION_COMMITTING = "before-commit";
String ON_TRANSACTION_COMMITTED = "committed";
String ON_TRANSACTION_ROLLED_BACK = "rolled-back";
void notify(String processInstanceId, String executionId, Task task, MapexecutionVariables, MapcustomPropertiesMap);
}
对于最后一个输入参数customPropertiesMap,他是可选的。可以提供一个configureable属性解析器。(参考下文的ReceiveTask示例)。
Activiti 依赖事务监听器(下)
Activiti 依赖事务监听器
Activiti依赖事务监听器(上)讲解了Activiti依赖事务监听器的概念,接下来看一下该如何使用TransactionDependentExecutionListener。
当引擎解析BPMN XML模型的Java模型的“常规”执行/任务听众会转换为ActivitiListeners事务相关的监听器。如果引擎操作的执行期间,遇到ActivitiListener要检查它是否一个事务依赖。如果是这样,它不执行它那一刻,就像它与其他听众,它计划为以后处理。
这种延迟处理需要拍摄快照依此当前的执行状态,因为事情可以改变实际执行侦听器之前。甚至更重要的是,执行本身执行侦听器时不再可用。
(关于这一点涉及到了源码的讲解,后续章节会详细的讲解)。
让我们看一下如何使用该接口。
首先定义一个流程文档,该流程文档对应的图如下所示:
上图对应的流程文档XML内容如下所示:
<process id="transaction-dependent-listeners">
<startEvent id="start">
<extensionElements>
<activiti:executionListener delegateExpression="${myActivityLogger}" event="start" />
</extensionElements>
</startEvent>
<sequenceFlow id="flow1" sourceRef="start" targetRef="script-task-1"/>
<scriptTask id="script-task-1" name="Script Task One" activiti:async="true" scriptFormat="groovy">
<extensionElements>
<activiti:executionListener delegateExpression="${myActivityLogger}" event="start" />
<activiti:executionListener delegateExpression="${myMessageProducer}" event="start" onTransaction="committed" />
</extensionElements>
<script>
println 'script task one; start new transaction'
</script>
</scriptTask>
<sequenceFlow id="flow2" sourceRef="script-task-1" targetRef="receive-task-1"/>
<receiveTask id="receive-task-1" name="Wait">
<extensionElements>
<activiti:executionListener delegateExpression="${myActivityLogger}" event="start" />
</extensionElements>
</receiveTask>
<sequenceFlow id="flow3" sourceRef="receive-task-1" targetRef="script-task-2"/>
<scriptTask id="script-task-2" name="Script Task Two" activiti:async="true" scriptFormat="groovy">
<extensionElements>
<activiti:executionListener delegateExpression="${myActivityLogger}" event="start" />
</extensionElements>
<script>
println 'script task two; start new transaction'
</script>
</scriptTask>
<sequenceFlow id="flow4" sourceRef="script-task-2" targetRef="end"/>
<endEvent id="end">
<extensionElements>
<activiti:executionListener delegateExpression="${myActivityLogger}" event="start" />
</extensionElements>
</endEvent>
</process>
当Activiti遇到ReceiveTask执行进入等待状态。这意味着流程实例逗留在在ACT_RU_EXECUTION表中。
假设在上面的示例脚本任务一个的将JMS队列上的消息。和消费者的消息将会发送一个信号流程实例(继续从等待状态)。如果发送信号发生的非常快,等待状态尚未就可能依然存在。这种情况是例外,但它可能发生。
下面看一下JMS的配置:
生产者:
/**
* @author www.shareniu.com
*/
@Component("myMessageProducer")
public class MyMessageProducer implements TransactionDependentExecutionListener {
private static final Logger logger = LoggerFactory.getLogger(MyMessageProducer.class);
@Autowired
private JmsTemplate jmsTemplate;
public void notify(String processInstanceId, String executionId, FlowElement currentFlowElement, MapexecutionVariables, MapcustomPropertiesMap) {
logger.debug("Sending message <{}> to queue", executionId);
jmsTemplate.convertAndSend("receive_task_signal", executionId);
}
}
消费者:
/**
* @author www.shareniu.com
*/
@Component
public class MyMessageConsumer {
private static final Logger logger = LoggerFactory.getLogger(MyMessageConsumer.class);
@Autowired
private RuntimeService runtimeService;
@JmsListener(destination = "receive_task_signal", containerFactory = "myFactory")
public void receiveMessage(String executionId) {
logger.debug("Received message: <" + executionId + ">");
logger.debug("Signaling execution with id: <" + executionId + ">");
runtimeService.trigger(executionId);
}
}
在上面的示例脚本任务一个只是一个占位符标记的开始一个事务。这同样适用于“脚本任务两个”。的脚本任务一个和接收任务“等待”是相同的事务的一部分。这意味着尽管myMessageProducer的侦听器配置的脚本任务一个侦听器将在“等待”是坚持执行。
运行上述的代码,执行如下的脚本即可。
mvn spring-boot:run
控制台的输出如下:
2017-02-07 15:18:52.229 DEBUG 1243900 --- [cTaskExecutor-1] o.a.demo.listener.MyActivityLogger : Current activity id:
script task one; start new transaction
2017-02-07 15:18:52.653 DEBUG 1243900 --- [cTaskExecutor-1] o.a.demo.listener.MyActivityLogger : Current activity id:
2017-02-07 15:18:52.657 DEBUG 1243900 --- [cTaskExecutor-1] o.a.demo.listener.MyMessageProducer : Sending message <5> to queue
2017-02-07 15:18:52.689 DEBUG 1243900 --- [enerContainer-1] org.activiti.demo.jms.MyMessageConsumer : Received message: <5>
2017-02-07 15:18:52.689 DEBUG 1243900 --- [enerContainer-1] org.activiti.demo.jms.MyMessageConsumer : Signaling execution with id: <5>
2017-02-07 15:18:52.696 DEBUG 1243900 --- [cTaskExecutor-2] o.a.demo.listener.MyActivityLogger : Current activity id:
script task two; start new transaction
2017-02-07 15:18:52.703 DEBUG 1243900 --- [cTaskExecutor-2] o.a.demo.listener.MyActivityLogger : Current activity id:
Activiti6 特性
了解 Activiti6 新增特性
本文重点分析 Activiti6
新增的一些特性,从而可以更好的了解 Activiti6
的走向。
Activiti6最大的变化点就是对代码进行了重构,该版本修复以往的Bug并不多,但内部实现相对来说变化比较大。其突出的变化如下所示:
-
新增两款新引擎,Form引擎和DMN引擎(动态引擎)。其中DMN引擎允许开发人员创建自己的决策表。可以通过变量和定义的规则方式从决策表中计算结果。这些决策表的数据可以被rule task调用,决策表与流程实例是完全隔离的,相互之间不需要知道对方的存在。Form引擎可以通过Activiti6 UI界面进行配置,通俗一点的理解就是Activiti6将Form表单独立出来了。Form表单信息可以以JSON格式进行定义和使用。Activiti6 UI 默认包括新的规则引擎和表单引擎。
-
新增ad-hoc子流程。可以参考文章(ad-hoc子流程使用)。
-
作业执行器被重构。Activiti6版本仅保留了Activiti5版本中的异步作业执行器(async executor)。定时作业被划分了四个不同的表:executable jobs, timer jobs, suspended jobs 和deadletter jobs。引擎可以更快的执行作业。定时器作业在新的版本存储于单独的表中,一个线程会定时轮训需要执行的作业,快到期的作业会被添加到suspended jobs表中。重试的作业已经被干掉了,需要重试的作业会被添加到deadletter jobs表中。这样的重构意义主要是为了提高查询效率,可以执行的作业可以很快的被查询出来。
-
作业执行器通过消息队列的方式进行,关于这一点可以参考随后的文章。
-
瞬态变量的引入。瞬态变量不会存储到 Activiti 变量表中,但仍为单一的事务持续时间执行。比如可以在REST服务之间进行调用的时候使用,或者使用于Java service task。
-
引入了事务依赖监听器,可以参考Activiti 依赖事务监听器(上)的讲解。
-
Activiti 6 UI 程序中,添加了DMN编辑器。
-
对于多实例节点而言,添加了终止多实例节点的相关方法。这个特性允许开发人员使用API结束多实例所有节点的执行。
-
优化补偿活动行为和在子流程中的使用。
-
在运行流程实例以及执行实例中添加了开始时间以及启动流程实例的人字段。在ru_task中增加了任务的认领时间(claim time)。
-
妥善解决数据库架构 (oracle/postgres) 的使用。
-
修复历史数据捕获。
-
大量重构 Activiti 6 UI 应用程序,例如应用程序中定义现在部署作为正常的活动部署,没有为其单独的应用程序定义表。
-
改进Activiti 6 QA中的问题。
Activiti 6.0 工作流入门
Activiti 6.0 工作流入门学习
工作流介绍
工作流: 是对工作流程及其各操作步骤之间业务规则的抽象、概括描述
工作流建模: 即将工作流程中的工作如何前后组织在一起的逻辑和规则,在计算机中以恰当的模型表达并对其实施计算
要解决的问题: 是为实某个业务目标,利用 计算机在多个参与者之间按某种预定规则自动传递文档、信息或者任务
- ProcessEnigne 流程引擎
- RepositoryService 流程仓库Service,可以管理流程仓库例如部署删除读取流程资源
- RuntimeService 运行时Service可以处理所有运行状态的流程实例流程控制(开始,暂停,挂起等)
- TaskService 任务Service用于管理、查询任务,例如签收、办理、指派等
- IdentitiServicec 身份Service可以管理查询用户、组之间的关系
- FormService 表单Service用于读取和流程、任务相关的表单数据
- HistoryService 历史Service用于查询所有的历史数据
- ManagementService 引擎管理Service,和具体业务无关,主要查询引擎配置,数据库作业
- DynamicBpmService 动态bpm服务
数据模型设计
数据表分类 | 描述 |
---|---|
ACT_GE_* | 通用数据表 |
ACT_RE_* | 流程定义存储表 |
ACT_ID_* | 身份信息表 |
ACT_RU_* | 运行时数据表 |
ACT_HI_* | 历史数据表 |
BPM2.0元素
- 流对象(FlowObject)
- 链接对象(ConnectingObject)
- 数据(Data)
- 泳道(Swimlanes)
- 描述对象(Artifacts)
审批流程模型化
购物工作流程模型化
部署Activiti
准备环境:
- Activiti User Guide
- Quick Start Guide
- Java Docs
- Activiti软件包activiti-6.0.0.zip
- java环境1.8
- tomcat
-
压缩activiti-6.0.0.zip找到wars把里面的activiti-admin.war,activiti-app.war复制到tomcat的webapps下面并启动tomcat
-
浏览器打开访问地址http://127.0.0.1:8080/activiti-app/账号amdin密码test
流程设计体验
创建用户
创建了三个用户
创建流程
画流程图并指定user
选择对应的用户
创建app
选择创建的流程保存
流程执行
切换userdev账号启动流程
切换到usertl账号进行审批
切换到userhr账号审批
登录管理员后台查看
登录http://127.0.0.1:8080/activiti-admin用管理员账号登录账号密码都是admin
修改端口号跟activiti-app项目的端口号一致这里是8080
Activiti 6.0 源码分析 helloword
Activiti 6.0 源码分析 helloword 学习
获取源码
- 然后从自己目录下把项目克隆到本地
- 切换分支>git checkout -b study6 activiti-6.0.0
- 编译>mvn clean test-compile
- 导入到编辑器
Activiti6.0模块介绍
- module/activiti-engine 核心模块
- module/activiti-spring Spring集成模块
- module/activiti-sping-boot SpringBoot集成模块
- module/activiti-rest 对外提供rest api模块
- module/activiti-form-engine 表单引擎模块
- module/activiti-ldap 集成ldap用户的模块
基于源码activiti-app运行
启动cativiti-app
cd modules/activiti-ui/activiti-app
mvn clean tomcat7:run
打开浏览器访问http://127.0.0.1:9999/activiti-app/
activiti-ui
activiti-app 集成发布的war工程
activiti-app-conf UI独立于业务外的配置
activiti-app-logic UI的业务逻辑
activiti-app-rest 提供接口的rest api
Activiti6.0初体验helloword
通过activiti-app画好流程图并设置属性
- id:startEvent | name:开始
- id:submitForm | name:填写审批信息
- id:decideSubmit | name:提交OR取消
- id:endEventCancel | name:取消
- id:tl_approve | name:主管审批
- id:decideTLApprove | name:主管审批校验
- id:hr_approve | name:人事审批
- id:decideHRApprove | name:人事审批校验
- id:endEvent |name:结束
设置form表单信息
填写审批信息表单
主管审批表单
hr审批表单
设置流转条件
提交or取消:
${submitType=="y"||submitType=="Y"}
${submitType=="n"||submitType=="N"}
主管审批校验:
${tlApprove=="y"||tlApprove=="Y"}
${tlApprove=="n"||tlApprove=="N"}
人事审批校验:
${hrApprove=="y"||hrApprove=="Y"}
${hrApprove=="n"||hrApprove=="N"}
导出工作流xml文件创建demoActiviti项目
- 把导入文件放入项目里
- 引入jar包
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.guosh.activiti</groupId>
<artifactId>guosh-activiti</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--activiti核心模块-->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.11</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<!--h2内存数据库-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.176</version>
</dependency>
</dependencies>
</project>
- 流程demo
public class DemoMain {
private static final Logger logger= LoggerFactory.getLogger(DemoMain.class);
public static void main(String[] args) throws ParseException {
logger.info("启动程序");
//创建流程引擎(基于内存数据库的流程引擎对象)
ProcessEngine processEngine = getProcessEngine();
//部署流程定义文件
ProcessDefinition processDefinition = getProcessDefinition(processEngine);
//启动流程
ProcessInstance processInstance = getProcessInstance(processEngine, processDefinition);
//处理流程任务
processTask(processEngine, processInstance);
logger.info("结束程序");
}
private static void processTask(ProcessEngine processEngine, ProcessInstance processInstance) throws ParseException {
Scanner scanner = new Scanner(System.in);
//流程不为空并且流程没有结束
while (processInstance!=null && !processInstance.isEnded()){
//任务Service用于管理、查询任务,例如签收、办理、指派等
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery().list();
for (Task task:list) {
logger.info("待处理任务 [{}]",task.getName());
Map<String, Object> variables = getStringObjectMap(processEngine, scanner, task);
//提交
taskService.complete(task.getId(),variables);
//获取流程对象最新状态
processInstance=processEngine.getRuntimeService().createProcessInstanceQuery()
.processInstanceId(processInstance.getId()).singleResult();
}
logger.info("待处理任务数量 [{}]",list.size());
}
scanner.close();
}
private static Map<String, Object> getStringObjectMap(ProcessEngine processEngine, Scanner scanner, Task task) throws ParseException {
//表单Service用于读取和流程、任务相关的表单数据
FormService formService = processEngine.getFormService();
//获取任务表单
TaskFormData taskFormData = formService.getTaskFormData(task.getId());
//获取属性集合
List<FormProperty> formProperties = taskFormData.getFormProperties();
//存储要提交的表单
Map<String, Object> variables=new HashMap<String, Object>();
String line=null;
for (FormProperty property:formProperties){
//如果是string类型
if(StringFormType.class.isInstance(property.getType())){
logger.info("请输入 {}",property.getName());
line=scanner.nextLine();
logger.info("您输入的内容是 [{}]",line);
variables.put(property.getId(),line);
}else if(DateFormType.class.isInstance(property.getType())){
logger.info("请输入 {} 格式 (yyyy-MM-dd)",property.getName());
line=scanner.nextLine();
SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd");
Date date = dateFormat.parse(line);
variables.put(property.getId(),date);
}else{
logger.info("类型不支持");
}
}
return variables;
}
private static ProcessInstance getProcessInstance(ProcessEngine processEngine, ProcessDefinition processDefinition) {
//运行时Service可以处理所有运行状态的流程实例流程控制(开始,暂停,挂起等)
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId());
logger.info("启动流程[{}]",processInstance.getProcessDefinitionKey());
return processInstance;
}
private static ProcessDefinition getProcessDefinition(ProcessEngine processEngine) {
//流程仓库Service,可以管理流程仓库例如部署删除读取流程资源
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("second_approve.bpmn20.xml")
.deploy();
//部署id
String deploymentId = deployment.getId();
//流程对应对象
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deploymentId)
.singleResult();
logger.info("流程名称 [{}],流程ID [{}],流程KEY [{}]",processDefinition.getName(),processDefinition.getId(),processDefinition.getKey());
return processDefinition;
}
private static ProcessEngine getProcessEngine() {
ProcessEngineConfiguration cfg = ProcessEngineConfiguration.createStandaloneInMemProcessEngineConfiguration();
//构造流程引擎
ProcessEngine processEngine = cfg.buildProcessEngine();
String name = processEngine.getName();
String version = processEngine.VERSION;
logger.info("流程引擎名称 [{}],版本 [{}]",name,version);
return processEngine;
}
}
Activiti 框架原理
本文基于一个简单的Demo流程介绍了Activiti框架启动、部署、运行过程。
Demo准备
流程图文件:
<?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:activiti="http://activiti.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.activiti.org/test">
<process id="hello" name="hello" isExecutable="true">
<!-- 流程开始节点 -->
<startEvent id="start" name="Start" ></startEvent>
<!-- serviceTask:执行me.likeyao.activiti.demo.HelloWorld的execute方法,打印hello world -->
<serviceTask id="helloworld" name="helloworld" activiti:class="me.likeyao.activiti.demo.HelloWorld"/>
<!-- 流程结束节点 -->
<endEvent id="end" name="End"></endEvent>
<!-- 流程迁移线:开始节点到serviceTask节点 -->
<sequenceFlow id="sid-1" sourceRef="start" targetRef="helloworld"></sequenceFlow>
<!-- 流程迁移线:serviceTask节点到结束节点 -->
<sequenceFlow id="sid-3" sourceRef="helloworld" targetRef="end"></sequenceFlow>
</process>
</definitions>
代码:
public class App {
public static void main(String[] args) {
//创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//部署流程图
processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();
//发起流程
processEngine.getRuntimeService().startProcessInstanceByKey("hello");
}
}
public class HelloWorld implements JavaDelegate{
public void execute(DelegateExecution execution) throws Exception {
System.out.println("Hello world!");
}
}
Demo实现的功能是发起一个流程,执行到流程的serviceTask节点时,打印Hello world!,然后流程结束。
源码版本:5.22.0
框架初始化
ProcessEngine类图
ProcessEngine
ProcessEngine是Activiti框架的门面,ProcessEngine本身不提供任何功能,通过getXXXService方法可以获取到对应的Service对象执行操作。Demo中涉及到的两个Service:
- RepositoryService:流程定义和流程部署相关功能。
- RuntimeService:流程实例相关功能(发起流程、获取流程实例变量)。
ProcessEngineConfiguration
ProcessEngineConfiguration负责Activiti框架的属性配置、初始化工作,初始化入口是buildProcessEngine方法,所有Activiti框架运行时需要用到的组件基本都在这里初始化:
public ProcessEngine buildProcessEngine() {
init();
return new ProcessEngineImpl(this);
}
protected void init() {
initConfigurators();
configuratorsBeforeInit();
initProcessDiagramGenerator();
initHistoryLevel();
initExpressionManager();
initDataSource();
initVariableTypes();
initBeans();
initFormEngines();
initFormTypes();
initScriptingEngines();
initClock();
initBusinessCalendarManager();
initCommandContextFactory();
initTransactionContextFactory();
initCommandExecutors();
initServices();
initIdGenerator();
initDeployers();
initJobHandlers();
initJobExecutor();
initAsyncExecutor();
initTransactionFactory();
initSqlSessionFactory();
initSessionFactories();
initJpa();
initDelegateInterceptor();
initEventHandlers();
initFailedJobCommandFactory();
initEventDispatcher();
initProcessValidator();
initDatabaseEventLogging();
configuratorsAfterInit();
}
这里有一个扩展点:ProcessEngineConfigurator。
public interface ProcessEngineConfigurator {
//组件初始化前
void beforeInit(ProcessEngineConfigurationImpl processEngineConfiguration);
//组件初始化后
void configure(ProcessEngineConfigurationImpl processEngineConfiguration);
//优先级
int getPriority();
}
在init初始化方法中,initConfigurators方法通过ServiceLoader加载ProcessEngineConfigurator。随后在configuratorsBeforeInit和configuratorsAfterInit方法中分别调用ProcessEngineConfigurator的beforeInit和configure方法,使用户可以在ProcessEngineConfiguration初始化前后编程式的修改属性,替换Activiti默认组件。
流程部署
流程部署实现的功能是将xml格式的流程图,转化为Activiti框架运行时依赖的流程定义对象。
RepositoryService
Demo中通过以下代码部署了一个流程:
processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();
createDeployment方法中创建了DeploymentBuilder对象,DeploymentBuilder对象负责读取指定路径的流程图xml文件的内容(byte数组),并缓存在DeploymentEntity对象中:
public DeploymentBuilder addInputStream(String resourceName, InputStream inputStream) {
...
byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
ResourceEntity resource = new ResourceEntity();
resource.setName(resourceName);
resource.setBytes(bytes);
deployment.addResource(resource);
return this;
}
最终DeploymentBuilder的deploy方法会调用RepositoryService的deploy方法,完成流程部署:
public Deployment deploy() {
return repositoryService.deploy(this);
}
CommandExecutor
在RepositoryService的deploy方法中,使用了CommandExecutor对象:
public Deployment deploy(DeploymentBuilderImpl deploymentBuilder) {
return commandExecutor.execute(new DeployCmd<Deployment>(deploymentBuilder));
}
在Activiti中,大部分操作都以Command模式实现,例如部署流程图的DeployCmd。CommandExecutor封装了一系列的CommandInterceptor,在内部形成CommandInterceptor链,在命令执行前后做了拦截。Activiti框架提供了一些
CommandInterceptor实现:
名称 | 作用 |
---|---|
CommandContextInterceptor | 用于生成命令执行的上下文(CommandContext)。 |
LogInterceptor | 开启日志Debug级别后,打印日志。 |
JtaTransactionInterceptor | 开启Jta事务 |
引入activiti-spring包,通过SpringTransactionInterceptor引入Spring的事务支持。
CommandExecutor在ProcessEngineConfigurationImpl的initCommandExecutors方法中初始化:
protected void initCommandExecutors() {
initDefaultCommandConfig();
initSchemaCommandConfig();
initCommandInvoker();
initCommandInterceptors();
initCommandExecutor();
}
可以设置ProcessEngineConfigurationImpl的customPreCommandInterceptors和customPostCommandInterceptors属性,添加自定义的CommandInterceptor:
protected void initCommandInterceptors() {
if (commandInterceptors==null) {
commandInterceptors = new ArrayList<CommandInterceptor>();
if (customPreCommandInterceptors!=null) {
commandInterceptors.addAll(customPreCommandInterceptors);
}
commandInterceptors.addAll(getDefaultCommandInterceptors());
if (customPostCommandInterceptors!=null) {
commandInterceptors.addAll(customPostCommandInterceptors);
}
commandInterceptors.add(commandInvoker);
}
}
这里的pre和post是指Activiti框架getDefaultCommandInterceptors()的前后。
CommandInvoker是CommandInterceptor链的最后一个对象,负责调用Command:
public class CommandInvoker extends AbstractCommandInterceptor {
@Override
public <T> T execute(CommandConfig config, Command<T> command) {
return command.execute(Context.getCommandContext());
}
}
CommandContext
CommandContext是Activit框架Command执行的上下文,主要包含各种SessionFactory:
sessionFactories = processEngineConfiguration.getSessionFactories();
SessionFactory负责生成Session,Session是Activiti操作持久化对象的统一接口:
名称 | 作用 |
---|---|
ProcessDefinitionEntityManager | 流程定义相关读写操作。 |
ExecutionEntityManager | 流程实例相关读写操作。 |
DefaultHistoryManager | 历史记录相关读写操作 |
CommandContext的生命周期
CommandConext在CommandContextInterceptor中创建,在finally代码块中销毁:
public <T> T execute(CommandConfig config, Command<T> command) {
//首先尝试从线程上下文的栈中获取CommandContext
CommandContext context = Context.getCommandContext();
boolean contextReused = false;
//什么时候创建新的CommandContext?
//1、CommandConfig中指定了不复用CommandContext
//2、当前线程上下文中不存在CommandConext
//3、当前线程上下文中的CommandConext已经抛出异常
if (!config.isContextReusePossible() || context == null || context.getException() != null) {
context = commandContextFactory.createCommandContext(command); }
else {
contextReused = true;
}
try {
//将前面获取到的CommandContext入栈
Context.setCommandContext(context);
Context.setProcessEngineConfiguration(processEngineConfiguration);
//执行下一个interceptor,在CommandInvoker中可以通过Context.getCommandContext()获取线程上下文中的CommandContext
return next.execute(config, command);
} catch (Exception e) {
//记录异常信息
context.exception(e);
} finally {
try {
//如果CommandContext不可复用,用完直接关闭
if (!contextReused) {
context.close();
}
} finally {
//出栈操作
Context.removeCommandContext();
Context.removeProcessEngineConfiguration();
Context.removeBpmnOverrideContext();
}
}
return null;
}
Activiti的框架可以在一个Command的执行过程中,调用另外一个Command,所以会出现是否需要复用CommandContext的选项,默认值为true。
流程的解析
在DeployCmd中,首先调用DeploymentEntityManager持久化存储DeploymentEntity对象:
commandContext.getDeploymentEntityManager().insertDeployment(deployment);
然后调用DeploymentManager部署流程(流程解析):
commandContext.getProcessEngineConfiguration().getDeploymentManager().deploy(deployment, deploymentSettings);
DeploymentEntityManager
DeploymentEntityManager的deploy方法中循环调用Deployer对象的deploy方法,Activiti默认的Deployer是BpmnDeployer。
另外DeploymentEntityManager中还缓存了解析好的流程定义对象和Bpmn模型对象。
Activiti持久化的是流程图xml文件,每次系统重新启动都要执行一次“deploy”操作,生成ProcessDefinitionEntity对象。
BpmnDeployer
BpmnDeployer的deploy方法中包含几个操作(代码缩略版):
public void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings) {
...
BpmnParse bpmnParse = bpmnParser.createParse().sourceInputStream(inputStream).setSourceSystemId(resourceName).deployment(deployment).name(resourceName);
bpmnParse.execute();
for (ProcessDefinitionEntity processDefinition: bpmnParse.getProcessDefinitions()) {
if (deployment.isNew()) {
ProcessDefinitionEntity latestProcessDefinition = ...
if (latestProcessDefinition != null) {
processDefinitionVersion = latestProcessDefinition.getVersion() + 1;
}else{
processDefinitionVersion = 1;
}
processDefinition.setId(idGenerator.getNextId());
dbSqlSession.insert(processDefinition);
}
...
}
}
- 通过BpmnParser对象创建BpmnParse。
- 调用BpmnParse的execute方法,将inputStream中的流程图转化为ProcessDefinitionEntity。
- 持久化ProcessDefinitionEntity对象。
BpmnParse
在BpmnParse的execute中完成了xml文件到ProcessDefinitionEntity对象的转化:
public BpmnParse execute() {
//xml->bpmnModel
bpmnModel = converter.convertToBpmnModel(streamSource, validateSchema, enableSafeBpmnXml, encoding);
//bpmnModel-> ProcessDefinitionEntity
transformProcessDefinitions();
}
protected void transformProcessDefinitions() {
for (Process process : bpmnModel.getProcesses()) {
bpmnParserHandlers.parseElement(this, process);
}
}
在流程定义解析过程中,会涉及到两套模型:
- Bpmn模型(由BpmnXMLConverter完成转换)
- PVM模型(由BpmnParseHandlers完成转换)
Bpmn模型
PVM模型
Bpmn模型更偏向于xml节点的描述,PVM模型是运行时模型。Bpmn模型中的ServiceTask、StartEvent等会统一映射转换为PVM的ActivityImpl对象,ServiceTask和StartEvent等节点行为上的差别,体现在ActivityImpl对象持有的不同的ActivityBehavior上。
运行流程
创建流程实例
在demo中通过RuntimeService发起流程实例:
processEngine.getRuntimeService().startProcessInstanceByKey("hello");
在startProcessInstanceByKey方法中执行StartProcessInstanceCmd命令:
public class StartProcessInstanceCmd<T> implements Command<ProcessInstance>, Serializable {
...
public ProcessInstance execute(CommandContext commandContext) {
//获取流程定义
ProcessDefinitionEntity processDefinition = ...
//创建流程实例
ExecutionEntity processInstance = processDefinition.createProcessInstance(businessKey);
//开始流程
processInstance.start();
return processInstance;
}
...
}
在StartProcessInstanceCmd方中通过流程定义ProcessDefinitionEntity创建了流程实例 ExecutionEntity:
ExecutionEntity实现了一些重要接口:
- PVM相关的接口,赋予了ExecutionEntity流程驱动的能力,例如single、start方法。
- 实现VariableScope接口让ExecutionEntity可以持久上下文变量。
- ProcessInstance接口暴露了ExecutionEntity关联的ProcessDefinitionEntity的信息。
- PersistentObject接口代表ExecutionEntity对象是需要持久化。
在ExecutionEntity中维护类一个属性:activity。activity属性代表当前执行到哪个节点,在创建ExecutionEntity过程中会设置activity,使流程从某一个节点开始,默认是开始节点。
最后StartProcessInstanceCmd还调用ExecutionEntity的start方法开始驱动流程:
public void start() {
performOperation(AtomicOperation.PROCESS_START);
}
驱动流程
Activiti框架的流程运行于PVM模型之上,在流程运行时主要涉及到PVM中几个对象:ActivityImpl、TransitionImpl和ActivityBehavior。
- ActivityImpl:ActivityImpl是流程节点的抽象,ActivityImpl维护流程图中节点的连线,包括有哪些进线,有哪些出线。另外还包含节点同步/异步执行等信息。
- TransitionImpl:TransitionImpl包含source和target两个属性,连接了两个流程节点。
- ActivityBehavior:每一个ActivityImpl对象都拥有一个ActivityBehavior对象,ActivityBehavior代表节点的行为。
ActivityImpl、TransitionImpl和ActivityBehavior只是描述了流程的节点、迁移线和节点行为,真正要让ExecutionEntity流转起来,还需要AtomicOperation的驱动:
AtomicOperation PROCESS_START = new AtomicOperationProcessStart();
AtomicOperation PROCESS_START_INITIAL = new AtomicOperationProcessStartInitial();
AtomicOperation PROCESS_END = new AtomicOperationProcessEnd();
AtomicOperation ACTIVITY_START = new AtomicOperationActivityStart();
AtomicOperation ACTIVITY_EXECUTE = new AtomicOperationActivityExecute();
AtomicOperation ACTIVITY_END = new AtomicOperationActivityEnd();
AtomicOperation TRANSITION_NOTIFY_LISTENER_END = new AtomicOperationTransitionNotifyListenerEnd();
AtomicOperation TRANSITION_DESTROY_SCOPE = new AtomicOperationTransitionDestroyScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_TAKE = new AtomicOperationTransitionNotifyListenerTake();
AtomicOperation TRANSITION_CREATE_SCOPE = new AtomicOperationTransitionCreateScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_START = new AtomicOperationTransitionNotifyListenerStart();
AtomicOperation DELETE_CASCADE = new AtomicOperationDeleteCascade();
AtomicOperation DELETE_CASCADE_FIRE_ACTIVITY_END = new AtomicOperationDeleteCascadeFireActivityEnd();
在ExecutionEntity的start方法中,调用了PROCESS_START,PROCESS_START做了几件事:
- 获取流程定义级别定义的监听start事件的ExecutionListener,调用notify方法。
- 如果开启了事件功能,发布ActivitiEntityWithVariablesEvent和ActivitiProcessStartedEvent。
- 调用PROCESS_START_INITIAL。
PROCESS_START_INITIAL也实现了类似的功能:
- 获取初始节点上定义的监听start事件的ExecutionListener,调用notify方法。
- 调用ACTIVITY_EXECUTE。
在Demo流程执行中涉及的AtomicOperation的链路主要包括:
- ACTIVITY_EXECUTE:调用当前activity的behavior。
- TRANSITION_NOTIFY_LISTENER_END:某个activity节点执行完毕,调用节点上声明的监听end事件的ExecutionListener。
- TRANSITION_NOTIFY_LISTENER_TAKE:触发线上的ExecutionListener。
- TRANSITION_NOTIFY_LISTENER_START:某个activity节点即将开始执行,调用节点上的监听start事件的ExecutionListener。
以Demo流程中的ServiceTask节点helloworld为例,在执行ACTIVITY_EXECUTE时,会获取activity关联的behavior:
public class AtomicOperationActivityExecute implements AtomicOperation {
public void execute(InterpretableExecution execution) {
...
ActivityImpl activity = (ActivityImpl) execution.getActivity();
ActivityBehavior activityBehavior = activity.getActivityBehavior();
activityBehavior.execute(execution);
...
}
}
ServiceTask解析时关联的是ServiceTaskJavaDelegateActivityBehavior,execution方法:
public void execute(ActivityExecution execution) throws Exception {
//execution中调用了me.likeyao.activiti.demo.HelloWorld
execute((DelegateExecution) execution);
//离开当前节点
leave(execution);
}
在leave方法中调用了:
bpmnActivityBehavior.performDefaultOutgoingBehavior(execution);
performDefaultOutgoingBehavior方法会在当前activity的 出线中选择一条,使流程流向下一个节点。在Demo中只有一条线存在:
protected void performOutgoingBehavior(ActivityExecution execution,
boolean checkConditions, boolean throwExceptionIfExecutionStuck, List<ActivityExecution> reusableExecutions) {
if (transitionsToTake.size() == 1) {
execution.take(transitionsToTake.get(0));
}
}
最终take方法会将流程驱动权交还到AtomicOperation中:
public class ExecutionEntity{
...
public void take(PvmTransition transition, boolean fireActivityCompletionEvent) {
...
setActivity((ActivityImpl)transition.getSource());
setTransition((TransitionImpl) transition);
performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_END);
...
}
...
}
AtomicOperation的问题
按照AtomicOperation的驱动模式,只有当遇到UserTask等需要等待single信号的节点,调用才会返回。这意味着当调用RuntimeService启动一个流程实例时,要一直等到流程运行到一个UserTask节点调用才会返回,如果流程比较长耗时非常验证。
另一个问题是当流程图比较复杂,ExecutionListener数量比较多时,AtomicOperation之间的互相调用会导致调用栈非常深。
AtomicOperation驱动模式与ExecutionEntity、Behavior等绑定的比较紧密,暂时没有特别好的办法替换掉。
小结
本文主要介绍了Activiti框架的启动、部署、运行的主链路,并没有深入BPMN规范和Activit功能的具体实现,后续打算根据Activiti的用户手册,详细分析每个功能的使用和实现。
Activiti 学习笔记
Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。Activiti基于Apache许可的开源BPM平台,创始人Tom Baeyens是JBoss jBPM的项目架构师,它特色是提供了eclipse插件,开发人员可以通过插件直接绘画出业务流程图。 采用工作流管理系统的优点 1、提高系统的柔性,适应业务流程的变化 2、实现更好的业务过程控制,提高顾客服务质量 3、降低系统开发和维护成本 首先我们来梳理一下Activiti的开发步骤: 我们要用到一个工作流,首先就要把这个工作流定义出来【也就是工作流的步骤的怎么样的】,Activiti支持以“图”的方式来定义工作流 定义完工作流,就要部署到起来【我们可以联想到Tomcat,我们光下载了Tomcat是没有用的,要把它部署起来】 随后我们就执行该工作流,该工作流就随着我们定义的步骤来一一执行!
使用IDEA开发activiti的配置
使用IDEA开发Activiti工作流
安装ativiti插件
首先安装ativiti插件,安装成功后重启IDEA
然后在文件夹右键选择
画图
然后就可以画图了,但是画图之后,没有连接图标怎么办呢
把鼠标放在开头的那个图标上,此时光标的形状改变了。 拖到另一个上面,连接成功了。
怎么连接成功的呢? 把鼠标放到图标的正中心,然后会看到光标变成了“扇子类型
”的样子(黑白相间)。只要看到变成这个样子,就可以拖动到另一个图标中,进行连接了
生成png图片
画完图后,但是并没有生成png图片,这个时候
重命名刚才创建的文件,把后缀改成xml
然后右键
然后保存到与刚才创建文件的一个文件夹内就可以了
中文乱码
保存图片后,发现图片中文乱码,解决方法:
找到IDEA的安装目录,找到bin目录
比如我的路径:D:\JetBrains\IntelliJ IDEA 2017.3\bin
找到这两个文件
具体要修改哪个文件,由你安装的IDEA的版本来决定,如果你安装了64位版本的IDEA,那么就修改
这个文件,反之亦然,两个都修改也可以, 具体修改的方法是在文件后面追加一条命令: -Dfile.encoding=UTF-8
一定注意,不要有空格,否则重启IDEA时会打不开,然后 重启IDEA,把原来的png图片删掉,再重新生成,即可解决乱码问题
解决问题后,把xml文件重新改成bpmn格式,和png图片一起压缩成zip包进行部署
AgileBPM 敏捷工作流开发平台
推荐个人认为做的比较不错的开源工作开发平台AgileBPM 敏捷工作流开发平台—— 开源免费-基于 Activiti 工作流引擎、Flowable
科技部门通过快速迭代支撑业务不断的创新、发展,最终构建出完善的、高效的业务线从而提高产品竞争力、行业壁垒。
AgileBPM
是一个快速开发平台,与众多快速开发平台不一样的地方是,他可以快速的进行业务流程的实施,助力企业快速构建业务流。
文档
摘要
我见过国内很多开发员从零开始整合流程引擎,而后也在使用一些不太合适的实施形式,导致很多开发付出了很沉重的开发代价,浪费了很多时间。
回想下吧,正在走向流程整合道路的您,已经踩过多少坑,比如 Activiti 表单、人员、自由跳转、会签多实例 、难用的API 等等这些坑,您或者已经踩过、或者正在奔坑而去!
不可否认 Activiti 的流程引擎高效强大,但是周边建设的确牵强人意
- 所以我们希望构建出一款流程服务组件
- 要比 Activiti 更易整合
- 不失性能的前提下拥有更丰富的流程功能
- 流程实施配置化
- 表单开发零代码
- 还有最重要的是 保持功能的可扩展性(只有保持扩展性才能适应更复杂的业务场景)
API
Agilebpm 项目模块说明
agilebpm 主工程
base基础模块
base-api : 提供 通用请求入参、返回参数、基础实体、基础service/dao API定义、异常规范、校验定义
base-core : 提供常用工具类、基础API实现 、ID生成、通用校验实现等
base-db : 整合多数据源,mybatis,jdbcTemplate等持久化层相关的实现
base-rest :提供 Rest 服务基类、Rest 服务相关的工具类 等
系统功能模块
sys-api
系统服务接口定义
jms
freemark
groovy
redis
scheduler
节假日、流水号、日程、工作台 等系统模块服务接口
sys-core
系统服务接口实现
sys-rest
对外 rest 服务
组织模块
org-api
组织架构接口定义
ab-org-core
orgAPI 实现层
用户管理,组织岗位,角色,用户组关系
说明:为流程、鉴权 等组件提供用户组织服务,该模块由 服务接口模块、接口适配器层、具体实现层三部分组成
业务对象
1、业务实体,业务对象的定义
2、业务实体数据的持久化服务
3、表字段控件定义,表单布局设计
说明:业务对象为具体的业务数据的结构,支持一对多、多对多、一对一、多层关联关系(学校-班级-学生…)
业务对象多表来自不同数据源,并支持多数据源分布式事务(性能与普通事务管理没什么差别)
表单模块
1、提供在线表单的生成(PC 、移动端 )
2、表单高级控件的配置 3、表单 模板管理 4、表单对于业务数据增删改查的简实现
说明:表单是业务对象的容器,依赖bus模块。
web 模块
仅仅为了组合 各个模块,提供web服务,没有任何其他代码逻辑
SpringBoot 版本
说明: 本项目使用Springboot2.1.4版本 ,以starter的形式整合 agilebpm 模块,具体模块源码都在 AgileBPM 主工程
agilebpm-base-starter
整合 base 模块, 主要是base-db 的整合
两个jar agilebpm-base-autoconfigure,agilebpm-base-starter
agilebpm-sys-starter
整合 sys 系统模块,主要整合了 mq,email 等组件
两个jar,agilebpm-sys-starter,agilebpm-sys-autoconfigure
agilebpm-security-starter
整合spring security,
agilebpm-security-starter,agilebpm-security-autoconfigure
agilebpm-wf-starter
整合了流程模块
agilebpm-ui
前端工程 与主工程中 bpm-explorer 源码一致,这里是以jar包的形式提供前端的资源
agilebpm-spring-boot-samples
spring boot Application 启动项目,以此为案例可以方便整合到自有 springboot工程中
主工程中 bus,form, org 模块直接依赖 rest 模块即可引入模块服务,所以不需要 提供 starter
SpringCloud微服务版本-商业版
说明:本项目依赖 SpringBoot版本的starter,基于 spring-cloud Greenwich.SR1
common 公共模块
commons-web 微服务项目公共模块,所有微服务模块均基层该基础模块
commons-session-adapter-api
当前登录用户的接口定义层
commons-session-adapter-server
提供当前登录用户 的具体实现,目前默认session实现
commons-adapter-client
org 微服务 REST 服务 适配的客户端
commons-org-adapter-server
org 微服务 REST 服务的 服务提供者
当需要为流程微服务提供组织微服务的时候、直接依赖 该模块,实现orgapi 即可提供 agilebpm 的组织服务
eureka-server
eureka 注册中心
gateway
zuul 网关、实现了 请求资源地址鉴权,csrf 防盗链,防xss注入攻击 ,路由服务请求
org-services
org 微服务的默认实现,依赖 ab-org-core
bpm-service
流程微服务,依赖 agilebpm-wf-starter,bus-rest,agilebpm-ui ,form-rest,agilebpm-sys-starter
前端工程
agilebpm 主工程 前端 bpm-explorer
移动端前端工程 bpm-app
iview 门户前端工程 agilebpm-eip-ui
商业版
说明: 所有前端工程后端项目源码 均在 agilebpm主工程
只不过有springboot形式、微服务形式、传统web形式 三种不同 服务提供形式
试用地址
普通版本
-
移动端
请关注公众号 AgileBPM 在线试用。
全新 OA 版本
OA版本是全新的UI,基于Iview-pro开发,拥有更漂亮的 UI、极致的用户体验和细节处理 并且支持国际化。
如果对前端有较高要求可以选择 高级OA版本或者企业OA版本。
OA 版本中,流程设计、表单开发是单独的一个前端工程,而面向客户的门户平台则使用的 iview ui 进行开发的,这样保证了面向用户的前端可以做到最轻量级
老版门户平台地址: http://test1.agilebpm.cn/agilebpm-eip-ui/index.html
商用版本如何选择?
AgileBPM 目前有以下版本
码云版
含移动端、系统功能模块、基础模块、鉴权模块、表单模块、业务对象模块、基础的流程功能(未开源) 商用需遵循 GPL开源协议
高级版
含 高级流程功能(开源)、 eip门户前端、SpringBoot版本、移动端、1年技术支持,商业实施无限制
企业版
高级版基础上新增了 SpringCloud版本、2年技术支持、上门技术讲解、插件定制、框架整合服务等
OA版
全新设计的一款前端工程,在企业版基础上新增了OA办公模块,这也是我们以后工作重心,会陆续新增 排班、考勤等功能
所有以上版本均支持永久升级服务
基础开源版本会满足 90%的流程需求,特殊需求也可以通过预留的扩展钩子实现。
AgileBPM 商用版本功能更强大,这些功能能让流程实施事半功倍!
AgileBPM 不同的版本线规划了版本内的开发计划,采购对应版本后支持永久升级,但是产品价格我们会以当下功能定价。
所以我们建议选择最具有空间的 OA 版本! 其他版本内容基本已经构建完善。
Actviti7 数据库环境
数据库表的命名规则
Activiti
的表都以 ACT_
开头。第二部分是表示表的用途的两个字母标识。用途也和服务的 API
对应。
-
ACT_RE_*
: 'RE'表示repository。这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。 -
ACT_RU_*
: 'RU'表示runtime。这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。Activiti 只在流程实例执行过程中保存这些数据,在流程结 束时就会删除这些记录。这样运行时表可以一直很小速度很快。 -
ACT_HI_*
: 'HI'表示history。这些表包含历史数据,比如历史流程实例,变量,任务等等。 -
ACT_GE_*
: 'GE'表示general。通用数据,用于不同场景下。
Activiti服务架构图
配置信息 activiti.cfg.xml
activiti
的引擎配置文件,包括: ProcessEngineConfiguration
的定义、数据源定义、事务管理器等,此文件其实就是一个 spring
配置文件,下面是一个基本的配置只配置了 ProcessEngineConfiguration
和数据源:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/contex http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--数据源配置dbcp-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://xxx:3306/activiti" />
<property name="username" value="activiti-xxx" />
<property name="password" value="activiti-xxx" />
</bean>
<!--
activiti单独运行的ProcessEngine配置对象(processEngineConfiguration),使用单独启动方式
默认情况下:bean的id=processEngineConfiguration
-->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!--代表数据源-->
<property name="dataSource" ref="dataSource" />
<!--<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />-->
<!--<property name="jdbcUrl" value="jdbc:mysql://xxx:3306/activiti" />-->
<!--<property name="jdbcUsername" value="activiti-xxx" />-->
<!--<property name="jdbcPassword" value="activiti-xxx" />-->
<!--代表是否生成表结构-->
<property name="databaseSchemaUpdate" value="true" />
</bean>
</beans>
ProcessEngineConfiguration
创建表 ActivitiTest.java
package com.itheima.test;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.junit.Test;
/**
* 测试类
* 作用:测试activiti所需要的25张表的生成
*/
public class ActivitiTest {
@Test
public void testGenTable(){
//条件:1.activiti配置文件名称:activiti.cfg.xml 2.bean的id="processEngineConfiguration"
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);
// HistoryService historyService = processEngine.getHistoryService();
}
/* @Test
public void testGenTable(){
//1.创建ProcessEngineConfiguration对象 第一个参数:配置文件名称 第二个参数是processEngineConfiguration的bean的id
ProcessEngineConfiguration configuration = ProcessEngineConfiguration
.createProcessEngineConfigurationFromResource("activiti.cfg.xml","processEngineConfiguration01");
//2.创建ProcesEngine对象
ProcessEngine processEngine = configuration.buildProcessEngine();
//3.输出processEngine对象
System.out.println(processEngine);
}*/
}
数据库导出 activiti
/*
Navicat MySQL Data Transfer
Source Server Version : 50729
Source Database : activiti
Target Server Type : MYSQL
Target Server Version : 50729
File Encoding : 65001
Date: 2020-03-17 13:53:36
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for ACT_EVT_LOG
-- ----------------------------
DROP TABLE IF EXISTS `ACT_EVT_LOG`;
CREATE TABLE `ACT_EVT_LOG` (
`LOG_NR_` bigint(20) NOT NULL AUTO_INCREMENT,
`TYPE_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TIME_STAMP_` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
`USER_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`DATA_` longblob,
`LOCK_OWNER_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`LOCK_TIME_` timestamp(3) NULL DEFAULT NULL,
`IS_PROCESSED_` tinyint(4) DEFAULT '0',
PRIMARY KEY (`LOG_NR_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_EVT_LOG
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_GE_BYTEARRAY
-- ----------------------------
DROP TABLE IF EXISTS `ACT_GE_BYTEARRAY`;
CREATE TABLE `ACT_GE_BYTEARRAY` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`DEPLOYMENT_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`BYTES_` longblob,
`GENERATED_` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_FK_BYTEARR_DEPL` (`DEPLOYMENT_ID_`),
CONSTRAINT `ACT_FK_BYTEARR_DEPL` FOREIGN KEY (`DEPLOYMENT_ID_`) REFERENCES `ACT_RE_DEPLOYMENT` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_GE_BYTEARRAY
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_GE_PROPERTY
-- ----------------------------
DROP TABLE IF EXISTS `ACT_GE_PROPERTY`;
CREATE TABLE `ACT_GE_PROPERTY` (
`NAME_` varchar(64) COLLATE utf8_bin NOT NULL,
`VALUE_` varchar(300) COLLATE utf8_bin DEFAULT NULL,
`REV_` int(11) DEFAULT NULL,
PRIMARY KEY (`NAME_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_GE_PROPERTY
-- ----------------------------
INSERT INTO `ACT_GE_PROPERTY` VALUES ('cfg.execution-related-entities-count', 'false', '1');
INSERT INTO `ACT_GE_PROPERTY` VALUES ('next.dbid', '1', '1');
INSERT INTO `ACT_GE_PROPERTY` VALUES ('schema.history', 'create(7.1.0-M6)', '1');
INSERT INTO `ACT_GE_PROPERTY` VALUES ('schema.version', '7.1.0-M6', '1');
-- ----------------------------
-- Table structure for ACT_HI_ACTINST
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_ACTINST`;
CREATE TABLE `ACT_HI_ACTINST` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`ACT_ID_` varchar(255) COLLATE utf8_bin NOT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`CALL_PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`ACT_NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`ACT_TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`ASSIGNEE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`START_TIME_` datetime(3) NOT NULL,
`END_TIME_` datetime(3) DEFAULT NULL,
`DURATION_` bigint(20) DEFAULT NULL,
`DELETE_REASON_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_HI_ACT_INST_START` (`START_TIME_`),
KEY `ACT_IDX_HI_ACT_INST_END` (`END_TIME_`),
KEY `ACT_IDX_HI_ACT_INST_PROCINST` (`PROC_INST_ID_`,`ACT_ID_`),
KEY `ACT_IDX_HI_ACT_INST_EXEC` (`EXECUTION_ID_`,`ACT_ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_ACTINST
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_HI_ATTACHMENT
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_ATTACHMENT`;
CREATE TABLE `ACT_HI_ATTACHMENT` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`USER_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`DESCRIPTION_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`URL_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`CONTENT_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TIME_` datetime(3) DEFAULT NULL,
PRIMARY KEY (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_ATTACHMENT
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_HI_COMMENT
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_COMMENT`;
CREATE TABLE `ACT_HI_COMMENT` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TIME_` datetime(3) NOT NULL,
`USER_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`ACTION_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`MESSAGE_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`FULL_MSG_` longblob,
PRIMARY KEY (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_COMMENT
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_HI_DETAIL
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_DETAIL`;
CREATE TABLE `ACT_HI_DETAIL` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`ACT_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin NOT NULL,
`VAR_TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`REV_` int(11) DEFAULT NULL,
`TIME_` datetime(3) NOT NULL,
`BYTEARRAY_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`DOUBLE_` double DEFAULT NULL,
`LONG_` bigint(20) DEFAULT NULL,
`TEXT_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TEXT2_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_HI_DETAIL_PROC_INST` (`PROC_INST_ID_`),
KEY `ACT_IDX_HI_DETAIL_ACT_INST` (`ACT_INST_ID_`),
KEY `ACT_IDX_HI_DETAIL_TIME` (`TIME_`),
KEY `ACT_IDX_HI_DETAIL_NAME` (`NAME_`),
KEY `ACT_IDX_HI_DETAIL_TASK_ID` (`TASK_ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_DETAIL
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_HI_IDENTITYLINK
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_IDENTITYLINK`;
CREATE TABLE `ACT_HI_IDENTITYLINK` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`GROUP_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`USER_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_HI_IDENT_LNK_USER` (`USER_ID_`),
KEY `ACT_IDX_HI_IDENT_LNK_TASK` (`TASK_ID_`),
KEY `ACT_IDX_HI_IDENT_LNK_PROCINST` (`PROC_INST_ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_IDENTITYLINK
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_HI_PROCINST
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_PROCINST`;
CREATE TABLE `ACT_HI_PROCINST` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`BUSINESS_KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`START_TIME_` datetime(3) NOT NULL,
`END_TIME_` datetime(3) DEFAULT NULL,
`DURATION_` bigint(20) DEFAULT NULL,
`START_USER_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`START_ACT_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`END_ACT_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`SUPER_PROCESS_INSTANCE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`DELETE_REASON_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`ID_`),
UNIQUE KEY `PROC_INST_ID_` (`PROC_INST_ID_`),
KEY `ACT_IDX_HI_PRO_INST_END` (`END_TIME_`),
KEY `ACT_IDX_HI_PRO_I_BUSKEY` (`BUSINESS_KEY_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_PROCINST
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_HI_TASKINST
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_TASKINST`;
CREATE TABLE `ACT_HI_TASKINST` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TASK_DEF_KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`PARENT_TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`DESCRIPTION_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`OWNER_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`ASSIGNEE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`START_TIME_` datetime(3) NOT NULL,
`CLAIM_TIME_` datetime(3) DEFAULT NULL,
`END_TIME_` datetime(3) DEFAULT NULL,
`DURATION_` bigint(20) DEFAULT NULL,
`DELETE_REASON_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`PRIORITY_` int(11) DEFAULT NULL,
`DUE_DATE_` datetime(3) DEFAULT NULL,
`FORM_KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`CATEGORY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_HI_TASK_INST_PROCINST` (`PROC_INST_ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_TASKINST
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_HI_VARINST
-- ----------------------------
DROP TABLE IF EXISTS `ACT_HI_VARINST`;
CREATE TABLE `ACT_HI_VARINST` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin NOT NULL,
`VAR_TYPE_` varchar(100) COLLATE utf8_bin DEFAULT NULL,
`REV_` int(11) DEFAULT NULL,
`BYTEARRAY_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`DOUBLE_` double DEFAULT NULL,
`LONG_` bigint(20) DEFAULT NULL,
`TEXT_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TEXT2_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`CREATE_TIME_` datetime(3) DEFAULT NULL,
`LAST_UPDATED_TIME_` datetime(3) DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_HI_PROCVAR_PROC_INST` (`PROC_INST_ID_`),
KEY `ACT_IDX_HI_PROCVAR_NAME_TYPE` (`NAME_`,`VAR_TYPE_`),
KEY `ACT_IDX_HI_PROCVAR_TASK_ID` (`TASK_ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_HI_VARINST
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_PROCDEF_INFO
-- ----------------------------
DROP TABLE IF EXISTS `ACT_PROCDEF_INFO`;
CREATE TABLE `ACT_PROCDEF_INFO` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`INFO_JSON_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`ID_`),
UNIQUE KEY `ACT_UNIQ_INFO_PROCDEF` (`PROC_DEF_ID_`),
KEY `ACT_IDX_INFO_PROCDEF` (`PROC_DEF_ID_`),
KEY `ACT_FK_INFO_JSON_BA` (`INFO_JSON_ID_`),
CONSTRAINT `ACT_FK_INFO_JSON_BA` FOREIGN KEY (`INFO_JSON_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`),
CONSTRAINT `ACT_FK_INFO_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_PROCDEF_INFO
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RE_DEPLOYMENT
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RE_DEPLOYMENT`;
CREATE TABLE `ACT_RE_DEPLOYMENT` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`CATEGORY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
`DEPLOY_TIME_` timestamp(3) NULL DEFAULT NULL,
`ENGINE_VERSION_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`VERSION_` int(11) DEFAULT '1',
`PROJECT_RELEASE_VERSION_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RE_DEPLOYMENT
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RE_MODEL
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RE_MODEL`;
CREATE TABLE `ACT_RE_MODEL` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`CATEGORY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`CREATE_TIME_` timestamp(3) NULL DEFAULT NULL,
`LAST_UPDATE_TIME_` timestamp(3) NULL DEFAULT NULL,
`VERSION_` int(11) DEFAULT NULL,
`META_INFO_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`DEPLOYMENT_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EDITOR_SOURCE_VALUE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EDITOR_SOURCE_EXTRA_VALUE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_FK_MODEL_SOURCE` (`EDITOR_SOURCE_VALUE_ID_`),
KEY `ACT_FK_MODEL_SOURCE_EXTRA` (`EDITOR_SOURCE_EXTRA_VALUE_ID_`),
KEY `ACT_FK_MODEL_DEPLOYMENT` (`DEPLOYMENT_ID_`),
CONSTRAINT `ACT_FK_MODEL_DEPLOYMENT` FOREIGN KEY (`DEPLOYMENT_ID_`) REFERENCES `ACT_RE_DEPLOYMENT` (`ID_`),
CONSTRAINT `ACT_FK_MODEL_SOURCE` FOREIGN KEY (`EDITOR_SOURCE_VALUE_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`),
CONSTRAINT `ACT_FK_MODEL_SOURCE_EXTRA` FOREIGN KEY (`EDITOR_SOURCE_EXTRA_VALUE_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RE_MODEL
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RE_PROCDEF
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RE_PROCDEF`;
CREATE TABLE `ACT_RE_PROCDEF` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`CATEGORY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`KEY_` varchar(255) COLLATE utf8_bin NOT NULL,
`VERSION_` int(11) NOT NULL,
`DEPLOYMENT_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`RESOURCE_NAME_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`DGRM_RESOURCE_NAME_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`DESCRIPTION_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`HAS_START_FORM_KEY_` tinyint(4) DEFAULT NULL,
`HAS_GRAPHICAL_NOTATION_` tinyint(4) DEFAULT NULL,
`SUSPENSION_STATE_` int(11) DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
`ENGINE_VERSION_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`APP_VERSION_` int(11) DEFAULT NULL,
PRIMARY KEY (`ID_`),
UNIQUE KEY `ACT_UNIQ_PROCDEF` (`KEY_`,`VERSION_`,`TENANT_ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RE_PROCDEF
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_DEADLETTER_JOB
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_DEADLETTER_JOB`;
CREATE TABLE `ACT_RU_DEADLETTER_JOB` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`EXCLUSIVE_` tinyint(1) DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROCESS_INSTANCE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXCEPTION_STACK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXCEPTION_MSG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`DUEDATE_` timestamp(3) NULL DEFAULT NULL,
`REPEAT_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_CFG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_FK_DEADLETTER_JOB_EXECUTION` (`EXECUTION_ID_`),
KEY `ACT_FK_DEADLETTER_JOB_PROCESS_INSTANCE` (`PROCESS_INSTANCE_ID_`),
KEY `ACT_FK_DEADLETTER_JOB_PROC_DEF` (`PROC_DEF_ID_`),
KEY `ACT_FK_DEADLETTER_JOB_EXCEPTION` (`EXCEPTION_STACK_ID_`),
CONSTRAINT `ACT_FK_DEADLETTER_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`),
CONSTRAINT `ACT_FK_DEADLETTER_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_DEADLETTER_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_DEADLETTER_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_DEADLETTER_JOB
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_EVENT_SUBSCR
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_EVENT_SUBSCR`;
CREATE TABLE `ACT_RU_EVENT_SUBSCR` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`EVENT_TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`EVENT_NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`ACTIVITY_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`CONFIGURATION_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`CREATED_` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_EVENT_SUBSCR_CONFIG_` (`CONFIGURATION_`),
KEY `ACT_FK_EVENT_EXEC` (`EXECUTION_ID_`),
CONSTRAINT `ACT_FK_EVENT_EXEC` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_EVENT_SUBSCR
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_EXECUTION
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_EXECUTION`;
CREATE TABLE `ACT_RU_EXECUTION` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`BUSINESS_KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`PARENT_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`SUPER_EXEC_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`ROOT_PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`ACT_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`IS_ACTIVE_` tinyint(4) DEFAULT NULL,
`IS_CONCURRENT_` tinyint(4) DEFAULT NULL,
`IS_SCOPE_` tinyint(4) DEFAULT NULL,
`IS_EVENT_SCOPE_` tinyint(4) DEFAULT NULL,
`IS_MI_ROOT_` tinyint(4) DEFAULT NULL,
`SUSPENSION_STATE_` int(11) DEFAULT NULL,
`CACHED_ENT_STATE_` int(11) DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`START_TIME_` datetime(3) DEFAULT NULL,
`START_USER_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`LOCK_TIME_` timestamp(3) NULL DEFAULT NULL,
`IS_COUNT_ENABLED_` tinyint(4) DEFAULT NULL,
`EVT_SUBSCR_COUNT_` int(11) DEFAULT NULL,
`TASK_COUNT_` int(11) DEFAULT NULL,
`JOB_COUNT_` int(11) DEFAULT NULL,
`TIMER_JOB_COUNT_` int(11) DEFAULT NULL,
`SUSP_JOB_COUNT_` int(11) DEFAULT NULL,
`DEADLETTER_JOB_COUNT_` int(11) DEFAULT NULL,
`VAR_COUNT_` int(11) DEFAULT NULL,
`ID_LINK_COUNT_` int(11) DEFAULT NULL,
`APP_VERSION_` int(11) DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_EXEC_BUSKEY` (`BUSINESS_KEY_`),
KEY `ACT_IDC_EXEC_ROOT` (`ROOT_PROC_INST_ID_`),
KEY `ACT_FK_EXE_PROCINST` (`PROC_INST_ID_`),
KEY `ACT_FK_EXE_PARENT` (`PARENT_ID_`),
KEY `ACT_FK_EXE_SUPER` (`SUPER_EXEC_`),
KEY `ACT_FK_EXE_PROCDEF` (`PROC_DEF_ID_`),
CONSTRAINT `ACT_FK_EXE_PARENT` FOREIGN KEY (`PARENT_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`) ON DELETE CASCADE,
CONSTRAINT `ACT_FK_EXE_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`),
CONSTRAINT `ACT_FK_EXE_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `ACT_FK_EXE_SUPER` FOREIGN KEY (`SUPER_EXEC_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_EXECUTION
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_IDENTITYLINK
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_IDENTITYLINK`;
CREATE TABLE `ACT_RU_IDENTITYLINK` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`GROUP_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`USER_ID_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_IDENT_LNK_USER` (`USER_ID_`),
KEY `ACT_IDX_IDENT_LNK_GROUP` (`GROUP_ID_`),
KEY `ACT_IDX_ATHRZ_PROCEDEF` (`PROC_DEF_ID_`),
KEY `ACT_FK_TSKASS_TASK` (`TASK_ID_`),
KEY `ACT_FK_IDL_PROCINST` (`PROC_INST_ID_`),
CONSTRAINT `ACT_FK_ATHRZ_PROCEDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`),
CONSTRAINT `ACT_FK_IDL_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_TSKASS_TASK` FOREIGN KEY (`TASK_ID_`) REFERENCES `ACT_RU_TASK` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_IDENTITYLINK
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_INTEGRATION
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_INTEGRATION`;
CREATE TABLE `ACT_RU_INTEGRATION` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROCESS_INSTANCE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`FLOW_NODE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`CREATED_DATE_` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
PRIMARY KEY (`ID_`),
KEY `ACT_FK_INT_EXECUTION` (`EXECUTION_ID_`),
KEY `ACT_FK_INT_PROC_INST` (`PROCESS_INSTANCE_ID_`),
KEY `ACT_FK_INT_PROC_DEF` (`PROC_DEF_ID_`),
CONSTRAINT `ACT_FK_INT_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`) ON DELETE CASCADE,
CONSTRAINT `ACT_FK_INT_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`),
CONSTRAINT `ACT_FK_INT_PROC_INST` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_INTEGRATION
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_JOB
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_JOB`;
CREATE TABLE `ACT_RU_JOB` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`LOCK_EXP_TIME_` timestamp(3) NULL DEFAULT NULL,
`LOCK_OWNER_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`EXCLUSIVE_` tinyint(1) DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROCESS_INSTANCE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`RETRIES_` int(11) DEFAULT NULL,
`EXCEPTION_STACK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXCEPTION_MSG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`DUEDATE_` timestamp(3) NULL DEFAULT NULL,
`REPEAT_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_CFG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_FK_JOB_EXECUTION` (`EXECUTION_ID_`),
KEY `ACT_FK_JOB_PROCESS_INSTANCE` (`PROCESS_INSTANCE_ID_`),
KEY `ACT_FK_JOB_PROC_DEF` (`PROC_DEF_ID_`),
KEY `ACT_FK_JOB_EXCEPTION` (`EXCEPTION_STACK_ID_`),
CONSTRAINT `ACT_FK_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`),
CONSTRAINT `ACT_FK_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_JOB
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_SUSPENDED_JOB
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_SUSPENDED_JOB`;
CREATE TABLE `ACT_RU_SUSPENDED_JOB` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`EXCLUSIVE_` tinyint(1) DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROCESS_INSTANCE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`RETRIES_` int(11) DEFAULT NULL,
`EXCEPTION_STACK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXCEPTION_MSG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`DUEDATE_` timestamp(3) NULL DEFAULT NULL,
`REPEAT_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_CFG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_FK_SUSPENDED_JOB_EXECUTION` (`EXECUTION_ID_`),
KEY `ACT_FK_SUSPENDED_JOB_PROCESS_INSTANCE` (`PROCESS_INSTANCE_ID_`),
KEY `ACT_FK_SUSPENDED_JOB_PROC_DEF` (`PROC_DEF_ID_`),
KEY `ACT_FK_SUSPENDED_JOB_EXCEPTION` (`EXCEPTION_STACK_ID_`),
CONSTRAINT `ACT_FK_SUSPENDED_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`),
CONSTRAINT `ACT_FK_SUSPENDED_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_SUSPENDED_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_SUSPENDED_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_SUSPENDED_JOB
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_TASK
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_TASK`;
CREATE TABLE `ACT_RU_TASK` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`NAME_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`BUSINESS_KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`PARENT_TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`DESCRIPTION_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TASK_DEF_KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`OWNER_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`ASSIGNEE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`DELEGATION_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PRIORITY_` int(11) DEFAULT NULL,
`CREATE_TIME_` timestamp(3) NULL DEFAULT NULL,
`DUE_DATE_` datetime(3) DEFAULT NULL,
`CATEGORY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`SUSPENSION_STATE_` int(11) DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
`FORM_KEY_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`CLAIM_TIME_` datetime(3) DEFAULT NULL,
`APP_VERSION_` int(11) DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_TASK_CREATE` (`CREATE_TIME_`),
KEY `ACT_FK_TASK_EXE` (`EXECUTION_ID_`),
KEY `ACT_FK_TASK_PROCINST` (`PROC_INST_ID_`),
KEY `ACT_FK_TASK_PROCDEF` (`PROC_DEF_ID_`),
CONSTRAINT `ACT_FK_TASK_EXE` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_TASK_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`),
CONSTRAINT `ACT_FK_TASK_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_TASK
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_TIMER_JOB
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_TIMER_JOB`;
CREATE TABLE `ACT_RU_TIMER_JOB` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`LOCK_EXP_TIME_` timestamp(3) NULL DEFAULT NULL,
`LOCK_OWNER_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`EXCLUSIVE_` tinyint(1) DEFAULT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROCESS_INSTANCE_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_DEF_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`RETRIES_` int(11) DEFAULT NULL,
`EXCEPTION_STACK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`EXCEPTION_MSG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`DUEDATE_` timestamp(3) NULL DEFAULT NULL,
`REPEAT_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_TYPE_` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`HANDLER_CFG_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TENANT_ID_` varchar(255) COLLATE utf8_bin DEFAULT '',
PRIMARY KEY (`ID_`),
KEY `ACT_FK_TIMER_JOB_EXECUTION` (`EXECUTION_ID_`),
KEY `ACT_FK_TIMER_JOB_PROCESS_INSTANCE` (`PROCESS_INSTANCE_ID_`),
KEY `ACT_FK_TIMER_JOB_PROC_DEF` (`PROC_DEF_ID_`),
KEY `ACT_FK_TIMER_JOB_EXCEPTION` (`EXCEPTION_STACK_ID_`),
CONSTRAINT `ACT_FK_TIMER_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`),
CONSTRAINT `ACT_FK_TIMER_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_TIMER_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_TIMER_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `ACT_RE_PROCDEF` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_TIMER_JOB
-- ----------------------------
-- ----------------------------
-- Table structure for ACT_RU_VARIABLE
-- ----------------------------
DROP TABLE IF EXISTS `ACT_RU_VARIABLE`;
CREATE TABLE `ACT_RU_VARIABLE` (
`ID_` varchar(64) COLLATE utf8_bin NOT NULL,
`REV_` int(11) DEFAULT NULL,
`TYPE_` varchar(255) COLLATE utf8_bin NOT NULL,
`NAME_` varchar(255) COLLATE utf8_bin NOT NULL,
`EXECUTION_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`PROC_INST_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`TASK_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`BYTEARRAY_ID_` varchar(64) COLLATE utf8_bin DEFAULT NULL,
`DOUBLE_` double DEFAULT NULL,
`LONG_` bigint(20) DEFAULT NULL,
`TEXT_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
`TEXT2_` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`ID_`),
KEY `ACT_IDX_VARIABLE_TASK_ID` (`TASK_ID_`),
KEY `ACT_FK_VAR_EXE` (`EXECUTION_ID_`),
KEY `ACT_FK_VAR_PROCINST` (`PROC_INST_ID_`),
KEY `ACT_FK_VAR_BYTEARRAY` (`BYTEARRAY_ID_`),
CONSTRAINT `ACT_FK_VAR_BYTEARRAY` FOREIGN KEY (`BYTEARRAY_ID_`) REFERENCES `ACT_GE_BYTEARRAY` (`ID_`),
CONSTRAINT `ACT_FK_VAR_EXE` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`),
CONSTRAINT `ACT_FK_VAR_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `ACT_RU_EXECUTION` (`ID_`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
-- ----------------------------
-- Records of ACT_RU_VARIABLE
-- ----------------------------
Activiti 流程定义及部署测试
流程定义
Activiti-Designer
使用- 绘制流程
- 指定流程定义
Key
- 指定任务负责人
安装 Activiti 插件
在eclipse或idea中安装activiti-designer插件即可使用,画板中包括以下结点:
Connection-连接
Event---事件
Task--任务
Gateway---网关
Container-容器
Boundary event-边界事件
Intermediate event- -中间事件
流程图设计完毕保存生成.bpmn文件。
1.搜索插件
-
点击菜单【File】-->【Settings...】打开【Settings】窗口。
-
点击左侧【Plugins】按钮,在右侧输出"actiBPM",点击下面的【Search in repositories】链接会打开【Browse Repositories】窗口。
2.开始安装
- 进入【Browse Repositories】窗口,选中左侧的【actiBPM】,点击右侧的【Install】按钮,开始安装。
3.安装进度
4.安装完成
- 安装完成后,会提示【Restart IntelliJ IDEA】,重启IDEA即可完成安装。
5.查看结果
- 打开【Settings】窗口,在【Plugins】中可看到安装的【actiBPM】插件,表示安装成功。
Activiti-Designer
使用
1.创建BPMN文件
- 点击菜单【File】-->【New】-->【BpmnFile】
- 输入文件名称,点击【OK】按钮
- 会出现如下绘制界面
2.绘制流程图
- 鼠标左键拖拽右侧图标,将其拖下左侧界面上,同样的方式再拖拽其他图标
- 鼠标移至图标的中心会变成黑白色扇形,拖拽到另一图标,即可连接
- 双击图标,可修改名称
3.导出图片
- 右击bpmn文件,选择【Refactor】-->【Rename】,修改其扩展名为.xml,点击【Refactor】
- 接着右击此xml文件,选择【Diagrams】-->【Show BPMN 2.0 Diagrams...】,打开如下界面
- 点击上图中【Export to file】图标,弹出【Save as image】窗口,点击【OK】即可导出png图片
4.解决中文乱码问题
- 在IDEA的安装目录,在下面两个文件中加上-Dfile.encoding=UTF-8
- 重启IDEA,乱码问题解决
启动一个流程实例
holiday.bpmn
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.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" xmlns:tns="http://www.activiti.org/test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="m1540200341676" name="" targetNamespace="http://www.activiti.org/test" typeLanguage="http://www.w3.org/2001/XMLSchema">
<process id="holiday" isClosed="false" isExecutable="true" name="请假流程" processType="None">
<startEvent id="_2" name="StartEvent"/>
<userTask activiti:assignee="zhangsan" activiti:exclusive="true" id="_3" name="填写请假申请单"/>
<userTask activiti:assignee="lishi" activiti:exclusive="true" id="_4" name="部门经理审批"/>
<userTask activiti:assignee="wangwu" activiti:exclusive="true" id="_5" name="总经理审批"/>
<endEvent id="_6" name="EndEvent"/>
<sequenceFlow id="_7" sourceRef="_2" targetRef="_3"/>
<sequenceFlow id="_8" sourceRef="_3" targetRef="_4"/>
<sequenceFlow id="_9" sourceRef="_4" targetRef="_5"/>
<sequenceFlow id="_10" sourceRef="_5" targetRef="_6"/>
</process>
<bpmndi:BPMNDiagram documentation="background=#FFFFFF;count=1;horizontalcount=1;orientation=0;width=842.4;height=1195.2;imageableWidth=832.4;imageableHeight=1185.2;imageableX=5.0;imageableY=5.0" id="Diagram-_1" name="New Diagram">
<bpmndi:BPMNPlane bpmnElement="holiday">
<bpmndi:BPMNShape bpmnElement="_2" id="Shape-_2">
<omgdc:Bounds height="32.0" width="32.0" x="280.0" y="45.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_3" id="Shape-_3">
<omgdc:Bounds height="55.0" width="85.0" x="255.0" y="140.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_4" id="Shape-_4">
<omgdc:Bounds height="55.0" width="85.0" x="255.0" y="245.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_5" id="Shape-_5">
<omgdc:Bounds height="55.0" width="85.0" x="255.0" y="350.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="55.0" width="85.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="_6" id="Shape-_6">
<omgdc:Bounds height="32.0" width="32.0" x="275.0" y="455.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="32.0" width="32.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="_7" id="BPMNEdge__7" sourceElement="_2" targetElement="_3">
<omgdi:waypoint x="296.0" y="77.0"/>
<omgdi:waypoint x="296.0" y="140.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_8" id="BPMNEdge__8" sourceElement="_3" targetElement="_4">
<omgdi:waypoint x="297.5" y="195.0"/>
<omgdi:waypoint x="297.5" y="245.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_9" id="BPMNEdge__9" sourceElement="_4" targetElement="_5">
<omgdi:waypoint x="297.5" y="300.0"/>
<omgdi:waypoint x="297.5" y="350.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="_10" id="BPMNEdge__10" sourceElement="_5" targetElement="_6">
<omgdi:waypoint x="291.0" y="405.0"/>
<omgdi:waypoint x="291.0" y="455.0"/>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="0.0" width="0.0" x="0.0" y="0.0"/>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
流程定义的部署
package com.itheima.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import java.io.InputStream;
import java.util.zip.ZipInputStream;
/**
* 流程定义的部署
* activiti表有哪些?
* act_re_deployment 部署信息
act_re_procdef 流程定义的一些信息
act_ge_bytearray 流程定义的bpmn文件及png文件
*/
public class ActivitiDeployment {
//流程定义部署 流程制作出来后要上传到服务器 zip文件更便于上传
public static void main(String[] args) {
//1.创建ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
//3.转化出ZipInputStream流对象
InputStream is = ActivitiDeployment.class.getClassLoader().getResourceAsStream("diagram/holidayBPMN.zip");
//将 inputstream流转化为ZipInputStream流
ZipInputStream zipInputStream = new ZipInputStream(is);
//3.进行部署
Deployment deployment = repositoryService.createDeployment()
.addZipInputStream(zipInputStream)
.name("请假申请单流程")
.deploy();
//4.输出部署的一些信息
System.out.println(deployment.getName());
System.out.println(deployment.getId());
}
//流程定义部署
/*public static void main(String[] args) {
//1.创建ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到RepositoryService实例
RepositoryService repositoryService = processEngine.getRepositoryService();
//3.进行部署
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("diagram/holiday.bpmn") //添加bpmn资源
.addClasspathResource("diagram/holiday.png")
.name("请假申请单流程")
.deploy();
//4.输出部署的一些信息
System.out.println(deployment.getName());
System.out.println(deployment.getId());
}*/
}
返回值
影响的数据库表
-- 流程定义的部署
-- com.itheima.activiti.ActivitiDeployment
SELECT * FROM ACT_GE_BYTEARRAY;
SELECT * FROM ACT_RE_DEPLOYMENT;
SELECT * FROM ACT_RE_PROCDEF;
Activiti 启动流程实例及分析
流程实例
package com.itheima.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.runtime.ProcessInstance;
/**
* 启动流程实例:
* 前提是先已经完成流程定义的部署工作
*
* 背后影响的表:
* act_hi_actinst 已完成的活动信息
act_hi_identitylink 参与者信息
act_hi_procinst 流程实例
act_hi_taskinst 任务实例
act_ru_execution 执行表
act_ru_identitylink 参与者信息
act_ru_task 任务
*/
public class ActivitiStartInstance {
public static void main(String[] args) {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到RunService对象
RuntimeService runtimeService = processEngine.getRuntimeService();
//3.创建流程实例 流程定义的key需要知道 holiday
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
//4.输出实例的相关信息
System.out.println("流程部署ID"+processInstance.getDeploymentId());//null
System.out.println("流程定义ID"+processInstance.getProcessDefinitionId());//holiday:1:4
System.out.println("流程实例ID"+processInstance.getId());//2501
System.out.println("活动ID"+processInstance.getActivityId());//null
}
}
返回值
影响的数据库表
-- 流程实例
-- com.itheima.activiti.ActivitiStartInstance
SELECT * FROM ACT_HI_ACTINST;
SELECT * FROM ACT_HI_IDENTITYLINK;
SELECT * FROM ACT_HI_PROCINST;
SELECT * FROM ACT_HI_TASKINST;
SELECT * FROM ACT_RU_EXECUTION;
SELECT * FROM ACT_RU_IDENTITYLINK;
SELECT * FROM ACT_RU_TASK;
Activiti 流程任务的查询 处理及分析
任务查询
package com.itheima.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
/**
* 查询当前用户的任务列表
*/
public class ActivitiTaskQuery {
//wangwu完成自己任务列表的查询
public static void main(String[] args) {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Task task = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("wangwu")
.singleResult();
//4.任务列表的展示
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("任务ID:"+task.getId()); //5002
System.out.println("任务负责人:"+task.getAssignee());
System.out.println("任务名称:"+task.getName());
}
//lishi完成自己任务列表的查询
/*public static void main(String[] args) {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
Task task = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("lishi")
.singleResult();
//4.任务列表的展示
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("任务ID:"+task.getId()); //5002
System.out.println("任务负责人:"+task.getAssignee());
System.out.println("任务名称:"+task.getName());
}
*/
//zhangsan任务列表的查询
/* public static void main(String[] args) {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.根据流程定义的key,负责人assignee来实现当前用户的任务列表查询
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("zhangsan")
.list();
//4.任务列表的展示
for(Task task :taskList){
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("任务ID:"+task.getId());
System.out.println("任务负责人:"+task.getAssignee());
System.out.println("任务名称:"+task.getName());
}
}*/
}
任务处理
package com.itheima.activiti;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.TaskService;
import org.activiti.engine.task.Task;
/**
* 处理当前用户的任务
* 背后操作的表:
* act_hi_actinst
act_hi_identitylink
act_hi_taskinst
act_ru_identitylink
act_ru_task
*/
public class ActivitiCompleteTask {
//查询当前用户wangwu的任务并处理掉
public static void main(String[] args) {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.查询当前用户的任务
Task task = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("wangwu")
.singleResult();
//4.处理任务,结合当前用户任务列表的查询操作的话,任务ID:task.getId()
taskService.complete(task.getId());
//5.输出任务的id
System.out.println(task.getId());
}
//lishi完成自己的任务
/*public static void main(String[] args) {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.处理任务,结合当前用户任务列表的查询操作的话,任务ID:5002
taskService.complete("5002");
}*/
//zhangsan完成自己的任务
/*public static void main(String[] args) {
//1.得到ProcessEngine对象
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//2.得到TaskService对象
TaskService taskService = processEngine.getTaskService();
//3.处理任务,结合当前用户任务列表的查询操作的话,任务ID:2505
taskService.complete("2505");
}*/
}
任务处理分析
影响到的数据库表
-- com.itheima.activiti.ActivitiCompleteTask
SELECT * FROM act_hi_actinst
SELECT * FROM act_hi_identitylink
SELECT * FROM act_hi_taskinst
SELECT * FROM act_ru_identitylink
SELECT * FROM act_ru_task
Activiti 进阶学习——网关
Activit各个网关使用简单介绍
网关用来控制流程的流向(或像BPMN 2.0里描述的那样,流程的tokens。) 网关可以消费也可以生成token。网关显示成菱形图形,内部有有一个小图标。 图标表示网关的类型。
排他网关 Exclusive Gateway
排他网关又叫互斥网关,条件计算为true的顺序流会被选择继续流程,有且只有一条出口,如果出现多个条件为true,则会默认选择第一条true来执行,如果没有条件输出true,流程走到这一步的时候则会报错
case:
当你的流程出现这样的场景:请假申请,三天以内,部门经理审批流程就结束了,三天以上需要总经理,这个时候就需要排他网关
注意这里的外出顺序流 与BPMN 2.0通常的概念是不同的。通常情况下,所有条件结果为true的顺序流 都会被选中,以并行方式执行,但排他网关只会选择一条顺序流执行。 就是说,虽然多个顺序流的条件结果为true, 那么XML中的第一个顺序流(也只有这一条)会被选中,并用来继续运行流程。 如果没有选中任何顺序流,会抛出一个异常。
并行网关 Parallel Gateway
它可以将执行分支(fork
)为多条路径,也可以合并(join
)执行的多条入口路径。简单点说就是,并行网关它可以有多条入口和出口,并且在合并入口处会等待其他分支执行完,然后再执行后面的流程,另外,需要注意的是,并行网关没有条件
的概念,即便设置了条件,它会自动忽略
case:
当出现这样的场景:请假申请开始,需要项目经理和部门经理都审批,两者没有前后需要两个人全部审批才能进入下个节点。这个时候就需要并行网关
-
分支: 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
-
汇聚: 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
包容网关 Inclusive Gateway
用内部带有’圆圈’图标的网关(菱形)表示。
这个网关比较特殊,它集合了前面两个网关的特点,一句话来说就是,它既可以像排他网关那样设置条件,也能像并行网关这样设置多条分支并行执行,在上图中,当流程启动后,如果流程变量paymentReceived == false
且shipOrder == true
,将会创建两个任务。如果只有一个流程变量等于true
,则只会创建一个任务。如果没有条件计算为true
,会抛出异常,并可通过指定出口顺序流
举个例子:启动流程的时候传入下面的参数,执行流程。
HashMap<String, Object> variableMap = new HashMap<String, Object>();
variableMap.put("receivedPayment", true);
variableMap.put("shipOrder", true);
在上面的例子中,只有ship order 一个任务会被创建,当这个任务完成后,第二个包容网关会合并这两个执行,并且由于只有一条出口顺序流,不会再创建并行执行路径,只会激活Archive Order任务继续后面的流程。
case:
当出现这样的场景:你申请请假,如果是病假,找项目经理审批,如果不是找部门经理审批。但是他俩不管谁签完都可以进行个节点。说白了就像是排他跟并行的合体
-
分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
-
汇聚: 所有并行分支到达包含网关,会进入等待章台, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
注意,如果同一个包含节点拥有多个进入和外出顺序流, 它就会同时含有分支和汇聚功能。 这时,网关会先汇聚所有拥有流程token的进入顺序流, 再根据条件判断结果为true的外出顺序流,为它们生成多条并行分支。
基于事件的网关 Event-based Gateway
这个网关相比其他三个网关,用的相对没那么多,主要还是根据业务需求吧
该网关在执行出口处,要连接一个捕获中间事件。当流程执行到该网关时,流程类似处于等待的状态,此时执行被暂停,并创建一个事件订阅
需要订阅什么事件。基于下列约束:
- 一个基于事件的网关,必须有两条或更多的出口顺序流。
- 基于事件的网关,只能连接至intermediateCatchEvent(捕获中间事件)类型的元素(Activiti不支持基于事件的网关后,连接接收任务,Receive Task)。
- 连接至基于事件的网关的intermediateCatchEvent,必须只有一个入口顺序流。
当然,新手看到了这里可能还是不知道什么是事件网关,它到底有什么用处呢?下面,我用一个例子来解释一下:
上图使用了事件网关、定时器事件,信号捕捉事件,当启动流程后,流程实例订阅alert信号事件,并创建一个 5分钟后触发的定时器。这使得流程引擎等待5分钟,并等待信号事件。如果信号在5分钟内触发,则定时器会被取消,执行沿着信号继续。如果信号未被触 发,执行会在定时器到时后继续,并取消信号订阅。
一句话概括事件网关,如果你提交了请假申请,此时流程处于等待状态,当某个事件触发时,流程走到用户任务,办理了你的请假申请,那么等待会被取消,如果超过1天,还是没有触发某个事件,那么你的请假单直接跳过你的上级,直接往后面的流程走
case:
基于事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。 与此同时,会为每个外出顺序流创建相对的事件订阅。
注意基于事件网关的外出顺序流和普通顺序流不同。这些顺序流不会真的"执行"。 相反,它们让流程引擎去决定执行到基于事件网关的流程需要订阅哪些事件。 要考虑以下条件:
-
基于事件网关必须有两条或以上外出顺序流。
-
基于事件网关后,只能使用intermediateCatchEvent类型。 (activiti不支持基于事件网关后连接ReceiveTask。)
-
连接到基于事件网关的intermediateCatchEvent只能有一条进入顺序流。
Activiti 转载文章
整理从其他网站转载比较好的文章
Activiti7的核心详解
Activiti7的核心详解
流程定义
流程定义是线下按照bpmn2.0标准去描述业务流程,通常使用activiti-explorer(web控制台)或activiti-eclipse-designer插件对业务流程进行建模,这两种方式都遵循bpmn2.0标准。
使用activiti-desinger设计业务流程,会生成.bpmn文件
.png图片需手动生成,在IDEA中步骤如下:
- 1)将holiday.bpmn文件改为扩展名xml的文件名称:holiday.xml
- 2)在holiday.xml文件上面,点右键并选择Diagrams菜单,再选择Show BPMN2.0 Designer...
- 3)点击Export To File的小图标,选择好保存图片的位置
- 4)中文乱码的解决
打开IDEA安装路径,找到如下的安装目录,在idea64.exe.vmoptions文件的最后一行追加一条命令:-Dfile.encoding=UTF-8
-Xms128m
-Xmx750m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.1.4\bin\JetbrainsCrack-2.8-release-enc.jar
-Dfile.encoding=UTF-8
一定注意,不要有空格,否则重启IDEA时会打不开,然后重启IDEA,把原来的png图片删掉,再重新生成,即可解决乱码问题
1.什么是流程定义部署
将线下定义的流程部署到activiti数据库中,这就是流程定义部署,通过调用activiti的api将流程定义的bpmn和png两个文件一个一个添加部署到activiti中,也可以将两个文件打成zip包进行部署。
单个文件部署方式:
/**
* 流程定义的部署
* activiti表有哪些?
* act_re_deployment 流程定义部署表,记录流程部署信息
* act_re_procdef 流程定义表,记录流程定义信息
* act_ge_bytearray 资源表(bpmn文件及png文件)
*/
@Test
public void createDeploy() {
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("diagram/holiday.bpmn")//添加bpmn资源
.addClasspathResource("diagram/holiday.png")
.name("请假申请单流程")
.deploy();
log.info("流程部署id:" + deployment.getName());
log.info("流程部署名称:" + deployment.getId());
}
2.流程定义查询
@Test
public void queryProcessDefinition() {
String processDefinitionKey = "holiday";
RepositoryService repositoryService = processEngine.getRepositoryService();
//查询流程定义
ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
List<ProcessDefinition> list = processDefinitionQuery.processDefinitionKey(processDefinitionKey)
.orderByProcessDefinitionVersion().desc().list();
for (ProcessDefinition processDefinition : list) {
log.info("------------------------");
log.info("流 程 部 署id:" + processDefinition.getDeploymentId());
log.info("流程定义id:" + processDefinition.getId());
log.info("流程定义名称:" + processDefinition.getName());
log.info("流程定义key:" + processDefinition.getKey());
log.info("流程定义版本:" + processDefinition.getVersion());
}
}
3.流程定义删除
/**
* 删除已经部署成功的流程定义
* 背后影响的表:
* act_ge_bytearray
* act_re_deployment
* act_re_procdef
*/
@Test
public void deleteDeployment() {
String deploymentId = "a2833cf7-10bb-11ea-9ac9-00155d65d6c0";
RepositoryService repositoryService = processEngine.getRepositoryService();
//删除流程定义,如果该流程定义已有流程实例启动则删除时出错
// repositoryService.deleteDeployment(deploymentId);
//设置true 级联删除流程定义,即使该流程有流程实例启动也可以删除,设置为false非级别删除方式,如果流程
repositoryService.deleteDeployment(deploymentId, true);
}
流程实例
1.什么是流程实例
参与者(可以是用户也可以是程序)按照流程定义内容发起一个流程,这就是一个流程实例。
2.启动流程实例
流程定义部署在activiti后,就可以在系统中通过activiti去管理该流程的执行,执行流程表示流程的一次执行。比如部署系统请假流程后,如果某用户要申请请假这时就需要执行这个流程,如果另外一个用户也要申请请假则也需要执行该流程,每个执行互不影响,每个执行是单独的流程实例。
@Test
public void startProcessInstance() {
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday");
log.info("流程定义ID:" + processInstance.getProcessDefinitionId());
log.info("流程实例ID:" + processInstance.getId());
}
3.Businesskey(业务标识)
启动流程实例时,指定的businesskey,就会在act_ru_execution #流程实例的执行表中存储businesskey。
Businesskey:业务标识,通常为业务表的主键,业务标识和流程实例一一对应。业务标识来源于业务系统。存储业务标识就是根据业务标识来关联查询业务系统的数据。 比如:请假流程启动一个流程实例,就可以将请假单的id作为业务标识存储到activiti中,将来查询activiti的流程实例信息就可以获取请假单的id从而关联查询业务系统数据库得到请假单信息。
@Test
public void startProcessInstance() {
RuntimeService runtimeService = processEngine.getRuntimeService();
//启动流程实例,同时还要指定业务标识businessKey 它本身就是请假单的id
//第一个参数:是指流程定义key
//第二个参数:业务标识businessKey
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday", "1001");
String businessKey = processInstance.getBusinessKey();
System.out.println("businessKey:" + businessKey);
}
4.操作数据库表
ct_ru_execution #流程实例执行表,记录当前流程实例的执行情况,一个流程实例运行完成,此表中与流程实例相关的记录删除。
ct_ru_task #任务执行表,记录当前执行的任务,启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。
ct_ru_identitylink #任务参与者,记录当前参与任务的用户或组
ct_hi_procinst #流程实例历史表,流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。
ct_hi_taskinst #任务历史表,记录所有任务,开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。
ct_hi_actinst #活动历史表,记录所有活动
5.查询流程实例
@Test
public void queryProcessInstance() {
String processDefinitionKey = "holiday";
RuntimeService runtimeService = processEngine.getRuntimeService();
List<ProcessInstance> list = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey).list();
for (ProcessInstance processInstance : list) {
System.out.println("-----------------------------");
System.out.println("流程实例id:" + processInstance.getProcessInstanceId());
System.out.println("所属流程定义id:" + processInstance.getProcessDefinitionId());
System.out.println("是否执行完成:" + processInstance.isEnded());
System.out.println("是否暂停:" + processInstance.isSuspended());
}
}
个人任务
1.分配任务负责人
- 1)固定分配
在进行业务流程建模时指定固定的任务负责人,在properties视图中,填写Assignee项为任务负责人。
由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照bpmn的配置去分配任务负责人。
- 2)表达式分配
Activiti使用UEL(统一表达式语言)表达式,activiti支持两个UEL表达式:UEL-value和UEL-method
assignee这个变量是activiti的一个流程变量
UEL-method方式如下:
ldapService是spring容器的一个bean,findManagerForEmployee是该bean的一个方法,emp是activiti流程变量,emp作为参数传到ldapService.findManagerForEmployee方法中。
其它:表达式支持解析基础类型、bean、list、array和map,也可作为条件判断。如下:${order.price > 100 && order.price < 250}
使用流程变量分配任务
@Test
public void assignVariables() {
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置assignee的取值,用户可以在界面上设置流程的执行人
Map<String, Object> map = new HashMap<String, Object>();
map.put("assignee0", "张三");
map.put("assignee1", "李四");
map.put("assignee2", "王五");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday2", map);
System.out.println(processInstance.getProcessInstanceId());
}
- 3)监听器分配
任务监听器是发生对应的任务相关事件时执行自定义java逻辑或表达式
- Create:任务创建后触发
- Assignment:任务分配后触发
- Delete:任务完成后触发
- All:所有事件发生都触发
定义任务监听类,且类必须实现org.activiti.engine.delegate.TaskListener接口
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setAssignee("赵六");
}
}
- 4)查询任务
@Test
public void findPersonalTaskList() {
String processDefinitionKey = "holiday2";
String assignee = "赵六";
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables().taskAssignee(assignee).list();
for (Task task : taskList) {
System.out.println("--------------------------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
流程变量
1.什么是流程变量
流程变量就是activiti在管理工作流时根据管理需要而设置的变量。
比如在请假流程流转时如果请假天数大于3天则由总经理审核,否则由人事直接审核,请假天数就可以设置为流程变量,在流程流转时使用。
2.流程变量作用域
流程变量的作用域默认是一个流程实例(processInstance),也可以是一个任务(task)或一个执行实例(execution),这三个作用域流程实例的范围最大,可以称为global变量,任务和执行实例仅仅是针对一个任务和一个执行实例范围,范围没有流程实例大,称为local变量。
3.流程变量的使用方法
-
1)设置流程变量
-
2)通过UEL表达式使用流程变量
4.使用Global变量控制流程
员工创建请假申请单,由部门经理审核,部门经理审核通过后请假3天及以下由人事经理直接审核,3天以上先由总经理审核,总经理审核通过再由人事经理存档。
请假天数大于3连线条件
- 1)启动流程时设置
在启动流程时设置流程变量,变量的作用域是整个流程实例。通过map<key,value>设置流程变量,map中可以设置多个变量,这个key就是流程变量的名字。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Holiday implements Serializable {
//必须实现序列化接口serializable,为了防止由于新增字段无法反序列化,需要生成serialVersionUID
private static final long serialVersionUID = 5707634407289856169L;
private Integer id;
private String holidayName;//申请人的名字
private Date beginDate;//开始时间
private Date endDate;//结束日期
private Float num;//请假天数
private String reason;//事由
private String type;//请假类型
}
/**
* 启动流程时设置流程变量
* act_ge_bytearray
* act_ru_variable
*/
@Test
public void startProcessInstance() {
String processDefinitionKey = "holiday4";
Holiday holiday = new Holiday();
holiday.setNum(5F);
//定义流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("holiday", holiday);
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
System.out.println("流程实例id:" + processInstance.getProcessInstanceId());
}
- 2)任务办理时设置
在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的key在流程实例中已存在相同的名字则后设置的变量替换前边设置的变量。
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
String key = "holiday4";
String assignee = "张三";
Task task = taskService.createTaskQuery().processDefinitionKey(key).
taskAssignee(assignee).singleResult();
Holiday holiday = new Holiday();
holiday.setNum(4F);
//初始化一些参数
Map<String, Object> map = new HashMap<>();
map.put("holiday", holiday);
if (task != null) {
taskService.complete(task.getId(), map);//完成任务时,设置流程变量的值
System.out.println("任务执行完毕");
}
}
- 3)通过当前流程实例设置
@Test
public void setGlobalVariableByExecutionId() {
String executionId = "f789207c-0aa2-11ea-9a53-00155d65d6c0";
RuntimeService runtimeService = processEngine.getRuntimeService();
Holiday holiday = new Holiday();
holiday.setNum(3F);
runtimeService.setVariable(executionId, "holiday", holiday);
System.out.println(runtimeService.getVariable(executionId, "holiday"));
}
executionId必须当前未结束流程实例的执行id,通常此id设置流程实例的id。也可以通过runtimeService.getVariable()获取流程变量
- 4)通过当前任务设置
@Test
public void setGlobalVariableByTaskId() {
String taskId = "4d47161e-0aa4-11ea-aea8-00155d65d6c0";
TaskService taskService = processEngine.getTaskService();
Holiday holiday = new Holiday();
holiday.setNum(5F);
Map<String, Object> variables = new HashMap<>();
variables.put("holiday", holiday);
taskService.setVariable(taskId, "holiday", holiday);
System.out.println(taskService.getVariable(taskId, "holiday"));
}
5.设置local流程变量
- 1)任务办理时设置
任务办理时设置local流程变量,当前运行的流程实例只能在该任务结束前使用,任务结束该变量无法在当前流程实例使用,可以通过查询历史任务查询。
@Test
public void completeTaskLocal() {
String taskId = "676c8a5d-0b35-11ea-a44a-00155d65d6c0";
Map<String, Object> variables = new HashMap<>();
Holiday holiday = new Holiday();
holiday.setNum(3F);
variables.put("holiday", holiday);
TaskService taskService = processEngine.getTaskService();
//设置local变量,作用域为该任务
taskService.setVariablesLocal(taskId, variables);
taskService.complete(taskId);
}
设置作用域为任务的local变量,每个任务可以设置同名的变量,互不影响。
6.查询历史流程变量
@Test
public void queryHistoricLocalVariables() {
HistoryService historyService = processEngine.getHistoryService();
HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
List<HistoricTaskInstance> list = historicTaskInstanceQuery.includeTaskLocalVariables()
.finished().list();
for (HistoricTaskInstance hti : list) {
System.out.println("============================");
System.out.println("任务id:" + hti.getId());
System.out.println("任务名称:" + hti.getName());
System.out.println("任务负责人:" + hti.getAssignee());
System.out.println("任务local变量:" + hti.getTaskLocalVariables());
}
}
组任务
1.Candidate-users候选人
在流程定义中在任务结点的assignee固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性差。针对这种情况可以给任务设置多个候选人,可以从候选人中选择参与者来完成任务。
在流程图中任务节点的配置中设置candidate-users(候选人),多个候选人之间用逗号分开。
2.办理组任务
- 1)用户查询组任务
@Test
public void findGroupTaskList() {
String processDefinitionKey = "holiday4";
String candidateUser = "李四";
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery().processDefinitionKey(processDefinitionKey)
.taskCandidateUser(candidateUser).list();//根据候选人查询
for (Task task : taskList) {
System.out.println("---------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
}
}
- 2)用户拾取组任务
/**
* 用户拾取组任务,该任务变为自己的个人任务
*/
@Test
public void claimTask() {
TaskService taskService = processEngine.getTaskService();
String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
String userId = "李四";
//校验该用户有没有拾取任务的资格
Task task = taskService.createTaskQuery().taskId(taskId)
.taskCandidateUser(userId).singleResult();//根据候选人查询
if (task != null) {
taskService.claim(taskId, userId);
System.out.println("任务拾取成功");
}
}
- 3)用户办理个人任务
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processDefinitionKey("holiday4")
.taskAssignee("张三").singleResult();
if (task != null) {
taskService.complete(task.getId());
System.out.println("用户任务执行完毕...");
}
}
- 4)归还组任务
如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人
/**
* 归还组任务,由个人任务变为组任务,还可以进行任务交接
*/
@Test
public void setAssigneeToGroupTask() {
TaskService taskService = processEngine.getTaskService();
String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
String userId = "李四";
//校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
//如果设置为null,归还组任务,该任务没有负责人
taskService.setAssignee(taskId, null);
}
}
- 5)任务交接
/**
* 任务交接,前提要保证当前用户是这个任务的负责人,这时候他才可以有权限去将任务交接给其他候选人
*/
@Test
public void setAssigneeToCandidateUser() {
TaskService taskService = processEngine.getTaskService();
String taskId = "a7ca3538-0b69-11ea-a1d2-00155d65d6c0";
String userId = "李四";
//校验userId是否是taskId的负责人,如果是负责人才可以归还组任务
Task task = taskService.createTaskQuery().taskId(taskId)
.taskAssignee(userId).singleResult();
if (task != null) {
//将此任务交给其它候选人办理该任务
String candidateUser = "张三";
//根据候选人和组任务id查询,如果有记录说明该候选人有资格拾取该任务
Task task1 = taskService.createTaskQuery().taskCandidateUser(candidateUser).singleResult();
if (task1 != null) {
taskService.setAssignee(taskId, candidateUser);
}
}
}
网关
1.排他网关
- 1)什么是排他网关
排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,注意,排他网关只会选择一个为true的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行)
- 2)流程定义
在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断请假天数是否大于3天,另一条是判断请假天数是否小于等于3天。
- 3)测试
流程定义部署-->启动流程设置流程变量-->执行任务
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processDefinitionKey("holiday5_1")
.taskAssignee("lisi").singleResult();
if (task != null) {
taskService.complete(task.getId());
System.out.println("用户任务执行完毕...");
}
}
2.并行网关
- 1)什么是并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
-
fork分支
并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
-
join汇聚
所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后,流程就会通过汇聚网关。
如果同一个并行网关有多个进入和多个外出顺序流,它就同时具有分支和汇聚功能。这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略
财务结算和行政考勤是两个execution分支,在act_ru_execution表有两条记录分别是财务结算和行政考勤,act_ru_execution还有一条记录表示该流程实例。待财务结算和行政考勤任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。
- 2)流程定义
- 3)测试
流程定义部署-->启动流程设置流程变量-->执行任务
@Test
public void completeTask() {
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery().processDefinitionKey("holidayParallel")
.taskAssignee("zhangsan").singleResult();
if (task != null) {
taskService.complete(task.getId());
System.out.println("用户任务执行完毕...");
}
}
3.包含网关
- 1)什么是包含网关
包含网关可以看做是排他网关和并行网关的结合体。和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。 包含网关的功能是基于进入和外出顺序流的:
-
分支
所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行,会为每个顺序流创建一个分支
-
汇聚
所有并行分支到达包含网关,会进入等待状态,直到每个包含流程token的进入顺序流的分支都到达。这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网关继续执行。
-
2)流程定义
企业体检流程,公司全体员工进行常规项检查、抽血化验,公司管理层除常规检查和抽血化验还要进行增加项检查。
员工类型:通过流程变量userType来表示,如果等于1表示普通员工,如果等于2表示领导
- 3)测试
Liferay-Activiti 功能介绍 (新版Liferay7基本特性)
一句话简介
Liferay是世界领先的开源企业门户(也可作为综合门户),是**最强大(没有之一)**的JAVA开源门户,在Gartner和Forrester和评价非常高,近几年已经超越了微软门户Sharepoint。
Activiti是著名开源jBPM的分支,创始人Tom Baeyens就是JBoss jBPM的项目架构师,以及jBPM另一位架构师Joram Barrez,一起创建的Alfresco公司开发的BPM工作流平台。
商业模式
Liferay和Activiti都分社区开源版 、企业版2种,内核代码都是一样的,区别在于收费插件和企业服务支持,从社区版到商业版可无缝转换。
为什么要选择Liferay和Activiti
作为IT技术负责人的你是否有以下选型烦恼:
- 选国内闭源产品(比如蓝凌OA),受制于大厂,费用高,周期慢,因产品框架限制不一定能实现较特殊的需求;
- 选的技术太旧(比如东软开发平台),开发出的项目,开发扩展困难,和其他业务系统集成困难,开发人员抵触;
- 选国内假开源产品,技术能力参差不齐,产品包装的很牛,但用起来各种细节问题;
为什么选基于Liferay + Activiti 开发企业门户产品
- Liferay是最优秀的开源企业门户,使用企业数千,使用者数千万,产品稳定性和先进度有保障,功能极具扩展性;
- Liferay移动端界面支持好(H5自适应);
- Liferay自身的BPM较弱,所以需要集成较强的BPM,而Activiti是非常优秀的BPM产品,
- Activiti 使用者众多,功能灵活,上手难度不大,它的前身jBPM在jBoss控制下越来越重,所以不选jBPM;
- Activiti BPM满足国际BPMN2.0规范,选用Activiti BPM在流程迁移方面有规范、风险低;
- 有代码,不会受制于人,更可控;
- 开发风险可控,最核心的2个功能(门户平台和BPM)依托成熟开源,稳定性不用担心; 总而言之就是核心风险可控,我们只需要关注开发连接插件,即可形成产品
我们要基于Liferay + Activiti 做什么
1、账户迁移工具或脚本
如果涉及旧OA或门户迁移,必须开发迁移工具;
2、组织架构迁移工具或脚本
组织架构是非常重要的基础数据;
3、权限组的迁移工具或脚本(非必须)
如果涉及旧OA或门户,权限组最好能批量迁移;
4、Activiti BPM集成Portlet(重点、难度较大)
Liferay自身的BPM较弱,所以需要集成较强Activiti,通过Portlet集成,Portlet其实就是Liferay规范化的servlet 。
5、Activiti BPM集成Liferay权限体系(重点、难度较大)
实现Liferay和Activiti 的权限通讯。
有三个方案:http://www.kafeitu.me/activiti/2012/04/23/synchronize-or-redesign-user-and-role-for-activiti.html
需要慎重选择方案。
6、Activiti-Designer 流程设计器的中国化改造(重点、难度较大)
Activiti 的表单设计器非常强大,但需要改造符合中国企业使用习惯;
Liferay自身的简单BPM估计能实现业务流程需求的简单需求(可能占50%),其余的需要Activiti 实现。
Activiti 中提供了 2 种可视化流程设计器:Web Application 形式的 Activiti Modeler 和 胖客户端形式的流程编辑器Activiti-Designer,必须先慎重选型。
7、开发开放API,方便业务系统调用
这部分可基于Liferay WebAPI扩展包装,难度不大;
Liferay 功能介绍
1、创建站点
管理员可创立多站点。
应对集团多分支的组织架构(按组织分割),或者垂直多用途的门户(按用途分割),比如内外门户、集中知识门户、文档中心等。
配置新站点
可以设置站点的语言、风格、权限等
2、管理页面
创建页面
选择页面的栏式
在页面添加应用
比如添加内置的wiki和最新blog列表应用
配置页面权限
注意:里面的角色(role)是可自行配置的。
创建页面内容:HTML5类型
通过WYSIWYG Web editor,添加文本、图片、视频内容等
创建页面内容:结构化元数据(Metadata )类型
支持的元数据(Metadata )类型有:
- Boolean: true (checked) or false (unchecked)
- Date: 日期
- Decimal: 数字,支持小数点位数;
- Documents and Media: 文档库类型,文档和媒体;
- Geolocation: 地理位置,用于移动端;具体可见https://dev.liferay.com/discover/portal/-/knowledge_base/7-0/geolocating-assets
- HTML: 使用WYSIWYG editor编辑的内容;
- Image: 图片;
- Integer: 整数;
- Link to Page: 链接;
- Number: 类似于Decimal,可能是big decimal,有待确认;
- Radio: 多选按钮;
- Select: 下拉选择按钮;
- Separator: 分隔栏;
- Text: 当行文本;
- Text Box: 多行文本,类似Java控件textArea;
控制页面发布展示和过期时间
scheduling web-content publication
内容搜索
liferay的内容搜索异常强大,不多赘述。
3、管理用户、组织、权限
组织管理
新建组织,并设置上级组织
Type :regular organization or a location,如果选location则没有下级组织
添加用户
用户组
用户组是剥离组织架构的独立逻辑分组,一个用户可以分配给多个用户组。
例如,公司的办公室/部门结构既可以通过组织机构进行建模。也能创建用户组,比如:
- 副总
- 办公室经理
- 会计
- JAVA开发人员
- 博客管理员
- 等等...
一个用户组可以创建一群人独立于他们的组织机构,使它更容易分配一个或多个角色,比如一次性分配权限给所有的JAVA开发人员。 向属于用户组的用户提供预定义的公共或私有页面。例如:
- 博客管理员用户组的成员可以创建管理博客
- 会计用户组的成员可以访问财务页面和应用
- ......
下面是创建(博客管理员)用户组的演示页面:
角色
角色是用来定义一个特定功能的权限(根据特定范围)
一个角色基本上只是一个定义了一个功能的权限集合,如留言板管理员。这有点容易和用户组混淆,但实际上角色还有范围的管辖权限控制
可以细化到4种范围类型选择(Regular role、Site role、Organization role、Team)
导航到控制面板,然后单击“角色”,可以让您创建角色、分配权限给他们,将用户分配给角色。
一个角色仅具有一定管辖范围的作用。比如
-
Site role:一个“企业知识站点留言板管理员”角色只能在一个特定站点(即”企业知识站点“)内管理留言板内容;
-
Team role:一个"CVTalk开发团队"角色只能在一个特定团队站点(即”CVTalk开发“团队)内发布内容;Regular role、Site role、Organization role、Team四种类型的角色之间的范围差异可以描述如下:
-
规则角色:权限在门户级别定义,并在门户级别应用。
-
站点角色:权限被定义在门户级别,并应用到一个特定的站点。
-
组织角色:权限在门户级别定义,并应用于一个特定的组织。
-
团队:权限被定义在一个特定的站点内,并被分配在特定的团队站点内。
用户验证
支持的用户认证方式:
- Liferay自带认证
- LDAP
- SSO
- CAS
- NTLM
- OpenId
- Crowd
- Open SSO
- SiteMinder
- Shibboleth
- SAML
4、文档管理
发布文件上传
发布元数据(Metadata )数据集
就是通过动态字段建立的数据
分布式集群文档存储
Liferay Portal的文件和媒体文件可以存储在许多不同的服务器或其他媒介方式,
默认情况下,Liferay Portal使用文档库中存储的选择被称为简单的文件存储在文件系统上。
您也可以使用一个完全不同的方法来存储文档和媒体文件:
- CMIS存储(Content Management Interoperability Services 内容管理交互服务):使用一个单独的从Liferay存储文件系统。
- DBstore(数据库):使用数据库存储文件。
- JCRstore(Java内容库):将文件分布式存储到兼容JSR-170规范的第三方厂商文档库。商业的有IBM、EMC、SAP、Macromedia的产品,开源的产品也不少,一线的CMS开源基本都符合JSR-170规范,比如Magnolia、eXo、Apache Jackrabbit、Liferay本身也符合,基于Magnolia存储是最优的方案,这样就把Magnolia作为一个用途专一的分布式存储仓库;
- S3store(亚马逊简单存储):使用亚马逊的云存储解决方案。
- 其他定制方案:这需要您自行实现Liferay开发接口,实现更大的灵活性;
文件同步客户端
有些类似百度云客户端,在授权的情况下,可以把文档库同步到个人电脑硬盘。
详情:Using Liferay Sync on Your Desktop
移动端访问文档库
移动端编辑文件
5、企业协作
博客
论坛
用户心情
Wiki
略
书签
略
企业微博
通知
投票
集成XMPP Web聊天
通过集成jabber方式和openfire通讯
集成Email
Liferay包含一个邮件插件,可以作为web邮件客户端
5、管理应用
Liferay的强大之处在于不仅内置应用繁多,它还提供易于开发的扩展体系,提供即插即用的平台支持,海量应用商店支持。
管理应用
应用商店
应用商店有数千个免费或商业插件
应用的类别:
- OSGi Modules
- Portlets
- Web Plugins
- Templates
- Themes
以上内容为官方文档的微缩版:The Liferay Distinction
6、企业功能
涉及用户数据列表、表单模板、高级表单定制、工作流。
放在下一篇介绍。
多谢观看!
Liferay-Activiti 企业特性功能介绍(新版Liferay7)
前言
如果你是开发者
你已经是多少次开发一个项目,一次次的用一些框架,一次次的写类似的重复的代码,一次次建表\写类和方法\写HTML\CSS\JAVASCRIPT,一次次测试,一次次的写Bug。。。
如果有一个平台,提供基线的框架,可以是应用程序\网站,支持移动端,不必一次次开发无法重用,一次次造就信息孤岛。
那么试试Liferay。 很多的应用开箱即用,如CMS\博客\企业协作\动态表单\良好的组织架构和权限体系。
先别急着上船,你得接受和适应Liferay的扩展框架体系,比如Portlet,还有应用程序显示模版的机制,这有些代价,但对于真正的JavaEE开发者,并不会困难,另外你还会学到另你获益终身的设计模式思想,OK,想好了就上船吧,这必将是愉快的旅程。
如果你是IT主管
可能选型选择开源不容易,哪怕是Liferay如此成功的产品,实际上Liferay的企业服务费用不算低,要节省成本用社区版,必然要有好的开发服务团队。
这必须非常谨慎,诚然,Liferay还有软肋,这个是其产品定位造成的,比如:
- BPM进来,如Activiti;
- 还有中国式的组织架构;
- 项目数据迁移,比如员工,组织架构数据;
- 和其他系统的集成;
- 还有一些中国式的操作习惯改造;
- 国内的甲方公司很难有这样的开发实力,这需要技术和耐力相结合,虽然只是开发连接器插件,但做好并不容易,开发技能栈必须全面。
言归正传,介绍Liferay的企业特性。
Liferay7架构 :
模块:
权限和认证体系
Liferay支持 权限、组、用户、团队、组织架构 的权限控制体系,详情见上一篇文章
支持的用户认证方式:
- Liferay自带认证
- LDAP
- SSO
- CAS
- NTLM
- OpenId
- Crowd
- Open SSO
- SiteMinder
- Shibboleth
- SAML
开发扩展方式
Liferay几乎什么都可以定制。应用程序接口可以重新设计,整个用户界面可以定制为主题,菜单项可以添加或删除...
所有的应用程序及扩展,是建立在liferay自定义分布式部署模块(典型的jar文件),用Java开发人员熟悉的方式开发,编译,定义模板,资源,和一些元数据。
它遵循一套非常强大的标准称为OSGi。多模块可以相互依赖、相互沟通,实时部署,不用重启服务器(热部署)。
模块可以有一个或多个组件。创建一个组件和Java类开发一样简单。
一个组件是一个更大的应用程序的最小的构建块,并且应用程序本身是由许多小的组件组成的,就是以重复使用的堆积木的方式开发系统。
组件由组件容器管理,该容器提供安装和激活。组件提供服务,通过一个强大的依赖管理系统,在运行时自动处理。
您可以编写组件以提供新的服务或以重写现有的服务,容器管理所有一切。Liferay是一个激动人心的平台,使开发人员更高效。
扩展方式介绍:
- OSGi Modules
- 建立Liferay Portal Web应用程序最常用的方法是用一个portlet;
- 利用Liferay的移动SDK,开发移动应用;
- 开发主题(theme);
- 开发MVCPortlet
可以使用现有框架,如Struts、Spring。使用Service Builder,很容易创建后台数据库表、对象关系映射。
它还可以生成JSON或SOAP的Web服务,为开发者提供完整的开发元素用于存储和检索数据,用于Web或移动客户端。
工作流
Liferay自身支持的BPM工作流:
-
Kaleo,集成Liferay表单的内置工作流,极简单,没有图形设计器;
-
jBPM3, 现在都6.4版了,有点跟不上时代;
另外还有两个外接集成插件:
集成Activiti5.11版:https://github.com/emdev-limited/activiti-liferay ,功能还不够完善,已经3年没有更新;
集成Bonita BPM:http://www.bonitalife.org ,Bonita 也是一款强大的BPM,这个插件还待试用评;
Activiti是完全实现BPMN 2.0规范的工作流引擎,它对比jBPM的优势是轻量级,容易集成,可单机或集群部署。
目前Activiti支持的数据库:
- DB2
- H2
- Oracle
- MySQL
- MS SQL
- PostgreSQL
Activiti的集成开发方式有:
- Standalone JDBC :通过Portlet方式,加入Activiti的jar包,直接集成;
- Spring:通过Spring MVC方式集成;
- JTA:对J2EE异构分布式数据源的集成开发;
- Web API:通过restful接口进行集成;
所有的开发重点在UI的集成、用户权限的集成、单点登录、流程设计器、控制面板、和Liferay表单的集成,待办已办待阅面板开发,工程较大。
目前Activiti只有用户组、用户上级、用户三种权限概念(用于流程节点分配);
扩展更复杂更集成业务系统的权限机制的方案: synchronize-or-redesign-user-and-role-for-activiti
一些企业扩展
企业社交代理
通过整合OAuth服务作为一个HTTP代理服务器的应用程序,该插件为您的应用程序提供了一个安全的令牌,可以将类似的网站推特,LinkedIn、微信;
图表插件
一个使用 Liferay 和 Lucene 实现企业门户智能帮助机器人的方法
很有意思
使用 Liferay 和 Lucene 实现企业门户智能帮助机器人
企业门户智能帮助机器人总体架构图
简易敏捷插件
用户反馈插件
Contour Dispatch – The Feedback Portlet
相册插件
MongoDB CRUD 简单应用插件
features.
- Insert New Record (in Collection/Table).
- Update inserted records.
- Delete Single/Multiple Record(s).
- Sorting (Ascending/Descending)
- Searching (AND search / OR search)
Liferay MongoDB CRUD Application
集成导入插件
使用这个Web服务插件,你可以从其他平台导入内容到Liferay中
更多插件
在应用商店:Marketplace
选择Liferay的好处是除了自带的强大功能,和海量商店应用,它还是可以灵活开发扩展的平台。
比较Activiti中三种不同的表单及其应用
开篇语
这个恐怕是初次接触工作流最多的话题之一了,当然这个不是针对Activiti来说的,每个工作流引擎都会支持多种方式的表单。目前大家讨论到的大概有三种。
- 动态表单
- 外置表单
- 普通表单
具体选择哪种方式只能读者根据自己项目的实际需求结合现有技术或者架构、平台选择!!!
1.动态表单
这是程序员 最喜欢
的方式,同时也是客户 最讨厌
的……因为表单完全没有布局,所有的表单元素都是顺序输出显示在页面。
此方式需要在流程定义文件( bpmn20.xml
)中用 activiti:formProperty
属性定义,可以在开始事件( Start Event
)和 Task
上设置,而且支持变量自动替换,语法就是 UEL
。
<startevent id="startevent1" name="Start">
<extensionelements>
<activiti:formproperty id="name" name="Name" type="string"></activiti:formproperty>
</extensionelements>
</startevent>
<usertask id="usertask1" name="First Step">
<extensionelements>
<activiti:formproperty id="setInFirstStep" name="SetInFirstStep" type="date"></activiti:formproperty>
</extensionelements>
</usertask>
下面是一个简单的动态表单的单元测试,读者可以下载运行以便更明确执行过程和判断动态表单能不能在企业项目中使用。
- DymaticForm.bpmn
- ProcessTestDymaticForm.java
下载之后复制到eclipse工程里,更改里面的路径配置使用JUnit测试即可。
当流程需要一些特殊处理时可以借助Listener或者Delegate方式实现。
注意:表单的内容都是以key和value的形式数据保存在引擎表中!!!
2.外置表单
这种方式常用于基于工作流平台开发的方式,代码写的很少,开发人员只要把表单内容写好保存到 .form
文件中即可,然后配置每个节点需要的表单名称( form key
),实际运行时通过引擎提供的 API
读取 Task
对应的 form
内容输出到页面。
此种方式对于在经常添加新流程的需求比较适用,可以快速发布新流程,把流程设计出来之后再设计表单之后两者关联就可以使用了。例如公司内部各种简单的审批流程,没有业务逻辑处理,仅仅是多级审批是否通过等等情况
当流程需要一些特殊处理时可以借助 Listener
或者 Delegate
方式实现。
Activiti Explorer
就是使用的这种方式,表单信息都配置在流程定义文件中。
代码片段:
<process id="FormKey" name="FormKey">
<startevent id="startevent1" name="Start" activiti:formkey="diagrams/form/start.form"></startevent>
…
</process>
同样也提供了单元测试:
- FormKey.bpmn20.xml
- start.form
- first-step.form
- ProcessTestFormKey.java
注意:表单的内容都是以key和value的形式数据保存在引擎表中!!!
3.普通表单
这个是最灵活的一种方式,常用于业务比较复杂的系统中,或者业务比较固定不变的需求中,例如ERP系统。
普通表单的特点是把表单的内容存放在一个页面(jsp、jsf、html等)文件中,存放方式也有两种(一体式、分离式):
-
一体式:把整个流程涉及到的表单放在一个文件然后根据处理的任务名称匹配显示,kft-activiti-demo的普通表单模式就是一体式的做法,把表单内容封装在一个div里面,div的ID以节点的名称命名,点击“办理”按钮时用对话框的方式把div的内容显示给用户。
-
分离式:对于非Ajax应用来说比较常用,每个任务对应一个页面文件,点击办理的时候根据任务的ID动态指定表单页面。
本博客发布的Activiti入门Demo中有演示: Activiti快速入门项目-kft-activiti-demo
和以上两种方式比较有两点区别:
- 表单:和第二种外置表单类似,但是表单的显示、表单字段值填充均由开发人员写代码实现。
- 数据表:数据表单独设计而不是和前两种一样把数据以key、value形式保存在引擎表中。
4.从业务数据和流程关联比较
- 动态表单:引擎已经自动绑定在一起了,不需要额外配置。
- 外置表单:和业务关联是可选的,提供的例子中是没有和业务关联的,如果需要关联只需要在提交StartForm的时候设置businessKey即可。
- 普通表单:这个应该是必须和业务关联,否则就是无头苍蝇了……,关联方式可以参考:工作流引擎Activiti使用总结中的2.3 业务和流程的关联方式
5.结束语
技术只是辅助工具,只能决定这件事能不能做,如何选择要看应用场合,希望简单的比较能提供一点思路。
[Activiti 6.x] Springboot 1.5x MySQL
Activiti 6.x 开篇 Springboot 1.5x + Activiti6.0 + MySQL 整合
创建项目
使用 STS
以java8创建项目
选择springboot 1.x的最新版本,activiti的starter是基于springboot1.x做的,所以这里采用1.x,不需要使用web,引入mysql即可。
整合配置
1.加入配置
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<version>6.0.0</version>
</dependency>
2.按照官网上的说明加入@EnableAutofiguration注解如下【官网上另外两个注解可以不加】
3.运行项目报错,activiti需要配置数据库
4.数据库配置,这里使用druid连接池与mysql数据库【默认使用H2内存数据库。QAQ嗯重新启动程序数据就没了】
配置如下,你懂的。顺手配一下日志
5.processes配置
one-task-process.bpmn20.xml 【来自官网】
<?xml version="1.0" encoding="UTF-8"?>
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="oneTaskProcess" name="The One Task Process">
<startEvent id="theStart" />
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="theTask" />
<userTask id="theTask" name="my task" />
<sequenceFlow id="flow2" sourceRef="theTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
</definitions>
6.配置完成
完成配置自动生成28张表
[Activiti 6.x] 基本流程讲解与开发前奏
Activiti 6.x 基本流程讲解与开发前奏
核心API介绍
springboot环境下不再以activiti.cfg.xml文件的形式配置,可在yml内配置,yml配置会在后文讲解。
RepositoryService:对流程定义进行管理。
RuntimeService:对流程实例的管理。
TaskService:对流程任务进行管理。
IdentityService:管理用户和用户组。
ManagementService:提供对activiti数据库的直接访问【一般不用】。
HistoryService:对流程的历史数据进行操作。
FormService:动态表单。
Activiti6 YML配置
1.加入配置
spring:
activiti:
database-schema-update: false
check-process-definitions: false
activiti使用starter配置后属于spring下。
check-process-definitions【检查Activiti数据表是否存在及版本号是否匹配】默认为true,自动创建好表之后设为false。设为false会取消自动部署功能。
database-schema-update【在流程引擎启动和关闭时处理数据库模式】如下(摘自官网)
- false (默认值):在创建流程引擎时检查库模式的版本,如果版本不匹配则抛出异常。
- true:在创建流程引擎时,执行检查并在必要时对数据库中所有的表进行更新,如果表不存在,则自动创建。
- create-drop:在创建流程引擎时,会创建数据库的表,并在关闭流程引擎时删除数据库的表。
准备步骤
1.测试项目结构:主要为做工作流部署的两种形式,zip为bpmn与png文件的压缩文件
2.安装eclipse activiti插件
3.流程图如下
代码后续用到功能时会贴出
RepositoryService
打开测试类
部署流程定义
/**部署流程定义*/
@Test
public void deploy(){
Deployment deployment = repositoryService.createDeployment()//创建一个部署对象
.name("helloworld入门程序")
.addClasspathResource("bpmn/MyProcess.bpmn")//从classpath的资源中加载,一次只能加载一个文件
.addClasspathResource("bpmn/MyProcess.png")
.deploy();
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
}
部署过程同时会影响三张表的数据
ACT_RE_DEPLOYMENT(第二行)
ACT_GE_BYTEARRAY(文件会被存在这张表内,activiti以纵表方式存储数据)
ACT_RE_PROCDEF
部署流程定义(zip)
/**部署流程定义(zip) */
@Test
public void deployzip() throws IOException{
InputStream in = this.getClass().getClassLoader().getResourceAsStream("bpmn/MyProcess.zip");
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deployment = repositoryService.createDeployment()
.name("helloworld入门程序2")
.addZipInputStream(zipInputStream)//指定zip格式的文件完成部署
.deploy();//完成部署
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
zipInputStream.close();
}
【PS:流程实例可以重复部署,有版本号作为标识】
删除流程定义
/**删除流程定义*/
@Test
public void deleteProcess(){
String deploymentId = "25001";
/**不带级联的删除:只能删除没有启动的流程,如果流程启动,就会抛出异常*/
// repositoryService.deleteDeployment(deploymentId);
/**级联删除:不管流程是否启动,都能可以删除(emmm大概是一锅端)*/
repositoryService.deleteDeployment(deploymentId, true);
System.out.println("删除成功!");
}
RuntimeService
【多个流程实例之间不会相互影响】
注入Service
启动流程实例
/**启动流程实例*/
@Test
public void startProcessInstance(){
//流程定义的key
String processDefinitionKey = "myProcess";
//key对应MyProcess.bpmn文件中id的属性值,使用key值启动,默认是按照最新版本的流程定义启动
ProcessInstance pi = runtimeService.startProcessInstanceByKey(processDefinitionKey);
System.out.println("流程实例ID:"+pi.getId());//流程实例ID
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());//流程定义ID
}
查询流程实例
/**查询流程实例*/
@Test
public void searchProcessInstance(){
String processDefinitionKey = "myProcess";
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.singleResult();
System.out.println("流程实例ID:"+pi.getId());
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());
}
流程实例的删除
/**流程实例的删除*/
@Test
public void deleteProcessInstanceTest(){
String processDefinitionKey = "myProcess";
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processDefinitionKey(processDefinitionKey)
.singleResult();
String processInstanceId = pi.getProcessInstanceId();
System.out.println("流程实例ID:"+pi.getId());
runtimeService.deleteProcessInstance(processInstanceId,"删除测试");
}
[Activiti 6.x] 核心API基础
Activiti 6.x 继续讲解核心API基础
核心API介绍
springboot环境下不再以activiti.cfg.xml文件的形式配置,可在yml内配置,yml配置会在后文讲解。
RepositoryService:对流程定义进行管理。
RuntimeService:对流程实例的管理。
TaskService:对流程任务进行管理。
IdentityService:管理用户和用户组。
ManagementService:提供对activiti数据库的直接访问【一般不用】。
HistoryService:对流程的历史数据进行操作。
FormService:动态表单。
IdentityService
用户管理
先讲IdentityService,后续联合TaskService、RuntimeService实现并行网关demo。 新建用户【无法创建两个ID一样的用户】
/**新建用户*/
@Test
public void testUser(){
User user = identityService.newUser("ptm");
user.setFirstName("潘");
user.setLastName("天淼");
user.setEmail("1458689676@qq.com");
user.setPassword("123456");
//新建用户
identityService.saveUser(user);
}
一般用户信息图片啥的就别存在activiti里面了,自带的表应该是满足不了真实业务需求的。呐QAQ你看下面这表。建议的话是在identityService的基础上,以userid与新建的表关联起来。RBAC了解一下。
获取用户信息【更新用户信息请先获取用户信息对获取的user,set属性调用saveUser方法即可】
/**获取用户信息*/
@Test
public void searchUser(){
String userid ="ptm";
User user = identityService.createUserQuery()
.userId(userid).singleResult();
System.out.println("博主的名字:"+user.getFirstName()+user.getLastName());
System.out.println("博主的邮箱:"+user.getEmail());
System.out.println("呐-ID肯定是相等的:"+userid.equals(user.getId()));
System.out.println("测试密码:"+user.getPassword());
System.out.println("是否验证成功:"+identityService.checkPassword("ptm","123456"));
}
删除用户信息
/**删除用户信息*/
@Test
public void delUser(){
identityService.deleteUser("ptm");
}
如果需要用activiti的用户表,接口有如下方法
组管理
/**用户组管理*/
@Test
public void testGroup(){
String groupId ="HRPTM";
//创建用户组对象
Group group = identityService.newGroup(groupId);
group.setName("HR");
group.setType("assignment");
//保存用户组
identityService.saveGroup(group);
Group groupInfo = identityService.createGroupQuery()
.groupId(groupId)
.singleResult();
System.out.println("组的名字:"+groupInfo.getName());
System.out.println("组类别"+groupInfo.getType());
System.out.println("GroupId:"+groupInfo.getId());
// //删除用户组
// identityService.deleteGroup("HRPTM");
}
组的管理与用户管理类似,就直接一回杀了。执行结果如下:
Membership管理(用户对应组关系)
/**Membership管理*/
@Test
public void testMembership(){
//建立关联
identityService.createMembership("ptm","HRPTM");
//查询属于HRPTM用户组的用户
User user = identityService.createUserQuery()
.memberOfGroup("HRPTM")
.singleResult();
System.out.println("博主的名字:"+user.getFirstName()+user.getLastName());
System.out.println("博主的邮箱:"+user.getEmail());
System.out.println("userid"+user.getId());
System.out.println("测试密码:"+user.getPassword());
System.out.println("是否验证成功:"+identityService.checkPassword("ptm","123456"));
//查询用户所属组
Group group = identityService.createGroupQuery()
.groupMember("ptm")
.singleResult();
System.out.println("组的名字:"+group.getName());
System.out.println("组类别"+group.getType());
System.out.println("GroupId:"+group.getId());
}
另外测试的key用的都是普通的英文,如是正式项目建议UUID。
[Activiti 6.x] 基础流程DEMO
Activiti 6.x 基础流程学习
流程图介绍:
流程图总览
网关condition设置
组设置
用户设置
【一般以流程变量形式设置办理人(把请假理解成一种任务,办理人即请假人)】
流程图代码
<?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:activiti="http://activiti.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="EmployeeAskForLeave">
<process id="myProcess" name="员工请假流程" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask1" name="填写请假申请" activiti:assignee="${userKey}"></userTask>
<exclusiveGateway id="exclusivegateway1" name="请假时间判断(排他网关)"></exclusiveGateway>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow>
<userTask id="usertask2" name="经理审批" activiti:candidateGroups="HR"></userTask>
<userTask id="usertask3" name="总监审批" activiti:candidateGroups="ZJ"></userTask>
<sequenceFlow id="flow3" name="小于等于3天" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days<=3}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" name="大于3天" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days>3}]]></conditionExpression>
</sequenceFlow>
<exclusiveGateway id="exclusivegateway2" name="请假时间判断(排他网关)"></exclusiveGateway>
<sequenceFlow id="flow5" sourceRef="usertask2" targetRef="exclusivegateway2"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="usertask3" targetRef="exclusivegateway2"></sequenceFlow>
<sequenceFlow id="flow7" sourceRef="exclusivegateway2" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
<bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="200.0" y="220.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="1040.0" y="220.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="320.0" y="210.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway1" id="BPMNShape_exclusivegateway1">
<omgdc:Bounds height="40.0" width="40.0" x="470.0" y="217.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="650.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
<omgdc:Bounds height="55.0" width="105.0" x="650.0" y="270.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="exclusivegateway2" id="BPMNShape_exclusivegateway2">
<omgdc:Bounds height="40.0" width="40.0" x="920.0" y="217.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="235.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="320.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="425.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="470.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="490.0" y="217.0"></omgdi:waypoint>
<omgdi:waypoint x="490.0" y="157.0"></omgdi:waypoint>
<omgdi:waypoint x="650.0" y="157.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="66.0" x="490.0" y="198.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="490.0" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="490.0" y="297.0"></omgdi:waypoint>
<omgdi:waypoint x="650.0" y="297.0"></omgdi:waypoint>
<bpmndi:BPMNLabel>
<omgdc:Bounds height="14.0" width="42.0" x="490.0" y="257.0"></omgdc:Bounds>
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow5" id="BPMNEdge_flow5">
<omgdi:waypoint x="755.0" y="157.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="157.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="217.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow6" id="BPMNEdge_flow6">
<omgdi:waypoint x="755.0" y="297.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="297.0"></omgdi:waypoint>
<omgdi:waypoint x="940.0" y="257.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow7" id="BPMNEdge_flow7">
<omgdi:waypoint x="960.0" y="237.0"></omgdi:waypoint>
<omgdi:waypoint x="1040.0" y="237.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
数据准备
//部署流程定义,新建数据
@Test
public void prepare() {
Deployment deployment = repositoryService.createDeployment()//创建一个部署对象
.name("请假流程")
.addClasspathResource("bpmn/MyProcess.bpmn")
.addClasspathResource("bpmn/MyProcess.png")
.deploy();
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
Group group1 = identityService.newGroup("HR");
group1.setName("HR");
group1.setType("HRassignment");
identityService.saveGroup(group1);//建立HR组
Group group2 = identityService.newGroup("ZJ");
group2.setName("ZJ");
group2.setType("ZJassignment");
identityService.saveGroup(group2);//建立ZJ组
Group group3 = identityService.newGroup("EP");
group3.setName("EP");
group3.setType("EPassignment");
identityService.saveGroup(group3);//建立员工组
//newUser传的是key【不是名字】
identityService.saveUser(identityService.newUser("HR1"));//高管
identityService.saveUser(identityService.newUser("HR2"));//高管
identityService.saveUser(identityService.newUser("ZJ"));//总监
identityService.saveUser(identityService.newUser("ZJ2"));//总监
identityService.saveUser(identityService.newUser("PTM"));//员工
identityService.createMembership("HR1", "HR");
identityService.createMembership("HR2", "HR");
identityService.createMembership("ZJ", "ZJ");
identityService.createMembership("ZJ2", "ZJ");
identityService.createMembership("PTM", "EP");
}
启动流程设置流程变量【流程变量必须指定不然报错】
/**启动流程实例分配任务给个人*/
@Test
public void start() {
String userKey="PTM";//脑补一下这个是从前台传过来的数据
String processDefinitionKey ="myProcess";//每一个流程有对应的一个key这个是某一个流程内固定的写在bpmn内的
HashMap<String, Object> variables=new HashMap<>();
variables.put("userKey", userKey);//userKey在上文的流程变量中指定了
ProcessInstance instance=runtimeService
.startProcessInstanceByKey(processDefinitionKey,variables);
System.out.println("流程实例ID:"+instance.getId());
System.out.println("流程定义ID:"+instance.getProcessDefinitionId());
}
运行结果:
【通过流程变量指定User的结果】
TaskService
查询当前人的个人任务
/**查询当前人的个人任务*/
@Test
public void findTask(){
String assignee = "PTM";
List<Task> list = taskService.createTaskQuery()//创建任务查询对象
.taskAssignee(assignee)//指定个人任务查询
.list();
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
}
}
}
Query里面可以写多个查询条件类是EF的那种
完成任务
/**完成任务*/
@Test
public void completeTask(){
//任务ID
String taskId = "47506";
HashMap<String, Object> variables=new HashMap<>();
variables.put("days", 4);//userKey在上文的流程变量中指定了
taskService.complete(taskId,variables);
System.out.println("完成任务:任务ID:"+taskId);
}
组任务查询【因为days=4所以由总监组负责】
/**查询当前人的组任务*/
@Test
public void findTaskGroup(){
//String assignee = "PTM";
List<Task> list = taskService.createTaskQuery()//创建任务查询对象
.taskCandidateUser("ZJ")//指定组任务查询
.list();
String taskid ="";
String instanceId ="";
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
taskid=task.getId();
instanceId = task.getProcessInstanceId();
}
}
//查询组任务成员[两种方式]
//List<IdentityLink> listIdentity = taskService.getIdentityLinksForTask(taskid);
List<IdentityLink> listIdentity = runtimeService.getIdentityLinksForProcessInstance(instanceId);
for(IdentityLink identityLink:listIdentity ){
System.out.println("userId="+identityLink.getUserId());
System.out.println("taskId="+identityLink.getTaskId());
System.out.println("piId="+identityLink.getProcessInstanceId());
}
}
//taskService.claim(taskid,"ZJ2");//指定办理人
//taskService.setAssignee(taskid, null);//回退为组任务状态
指定办理人后会变为用户任务。
/**查询当前人的组任务*/
@Test
public void findTaskGroup(){
String assignee = "ZJ2";
List<Task> list = taskService.createTaskQuery()//创建任务查询对象
//.taskCandidateUser("ZJ")//指定组任务查询
.taskAssignee(assignee)//指定个人任务查询
.list();
String taskid ="";
String instanceId ="";
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
taskid=task.getId();
instanceId = task.getProcessInstanceId();
}
}
//taskService.claim(taskid,"ZJ2");//指定办理人
taskService.setAssignee(taskid, null);//回退为组任务状态
List<IdentityLink> listIdentity = taskService.getIdentityLinksForTask(taskid);
//List<IdentityLink> listIdentity = runtimeService.getIdentityLinksForProcessInstance(instanceId);
//runtime查询没有taskId,task查询没有InstanceId
for(IdentityLink identityLink:listIdentity ){
System.out.println("userId="+identityLink.getUserId());
System.out.println("taskId="+identityLink.getTaskId());
System.out.println("piId="+identityLink.getProcessInstanceId());
}
}
代码里退回为组任务【即无办理人】
【流程就结束了。记录会被移到history表里面去。】
[Activiti 6.x] 剩下的核心API
Activiti 6.x 剩下的核心API讲解
HistoryService
流程定义与流程实例
/**HistoryProcessInstance*/
@Test
public void HistoryProcessInstance() {
List<HistoricProcessInstance> datas = historyService.createHistoricProcessInstanceQuery()
.finished().list();
for (HistoricProcessInstance historicProcessInstance : datas) {
System.out.println("流程实例id: "+historicProcessInstance.getId());
System.out.println("部署id: "+historicProcessInstance.getDeploymentId());
System.out.println("结束event: "+historicProcessInstance.getEndActivityId());
System.out.println("流程名称: "+historicProcessInstance.getName());
System.out.println("PROC_DEF_ID: "+historicProcessInstance.getProcessDefinitionId());
System.out.println("流程定义Key: "+historicProcessInstance.getProcessDefinitionKey());
System.out.println("流程定义名称: "+historicProcessInstance.getProcessDefinitionName());
System.out.println("开始event: "+historicProcessInstance.getStartActivityId());
}
}
【其他HistoryService查询类似】
FormService
【个人感觉在前端框架比较完善的今天几乎不会去用这个,但是还是做一些小demo】
activiti:formProperty【动态表单】
【流程图】
<?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:activiti="http://activiti.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.activiti.org/test">
<process id="whatFk" name="whatFk" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="User Task">
<extensionElements>
<activiti:formProperty id="userName" name="userName" type="string" variable="userName"></activiti:formProperty>
<activiti:formProperty id="age" name="age" type="string" variable="age"></activiti:formProperty>
</extensionElements>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
<userTask id="usertask2" name="User Task"></userTask>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_whatFk">
<bpmndi:BPMNPlane bpmnElement="whatFk" id="BPMNPlane_whatFk">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="210.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
<omgdc:Bounds height="55.0" width="105.0" x="310.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="840.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
<omgdc:Bounds height="55.0" width="105.0" x="560.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="245.0" y="147.0"></omgdi:waypoint>
<omgdi:waypoint x="310.0" y="147.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="415.0" y="147.0"></omgdi:waypoint>
<omgdi:waypoint x="560.0" y="147.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="665.0" y="147.0"></omgdi:waypoint>
<omgdi:waypoint x="840.0" y="147.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
@Test
public void FormType(){
Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/form.bpmn").deploy();
ProcessInstance pi = runtimeService.startProcessInstanceByKey("whatFk");
System.out.println(pi);
HashMap<String, String> variables = new HashMap<>();
variables.put("userName", "潘天淼");
variables.put("age", "18");
Task task = taskService.createTaskQuery().deploymentId(dep.getId()).singleResult();
formService.submitTaskFormData(task.getId(), variables);
}
数据库内以流程变量的形式存储了内容【注意:此方式设置的流程变量均为taskID】
@Test
public void FormTypeRead(){
//75005为偷懒直接从数据库内复制的流程
Task task = taskService.createTaskQuery().processInstanceId("75005").singleResult();
TaskFormData taskFormData = formService.getTaskFormData(task.getId());
List<FormProperty> formProperties = taskFormData.getFormProperties();
for (FormProperty formProperty : formProperties) {
System.out.println(formProperty.getId() + ",value=" + formProperty.getValue());
}
}
上方代码在该task未执行时能够获取表单所有元素
activiti:formKey【外置表单】
【修改代码流程文件与form要一同部署】
@Test
public void FormType(){
Deployment dep = repositoryService.createDeployment()
.addClasspathResource("bpmn/form.bpmn")
.addClasspathResource("bpmn/completeForm.form")
.deploy();
ProcessInstance pi = runtimeService.startProcessInstanceByKey("whatFk");
HashMap<String, String> variables = new HashMap<>();
variables.put("userName", "潘天淼");
variables.put("age", "18");
Task task = taskService.createTaskQuery().deploymentId(dep.getId()).singleResult();
formService.submitTaskFormData(task.getId(), variables);
}
【测试结果】
@Test
public void OutFormTypeRead(){
System.out.println(formService.getRenderedTaskForm("105016")==null);
HashMap<String, String> variables = new HashMap<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar ca = Calendar.getInstance();
String startDate = sdf.format(ca.getTime());
ca.add(Calendar.DAY_OF_MONTH, 2); // 当前日期加2天
String endDate = sdf.format(ca.getTime());
variables.put("startDate", startDate);
variables.put("endDate", endDate);
variables.put("reason", "公休");
formService.submitTaskFormData("105016", variables);
}
正常情况下呢这里是可以获取表单的。对于外置表单,只是把表单内容都保存在单独的form文件中,所以只能通过读取所有的请求参数作为流程启动参数。
图示部分为整块HTML的内容【想些啥都OK我是复制了一段HTML带表单的说】。
小结
activiti 基础部分到此就结束了。关于managementService据说是不怎么用的到的,呐网上资源也比较少,就不去细说了。接下来的话。本周内会把BPMN2.0涉及到的组件全部过一遍【不常用的Task组件我就不讲了。中间组件和补偿边界会全部涉及】。
Flowable 学习章节
官网地址:https://www.flowable.org/ Flowable6.3中文教程:https://tkjohn.github.io/flowable-userguide/ Flowable6.4.2中文教程:http://web.wzhz.xyz/flowable/bpmn/ Flowable6.5.0中文教程: http://www.shareniu.com/flowable6.5_zh_document/bpm/index.html http://jeesite.com/docs/bpm/ https://jeesite.gitee.io/front/flowable/6.5.0/bpmn/ 可以在官网下载对应的jar包在本地部署运行,官方提供了下面的五个应用程序: Flowable Modeler:流程定义管理 Flowable Task:用户任务管理 Flowable IDM:用户组权限管理 Flowable REST API:流程引擎对外提供的API接口 Flowable Admin:后台管理
Activiti与Flowable对比
这里对比 Activiti7
和 Flowable6
对比框架简介
- jBPM,是始祖,但目前市场较小,不作为对比重点
- Activiti,2010年基于jBPM诞生
- Flowable,2016年基于Activiti诞生
- Comframe,亚信的工作流框架,从时间上推测,用的可能是jBPM2.0或者3.0
- 新零售工作流框架,从使用的ExtJS3和dwr推测,应该诞生于2010年前后,应该没有用开源工作流框架,功能可能比较简单
对比框架选择
- jBPM7主要与JBoss下的规则引擎集成较好,其他没有什么优势,不太建议
- Activiti6在主力Tijs Rademakers和Joram Barrez离开后,就没有开发了,一直停留在6.0.0,不太建议
- Salaboy可能是空降,对核心引擎不熟,也可能Alfresco想发展cloud方向,因此6.0以后直接启动7.0
- Flowable6以后,Tijs继续他的本行,开发引擎核心
因此,这里对比activiti7和flowable6
Roadmap对比
Activiti的roadmap
Flowable的roadmap
可以看出:
- Activiti7的方向在云化
- 而Flowable6的方向是继续它的引擎核心,新加了RESTful任务(这个activiti没有,我之前是通过写代码实现),新加了异步存历史数据从而提升效率等核心功能
- 冀正在他的博客里讲了Flowable比Activiti多的功能, 当然,因为冀正是flowable的commiter,所以没有说Activiti比Flowable多的功能,有点偏颇,可以作为参考。
功能与其他总体对比
公司的个人初步感觉:
结论
- 具体选择看看锦华和龚总觉得哪个功能更为重要
- 我个人根据开发效率、功能、总体印象排列如下
- flowable6
- activiti7
- activiti6
- AI Comframe(AIF)
- 新零售工作流
- jbpm7
- activiti5、flowable5、jbpm5/6/4/3,其他国产开源框架或商用框架等
附录
flowable6和activit7的对比中文翻译列表
Flowable6(比activit6多的功能)
- 1、flowable已经支持所有的历史数据使用mongdb存储,activiti没有。
- 2、flowable支持事务子流程,activiti没有。
- 3、flowable支持多实例加签、减签,activiti没有。
- 4、flowable支持httpTask等新的类型节点,activiti没有。
- 5、flowable支持在流程中动态添加任务节点,activiti没有。
- 6、flowable支持历史任务数据通过消息中间件发送,activiti没有。
- 7、flowable支持java11,activiti没有。
- 8、flowable支持动态脚本,,activiti没有。
- 9、flowable支持条件表达式中自定义juel函数,activiti没有。
- 10、flowable支持cmmn规范,activiti没有。
- 11、flowable修复了dmn规范设计器,activit用的dmn设计器还是旧的框架,bug太多。
- 12、flowable屏蔽了pvm,activiti6也屏蔽了pvm(因为6版本官方提供了加签功能,发现pvm设计的过于臃肿,索性直接移除,这样加签实现起来更简洁、事实确实如此,如果需要获取节点、连线等信息可以使用bpmnmodel替代)。
- 13、flowable与activiti提供了新的事务监听器。activiti5版本只有事件监听器、任务监听器、执行监听器。-
- 14、flowable对activiti的代码大量的进行了重构。
- 15、activiti以及flowable支持的数据库有h2、hsql、mysql、oracle、postgres、mssql、db2。其他数据- 库不支持的。使用国产数据库的可能有点失望了,需要修改源码了。
- 16、flowable支持jms、rabbitmq、mongodb方式处理历史数据,activiti没有。
- 17、等等
Activiti7(比activiti6多的功能)
- 清理老版本的代码
- 清理仓库和重构
- 域API + HAL API +运行时包
- 流程定义重写
- 流程实例
- 任务
- 流程定义支持XML/JSON/SVG三种风格
- 审计服务:用于审计信息的事件存储
- 身份管理和SSO(KeyCloak 实现)
- 改进,改进和新增内容
- 查询服务:运行时信息使用事件存储。
- 开启Security
- JPA——参考实现
- 基础设施启用服务
- 网关(Zuul)
- 应用程序注册表(Eureka)
- SSO 和IDM(Keycloak )
- 所有服务都启用了Docker
- 所有的服务都可以部署到Kubernetes
- Cloud 实例 8月的工作是让流程引擎和其他服务与一些基础设施服务协作在云环境中工作。我们创建了Activiti Cloud 启动器,以确保在这些环境中工作既简单又直观。 我们还得到了查询服务的初始实现,这将允许您在不影响(和影响)任何流程引擎运行时的情况下使用关于流程执行的数据。
- 您可以通过查看我们的activiti - cloud示例存储库来使用所有这些服务,在这里您将找到一组描述符,以使用Docker撰写和Kubernetes和一个JavaScript应用程序来启动我们的所有服务,该应用程序演示了如何与提供的所有服务交互。这个简单的示例显示了当您想要与我们的服务交互时,安全层(SSO)是如何启动的。
- 集成事件和云连接器
- 释放Maven中央仓库地址
- 分布式通知服务(设计和初始实现)
- 基础设施启用服务
- 示踪剂(Zipkin)
- ELK 堆栈支持(可选)
- Activiti Cloud 文档
- 验证的例子
- AWS
- CloudFoundry 9月将致力于完善我们现有的服务,以确保我们使用合适的工具来进行正确的基础设施建设。我们支持的环境越多(AWS,CloudFoundry,Kubernetes),我们需要替换和集成的内容越多。基于Kubernetes已经提供了服务注册中心的事实,我们正在考虑替换Eureka在Kubernetes上运行。 本月的高优先级将是集成事件生产者和消费者(云连接器)的初始实现,这将使我们能够消除对类路径扩展的需求,并提高我们服务的互操作性。 我们的目标是在月底前对我们的通知服务进行非常简单的实现,以演示我们的基础结构如何允许您构建反应性和上下文应用程序。
- 将提供对Zipkin的支持,以监视和排除服务之间的交互。
- 应用程序上下文服务——初始版本
- 提供基本的案例管理结构
- 发布/部署运行时包服务
- 分布式模型存储库服务(设计和初始实现)
- 新的决策运行时设计和初始实现(有别于Flowable)
- 流程引擎清理和重构
- BPMN2扩充
- 历史上的服务
- 定时执行器
- 计时器
- 电子邮件服务
总结:
-
Activiti7好像基本叫Activiti Cloud7,专注于cloud的开发,包括与Zuul、Eureka、Zipkin、Sping Cloud、Docker、Kubernetes、ELK、Jenkins(持续集成)等功能;同时还包括一些我们用不到的云方面的功能,包括Cloud Native(云原生?)、KNative(谷歌的serverless开源框架)、Istio(service mesh的一个开源实现)、JHispter(前端的微服务框架?)、AWS(肯定不会与阿里云整合的)等;还有一些我看不懂的云方面的技术名词。
-
而Flowable专注于工作流引擎在NoSQL、消息队列的实现,可以完全不用关系型数据库,通过消息队列异步也可以提高效率;还专注于CMMN、DMN等流程规范、规则引擎方面的功能;还有如JUnit5(单元测试)、Jupiter(代码审查)等的功能;未来也打算开发K8s的整合功能。也就是主要专注于工作流引擎核心的功能。
附录:
Activiti7最新开发路线图(中文翻译)
Flowable最新版(6.4/6.3.1/6.3/6.2/6.1.1/6.1) 新特性(中文翻译)
Flowable v5 和v6版本的区别 http://www.shareniu.com/article/85.htm
Flowable 框架
Flowable 框架学习笔记
Flowable 入门介绍
官网地址:flowable.org
Flowable BPMN 用户手册 (v 6.4.2-SNAPSHOT)
Flowable BPMN 用户手册 (v 6.5.0-SNAPSHOT)
可以在官网下载对应的jar包在本地部署运行,官方提供了下面的五个应用程序:
-
Flowable Modeler:流程定义管理
-
Flowable Task:用户任务管理
-
Flowable IDM:用户组权限管理
-
Flowable REST API:流程引擎对外提供的API接口
-
Flowable Admin:后台管理
初识Flowable五大引擎
Flowable有五大引擎,每个之间都是相互独立互不影响。
ProcessEngine是里面最核心也是最重要的一个引擎,如果失去它那Flowable也就意义了。
流程引擎使用架构
Flowable引擎在使用前需要先通过配置来初始化ProcessEngine。
初始化ProcessEngineConfiguration一般有两种方式:
-
通过Spinrg配置文件进行依赖注入,通过flowable.cfg.xml文件来初始化ProcessEngineConfiguration(这里的文件名必须为flowable.cfg.xml,否则Flowable识别不到)
-
通过编写程序的方式来构造ProcessEngineConfiguration对象
ProcessEngineConfiguration在初始化过程中会同时初始化数据库,如果数据库已经存在,则不会做创建更新操作,如果数据库不存在,则会默认执行数据库创建脚本。
流程引擎初体验
-
简单了解Bpmn
-
Task任务:
用户任务(userTask)
系统任务(serviceTask )
-
Event事件:
定时器事件(timerEventDefinition)
-
Gateway网关:
排他网关(exclusive gateway)
-
-
目标:实现以下简化版的请假流程
-
步骤1:定义相应的BPMN文件
-
步骤2:配置flowable.cfg.xml
-
步骤3:将流程定义添加到Repository仓储中
-
步骤4:Runtime开始一个流程实例
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")
-
角色且部门
-
使用Flowable工作流引擎的时候,不可避免就需要考虑相应的用户权限,
根据官方文档提供的教程,实现Flowable的用户权限体系总共有两大类:
Flowable提供的IdmEngine身份识别引擎
使用Flowable提供的IdmEngine,也有三种方案:
-
方案一: Flowable默认提供的IdmEngine已经实现IdmIdentityService接口,包含对用户、组、权限等的操作;
-
方案二: 集成LDAP来实现轻量级用户权限管理。LDAP内部已经实现IdmIdentityService接口,包含对用户、组、权限等的操作;
-
方案三: 自定义实现IdmIdentityService接口,实现对用户、组、权限的操作;
自定义身份识别引擎
有时候在项目上已经实现了自己的用户体系,接入Flowable的工作流引擎时,就需要考虑如何将自己的用户体系映射到Flowable工作流引擎的数据权限上去。Flowable提供的数据权限体系是用户、组、租户的结构体系,因为这里提供一个映射的设计思路:
在实际项目中,往往用户会存在租户、角色、部门之间的对应关系,因而在对应到Flowable的用户、组、租户的时候可以用以下思路来处理:
具体的案例如下:
用户A(id值为1) - 角色A(id值为1) - 部门A(id值为1) - 区域A(id值为1)(某个租户的用户A是角色A,所在部门为部门A,所属区域为区域A)
根据上述的映射关系转换成如下
-
用户转换后的id:1
-
角色转换后的id:R1
-
部门转换后的id:O1
-
区域转换后的id:A1
将映射关系对应到Flowable中
-
用户:1
-
组:R1、O1、A1
知道如果映射后,现在的问题就是在哪里去实现这部分的映射关系,以下提供几种实现的思路,目前还没正式验证过,仍处于实验阶段:
-
一、修改官方的源码
已知对bpmn的用户任务进行解析的方法类为UserTaskXMLConverter,通过该类的源码可以了解相关的解析机制,在此基础上对自己需要的节点添加相关的解析操作
-
二、找到官方提供的自定义配置,修改自定义配置属性(还未找到方案)
-
三、在前端设计bpmn文件的时候,通过相关的方法对id进行映射后生成最终的bpmn文件
-
四、自定义权限的表达式,例如:
<userTask id="approveTask" name="刘备审批" flowable:assignee="#{idmTest.org('emp')}"/>
其中idmTest.org就是用来自定义返回的id集的方法
数据库介绍(34张表)
ACT_RE_ *:RE代表repository。具有此前缀的表包含静态信息,例如流程定义和流程资源(图像,规则等)。
ACT_RU_ *:RU代表runtime。这些是包含运行时的流程实例,用户任务,变量,作业等的运行时数据的运行时表。Flowable仅在流程实例执行期间存储运行时数据,并在流程实例结束时删除记录。这使运行时表保持小而快。
ACT_HI_ *:HI代表history。这些是包含历史数据的表,例如过去的流程实例,变量,任务等。
ACT_GE_ *:general数据,用于各种用例。
ACT_ID_ *:Idm的用户、组
表分类 | 表名 | 表说明 |
---|---|---|
一般数据(2) | ACT_GE_BYTEARRAY | 通用的流程定义和流程资源 |
ACT_GE_PROPERTY | 系统相关属性 | |
流程历史记录(8) | ACT_HI_ACTINST | 历史的流程实例 |
ACT_HI_ATTACHMENT | 历史的流程附件 | |
ACT_HI_COMMENT | 历史的说明性信息 | |
ACT_HI_DETAIL | 历史的流程运行中的细节信息 | |
ACT_HI_IDENTITYLINK | 历史的流程运行过程中用户关系 | |
ACT_HI_PROCINST | 历史的流程实例 | |
ACT_HI_TASKINST | 历史的任务实例 | |
ACT_HI_VARINST | 历史的流程运行中的变量信息 | |
用户用户组表(9) | ACT_ID_BYTEARRAY | 二进制数据表 |
ACT_ID_GROUP | 用户组信息表 | |
ACT_ID_INFO | 用户信息详情表 | |
ACT_ID_MEMBERSHIP | 人与组关系表 | |
ACT_ID_PRIV | 权限表 | |
ACT_ID_PRIV_MAPPING | 用户或组权限关系表 | |
ACT_ID_PROPERTY | 属性表 | |
ACT_ID_TOKEN | 系统登录日志表 | |
ACT_ID_USER | 用户表 | |
流程定义表(3) | ACT_RE_DEPLOYMENT | 部署单元信息 |
ACT_RE_MODEL | 模型信息 | |
ACT_RE_PROCDEF | 已部署的流程定义 | |
运行实例表(10) | ACT_RU_DEADLETTER_JOB | 正在运行的任务表 |
ACT_RU_EVENT_SUBSCR | 运行时事件 | |
ACT_RU_EXECUTION | 运行时流程执行实例 | |
ACT_RU_HISTORY_JOB | 历史作业表 | |
ACT_RU_IDENTITYLINK | 运行时用户关系信息 | |
ACT_RU_JOB | 运行时作业表 | |
ACT_RU_SUSPENDED_JOB | 暂停作业表 | |
ACT_RU_TASK | 运行时任务表 | |
ACT_RU_TIMER_JOB | 定时作业表 | |
ACT_RU_VARIABLE | 运行时变量表 | |
其他表(2) | ACT_EVT_LOG | 事件日志表 |
ACT_PROCDEF_INFO | 流程定义信息 |
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 资源文件表
Activiti Flowable transient变量
Activiti Flowable transient变量
Activiti6 增加了瞬时变量( transient
变量)。接下来展示如何使用 Activiti6 提供的 transient
变量。
Activiti6 版本之前所有的变量都会持久化到数据库表( act_ru_variable 或者 act_hi_varinst ),变量存储于 act_hi_varinst 表前提是开启了历史数据的归档开关。瞬时变量使用起来跟持久化的变量一样一样的。唯一的区别是他不会持久化到数据库表。下面对瞬时变量进行一些说明:
瞬时变量可以存活下来知道下一步的等待状态,前提是流程实例的数据可以存储到数据库。
瞬态变量隐藏具有相同名称的持久性变量。
1.1 瞬时变量的操作
void setTransientVariable(String variableName, Object variableValue);
void setTransientVariableLocal(String variableName, Object variableValue);
void setTransientVariables(MaptransientVariables);
void setTransientVariablesLocal(MaptransientVariables);
Object getTransientVariable(String variableName);
Object getTransientVariableLocal(String variableName);
MapgetTransientVariables();
MapgetTransientVariablesLocal();
void removeTransientVariable(String variableName);
void removeTransientVariableLocal(String variableName);
接下来演示瞬时变量的使用过程,操作起来非常的简单。
首先定义一个流程文档(名称process.bpmn20.xml),该流程图如下所示:
部署流程文档如下代码所示:
public void addInputStreamTest() throws Exception {
//定义的文件信息的流读取
InputStream inputStream = DeploymentBuilderTest.class.getClassLoader().getResourceAsStream("com/shareniu/activiti6/transientvars/process.bpmn20.xml");
//流程定义的分类
String category="variabletypeTest";
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment().category(category).addInputStream("variabletype.bpmn", inputStream);
//部署
Deployment deploy = deploymentBuilder.deploy();
}
启动流程实例时,我们传递的变量是定期的变量。他们将保留和审核历史记录会保留,因为实在没有理由为什么这不应该是这样。
流程实例启动完毕之后,会首先执行‘execute HTTP call’,那么会立刻触发ExecuteHttpCallDelegate类中的逻辑,该类的定义如下所示:
public class ExecuteHttpCallDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) {
execution.setTransientVariable("response", "分享牛(shareniu.com)");
execution.setTransientVariable("responseStatus",200);
}
}
上述的两个变量response和responseStatus都为瞬时变量。
接下来看一下ProcessResponseExecutionListener类的定义如下所示:
public class ProcessResponseExecutionListener implements ExecutionListener {
public void notify(DelegateExecution execution) {
Listlist=new ArrayList<>();
list.add("shareniu1");
list.add("shareniu2");
execution.setTransientVariable("searchResults",list );
}
}
Ok,上述工作完毕之后直接启动新的流程实例如下所示:
public void startProcessInstanceById() throws Exception{
runtimeService.startProcessInstanceById("transient-vars:1:37504");
}
act_ru_variable表的变化如下所示:
看到上图可知response、responseStatus这两个瞬时变量没有被存储,但是searchResults瞬时变量被存储了,那么问题来了,什么时候瞬时变量会转化为持久化变量呢?关于这一点可以参考随后的章节。
Flowable Activiti 会签 多实例
Flowable Activiti 会签 多实例
在实际的业务中,可能存在存在这么一种情况,当流程运行到某一个环节时,可能需要同时多个人的参与,才可以完成此环节。此时就可以用到activiti的多实例来解决此问题。
将一个节点设置成多实例:
要把一个节点设置为多实例,节点xml元素必须设置一个 multiInstanceLoopCharacteristics
子元素。
当 isSequential=true
时,表示的顺序执行,即虽然该节点有多条任务,但只有上一条执行完,才可以执行下一条。
当 isSequential=false
时,表示的并行执行,即该节点下的多条任务可以同时执行。
设置会签环节的参与者
activiti:collection
:用于执行该会签环节的参与参与的人,此处是使用的一个名叫pers的流程变量
activiti:elementVariable
:此处表示的是每一个分支都有一个名叫per的流程变量,和上方的activiti:assignee
结合使用就可以执行该分支应该由谁来处理。
指定会签环节的结束条件
当画红线的部分返回一个true的时候,该会签环节结束。进入下一个流程执行的环节.
completionCondition中写的是juel表达式。其中的mulitiInstance如果是和spring整合了,就是spring管理的bean的id,否则就是流程变量的key.
会签环节中涉及的几个默认的流程变量
1.nrOfInstances 该会签环节中总共有多少个实例
2.nrOfActiveInstances 当前活动的实例的数量,即还没有 完成的实例数量。
3.nrOfCompletedInstances 已经完成的实例的数量
代码
1.分配会签环节的人:
/**
* 分配下一环节会签的人
* @author huan
*/
public class AssgineeMultiInstancePer implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) throws Exception {
System.out.println("设置会签环节的人员.");
execution.setVariable("pers", Arrays.asList("张三", "李四", "王五", "赵六"));
}
}
2.多实例判断完成的条件:(注意:有于我没有和spring整合,所以此类要实现Serializable接口)
/**
* 多实例完成的条件判断
* @author huan
*/
public class MulitiInstanceCompleteTask implements Serializable {
private static final long serialVersionUID = 1L;
public boolean completeTask(DelegateExecution execution) {
System.out.println("总的会签任务数量:" + execution.getVariable("nrOfInstances") + "当前获取的会签任务数量:" + execution.getVariable("nrOfActiveInstances") + " - " + "已经完成的会签任务数量:" + execution.getVariable("nrOfCompletedInstances"));
System.out.println("I am invoked.");
return false;
}
}
3.会签环节中的一个监听器:
/**
* 测试会签过程中监听器的执行情况
* @author huan
*/
public class TestLinstener implements TaskListener {
private static final long serialVersionUID = -5754522101489239675L;
@Override
public void notify(DelegateTask delegateTask) {
System.out.print(delegateTask.getId() + " - " + delegateTask.getProcessInstanceId() + " - " + delegateTask.getEventName() + " - " + delegateTask.getTaskDefinitionKey());
}
}
4.测试代码:
/**
* 测试会签
* @author huan
*/
public class TestMultiInstance {
@Test
public void testProcess() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
Deployment deploy = repositoryService.createDeployment()//
.name("会签流程测试")//
.addInputStream("multiInstances.bpmn", this.getClass().getResourceAsStream("multiInstances.bpmn"))//
.addInputStream("multiInstances.png", this.getClass().getResourceAsStream("multiInstances.png"))//
.deploy();
System.out.println(deploy.getId() + " " + deploy.getName());
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("mulitiInstance", new MulitiInstanceCompleteTask());
ProcessInstance pi = runtimeService.startProcessInstanceByKey("multiInstances",variables);
System.out.println(pi.getId() + " " + pi.getActivityId());
Task task1 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("张三").singleResult();
System.out.println(task1.getId() + " - " + task1.getAssignee() + " - " + task1.getProcessInstanceId() + " - " + task1.getProcessDefinitionId());
Task task2 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("李四").singleResult();
System.out.println(task2.getId() + " - " + task2.getAssignee() + " - " + task2.getProcessInstanceId() + " - " + task2.getProcessDefinitionId());
Task task3 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("王五").singleResult();
System.out.println(task3.getId() + " - " + task3.getAssignee() + " - " + task3.getProcessInstanceId() + " - " + task3.getProcessDefinitionId());
Task task4 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("赵六").singleResult();
if (task4 != null) {
System.out.println(task4.getId() + " - " + task4.getAssignee() + " - " + task4.getProcessInstanceId() + " - " + task4.getProcessDefinitionId());
}
Task task5 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("钱七").singleResult();
System.out.println(task5);
taskService.complete(task1.getId());
taskService.complete(task2.getId());
taskService.complete(task3.getId());
Task task6 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("钱七").singleResult();
System.out.println(task6);
taskService.complete(task4.getId());
Task task7 = taskService.createTaskQuery().processInstanceId(pi.getId()).taskAssignee("钱七").singleResult();
System.out.println(task7);
taskService.complete(task7.getId());
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(pi.getId()).singleResult();
if (null == processInstance) {
System.out.println("流程完成.");
}
}
}
5.流程文件:
<?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:activiti="http://activiti.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.activiti.org/test">
<process id="multiInstances" name="流程会签测试" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="A001"></sequenceFlow>
<serviceTask id="A001" name="设置下一环节的人" activiti:class="com.huan.activiti.liuyang.会签.AssgineeMultiInstancePer"></serviceTask>
<userTask id="B001" name="会签环节" activiti:assignee="${per}">
<extensionElements>
<activiti:taskListener event="complete" class="com.huan.activiti.liuyang.会签.TestLinstener"></activiti:taskListener>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="pers" activiti:elementVariable="per">
<completionCondition>${mulitiInstance.completeTask(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="flow2" sourceRef="A001" targetRef="B001"></sequenceFlow>
<userTask id="C001" name="会签后的环节" activiti:assignee="钱七"></userTask>
<sequenceFlow id="flow3" sourceRef="B001" targetRef="C001"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="C001" targetRef="endevent1"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_multiInstances">
<bpmndi:BPMNPlane bpmnElement="multiInstances" id="BPMNPlane_multiInstances">
<bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
<omgdc:Bounds height="35.0" width="35.0" x="100.0" y="240.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="A001" id="BPMNShape_A001">
<omgdc:Bounds height="71.0" width="117.0" x="190.0" y="222.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="B001" id="BPMNShape_B001">
<omgdc:Bounds height="55.0" width="105.0" x="380.0" y="230.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="C001" id="BPMNShape_C001">
<omgdc:Bounds height="55.0" width="105.0" x="561.0" y="230.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
<omgdc:Bounds height="35.0" width="35.0" x="740.0" y="240.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
<omgdi:waypoint x="135.0" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="190.0" y="257.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
<omgdi:waypoint x="307.0" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="380.0" y="257.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
<omgdi:waypoint x="485.0" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="561.0" y="257.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
<omgdi:waypoint x="666.0" y="257.0"></omgdi:waypoint>
<omgdi:waypoint x="740.0" y="257.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
7.流程图:
Flowable6.4 – 会签实现方案
Flowable6.4 – 会签实现方案
之前介绍过多实例,但只是理论上如何实现,本文将介绍一种能够实际应用的会签方案。
前面几篇文章描述过如何为一个UserTask节点增加扩展属性,多实例UserTask节点也可以按照之前的方法增加扩展属性。
但是分配办理人时,与普通的UserTask节点稍有不同,不能直接从UserTask节点的扩展属性内获取办理人信息。
如何获得办理人?
首先, 声明一个辅助处理器,用来帮助获得UserTask节点的办理人和检查是否结束多实例节点的办理。主要代码如下:
/**
* 获得当前节点的处理者列表
* @param execution 当前执行实例
* @return 处理者列表
*/
public List<DealerInfo> getList(DelegateExecution execution) {
FlowElement flowElement = execution.getCurrentFlowElement();
UserTask userTask = (UserTask) flowElement;
UserTaskExtension userTaskExtension = FlowUtil.getUserTaskExtension(userTask);
//DealerInfo是一个实体,用来表示办理人的具体信息
//从扩展属性内读取UserTask的办理人信息
//返回一个集合,该集合的数量影响了UserTask多实例的数量
return userTaskExtension.getDealers();
}
/**
* 获取会签是否结束
* @param execution 当前执行实例
* @return 是否结束
*/
public boolean getComplete(DelegateExecution execution) {
Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");
Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
int agreeCount = 0, rejectCount = 0, abstainCount = 0;
Map<String, Object> vars = execution.getVariables();
for (String key : vars.keySet()) {
//会签投票以SIGN_VOTE+TaskId标识
//获得会签投票的统计结果
if (key.contains(FlowConst.SIGN_VOTE) && !key.equals(FlowConst.SIGN_VOTE_RESULT)) {
Integer value = (Integer) vars.get(key);
//统计同意、驳回、弃权票数
//省略代码若干......
}
}
//以下为一段简单的规则,可以按情况实现自己的会签规则
if (!nrOfCompletedInstances.equals(nrOfInstances)) {
//必须等所有的办理人都投票
return false;
} else {
//会签全部完成时,使用默认规则结束
if (rejectCount > 0) {
//有反对票,则最终的会签结果为不通过
//移除SIGN_VOTE+TaskId为标识的参数
removeSignVars(execution);
//增加会签结果参数,以便之后流转使用
execution.setVariable(FlowConst.SIGN_VOTE_RESULT, false);
//会签结束
return true;
} else {
//没有反对票时,则最终的会签结果为通过
removeSignVars(execution);
execution.setVariable(FlowConst.SIGN_VOTE_RESULT, true);
return true;
}
}
}
然后, 对流程图内的UserTask节点进行设置:
上图中的Collection设置为:${mutiInstanceHandler.getList(execution)}
。
上图中的Completion设置为:${mutiInstanceHandler.getComplete(execution)}
。
一定要注意的是:Element Var,这个参数将决定多实例中每一个UserTask的办理人。
接下来,设置办理人,之前的教程可以参考《Flowable6.4 - 分派办理人》,主要的代码如下:
/**
* 分配办理人员
*/
@Override
protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution) {
if (null != this.userTaskExtension) {
//由扩展属性读取办理人规则
List<DealerInfo> dealerInfos = getDealerInfo();
if (null == dealerInfos || dealerInfos.size() == 0) {
throw new RuntimeException("处理者信息不存在");
}
if (hasMultiInstanceCharacteristics())
//多实例节点分配处理者
//获得传参,流程图中的Element Var
Object objDealer = execution.getVariable("dealerInfo");
if (objDealer instanceof DealUserInfo) {
DealUserInfo userInfo = (DealUserInfo) objDealer;
assignee = userInfo.getId();
} else if (objDealer instanceof DealRoleInfo) {
DealRoleInfo roleInfo = (DealRoleInfo) objDealer;
task.addGroupIdentityLink(roleInfo.getId(), "role");
}
}
}
super.handleAssignments(taskService, assignee, owner, candidateUsers, candidateGroups, task, expressionManager, execution);
}
最后, 在Complete流程的时候,按照调用方传回的信息,设置投票的值即可:
//设置会签投票结果
variables.put(FlowConst.SIGN_VOTE + "_" + taskId, signVoteType.getCode());
如何使用会签结果?
在需要判断会签结果的Sequence Flow Condition中直接设置:
Flowable6.4 - 分派办理人
Flowable6.4 - 分派办理人
这次分享的是从一个开源项目的代码里面学来的,有兴趣的同学可以去阅读一下该项目的源码,还是有很多可以参考的地方,项目地址如下:
https://gitee.com/threefish/NutzFw.git
首先, 存储办理人的表:act_ru_identitylink。如果想为一个Task分配办理人,可以使用以下的API:
task.addCandidateGroup(String groupId);
task.addCandidateUser(String userId);
task.addCandidateGroups(Collection<String> candidateGroups);
task.addCandidateUsers(Collection<String> candidateUsers);
如果使用以上的API增加一个办理人,会在act_ru_identitylink表中增加一条记录,如下:
使用上述API设置的人员或者组,表中的TYPE_为“candidate”。如果阅读Flowable的源代码,就会发现原因:
//设置人员
public IdentityLinkEntity addCandidateUser(String taskId, String userId) {
return this.addTaskIdentityLink(taskId, userId, (String)null, "candidate");
}
//设置组
public IdentityLinkEntity addCandidateGroup(String taskId, String groupId) {
return this.addTaskIdentityLink(taskId, (String)null, groupId, "candidate");
}
但是, 有时候这种固定的属性无法满足我们的业务需要。比如,有时候期望设置部门、岗位、角色。这时候,就需要使用其它的API进行设置,如下:
通过上面的“identityLinkTyp”,就可以自定义TYPE_的值,如下图所示:
之后,就可以进入本次的主要内容了,如何为UserTask节点分配办理人。这里提供的一个方案是通过重写UserTaskActivityBehavior来实现。
关于如何重写UserTaskActivityBehavior,可以参考之前的文章,链接如下:
Flowable6.4 - Behavior使用初探
这里需要重写UserTaskActivityBehavior内的handleAssignments方法,主要的代码如下:
public class ExtUserTaskActivityBehavior extends UserTaskActivityBehavior {
private static final long serialVersionUID = 7711531472879418236L;
public ExtUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}
/**
* 分配办理人员
*/
@Override
protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution) {
//此处可以根据业务逻辑自定义
super.handleAssignments(taskService, assignee, owner, candidateUsers, candidateGroups, task, expressionManager, execution);
}
}
比如NutzFW这个开源项目就是通过如下的过程设置的:
通过扩展UserTask节点属性,设置办理人。
当触发handleAssignments方法时,读取UserTask节点属性。
根据节点属性设置办理人。
主要的设置代码如下:
case SINGLE_USER:
//单人情况下,直接设置办理人
assignee = taskExtensionDTO.getAssignee();
break;
case MULTIPLE_USERS:
//多人情况下,设置candidateUsers
candidateUsers = taskExtensionDTO.getCandidateUsers().stream().map(CandidateUsersDTO::getUserName).collect(Collectors.toList());
break;
case USER_ROLE_GROUPS:
//角色时,设置group
candidateGroups = taskExtensionDTO.getCandidateGroups().stream().map(CandidateGroupsDTO::getRoleCode).collect(Collectors.toList());
break;
Flowable6.4 – 加签和减签
Flowable6.4 – 加签和减签
趁着旅游归来的短暂休息,了解一下Flowable中的加签和减签操作。主要是以下两个方法来实现:
runtimeService.addMultiInstanceExecution(String activityId, String parentExecutionId, Map<String, Object> executionVariables)
runtimeService.deleteMultiInstanceExecution(String executionId, boolean executionIsCompleted)
依然是先上流程图:
其中会签节点是多实例节点,此流程的关键xml片段如下:
<process id="TestMutiTask" isExecutable="true">
<startEvent id="Start1" name="开始"></startEvent>
<userTask id="UserTask1" name="处理"></userTask>
<sequenceFlow id="sid-D14A5BC6-A61E-461F-AD33-0042E91B8B13" sourceRef="Start1" targetRef="UserTask1"></sequenceFlow>
<userTask id="UserTask2" name="会签">
<multiInstanceLoopCharacteristics isSequential="false" flowable:collection="${subProcessHelper.getUserNames()}" flowable:elementVariable="assignee">
<completionCondition>${subProcessHelper.isComplete(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="sid-E6847EF6-F54F-409B-AF8B-DCA62ECDC76F" sourceRef="UserTask1" targetRef="UserTask2"></sequenceFlow>
<userTask id="UserTask3" name="审批"></userTask>
<sequenceFlow id="sid-AF828B84-DDAA-4056-88C5-9D4F6EA9F725" sourceRef="UserTask2" targetRef="UserTask3"></sequenceFlow>
<endEvent id="End1" name="结束"></endEvent>
<sequenceFlow id="sid-C49B6256-0827-4CF5-8A47-5860A107142A" sourceRef="UserTask3" targetRef="End1"></sequenceFlow>
</process>
subProcessHelper.getUserNames():会返回一个List<String>
集合,会签节点会根据此集合的数量生成相对应的实例。
subProcessHelper.isComplete(execution):用来判断会签节点是否完成,这里设置的条件为“已完成数量/总数量>2/3
”。
调用加签和减签的方法如下:
/**
* 增加流程执行实例
* @param nodeId
* @param proInstId
* @param assigneeStr 以逗号隔开的字符串
*/
@RequestMapping(value = "addExecution/{nodeId}/{proInstId}/{assignees}")
public void addExecution(@PathVariable("nodeId") String nodeId,
@PathVariable("proInstId") String proInstId,
@PathVariable("assignees") String assigneeStr) {
String[] assignees = assigneeStr.split(",");
for (String assignee : assignees) {
runtimeService.addMultiInstanceExecution(nodeId, proInstId, Collections.singletonMap("assignee", (Object) assignee));
}
}
/**
* 删除流程执行实例
* @param excutionId
* @param complated 是否完成此流程执行实例
*/
@RequestMapping(value = "delExecution/{excutionId}/{complated}")
public void delExecution(@PathVariable("excutionId") String excutionId,
@PathVariable("complated") Boolean complated) {
runtimeService.deleteMultiInstanceExecution(excutionId, complated);
}
启动流程,将流程跳转至会签节点,如下图所示:
act_ru_task
此时请求加签方法:
http://localhost:8080/flowabledemo/flow/addExecution/UserTask2/55001/test004
流程会增加一个新的子实例,并且会增加相对应的参数,如下:
act_ru_task
此时如果请求减签的方法:
http://localhost:8080/flowabledemo/flow/delExecution/55034/0
流程中相对应的Task和Variable会被删除:
以上,就是本次试验的内容,需要注意的是,在减签时,如果Task正好是该多实例节点中的最后一个,将导致流程无法继续流转。下次可以分析一下源码,看看为何会这样。
Flowable6.4 – 加签和减签的源码解析
Flowable6.4 – 加签和减签的源码解析
上一篇简单实现了一下加签和减签的操作,这次主要是看看Flowable是如何实现加签和减签的。
首先,加签。
Flowable实现加签主要是通过下面的方法实现的:
runtimeService.addMultiInstanceExecution(String activityId, String parentExecutionId, Map<String, Object> executionVariables)
跟踪代码进入其方法体,发现执行了下面这个命令:
AddMultiInstanceExecutionCmd(activityId, parentExecutionId, executionVariables)
这里的三个参数所代表的的意义是:
activityId:流程节点的标识。
parentExecutionId:流程执行实例标识,proInstId。
executionVariables:所要传入的参数。
查看AddMultiInstanceExecutionCmd
这个类的源码,主要关注方法execute()
,此方法就是加签操作实现的关键。
关键的代码加注释,如下:
@Override
public Execution execute(CommandContext commandContext) {
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
//获得multi instance execution,即IS_MI_ROOT
ExecutionEntity miExecution = searchForMultiInstanceActivity(activityId, parentExecutionId, executionEntityManager);
if (miExecution == null) {
throw new FlowableException("No multi instance execution found for activity id " + activityId);
}
if (Flowable5Util.isFlowable5ProcessDefinitionId(commandContext, miExecution.getProcessDefinitionId())) {
throw new FlowableException("Flowable 5 process definitions are not supported");
}
//创建新的流程执行实例
ExecutionEntity childExecution = executionEntityManager.createChildExecution(miExecution);
childExecution.setCurrentFlowElement(miExecution.getCurrentFlowElement());
//获得BPMN模型中节点的配置信息
BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(miExecution.getProcessDefinitionId());
Activity miActivityElement = (Activity) bpmnModel.getFlowElement(miExecution.getActivityId());
MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = miActivityElement.getLoopCharacteristics();
//设置流程参数nrOfInstances
Integer currentNumberOfInstances = (Integer) miExecution.getVariable(NUMBER_OF_INSTANCES);
miExecution.setVariableLocal(NUMBER_OF_INSTANCES, currentNumberOfInstances + 1);
//设置子流程执行实例的参数
if (executionVariables != null) {
childExecution.setVariablesLocal(executionVariables);
}
//如果是并行,需要执行操作,生成Task记录
if (!multiInstanceLoopCharacteristics.isSequential()) {
miExecution.setActive(true);
miExecution.setScope(false);
childExecution.setCurrentFlowElement(miActivityElement);
CommandContextUtil.getAgenda().planContinueMultiInstanceOperation(childExecution, miExecution, currentNumberOfInstances);
}
return childExecution;
}
需要注意的就是最后部分的操作,因为节点有并行和串行的区分,所以需要不同的处理。
再看,减签。
Flowable实现加签主要是通过下面的方法实现的:
runtimeService.deleteMultiInstanceExecution(String executionId, boolean executionIsCompleted)
跟踪代码进入其方法体,发现执行了下面这个命令:
DeleteMultiInstanceExecutionCmd(executionId, executionIsCompleted)
这里的两个参数所代表的的意义是:
executionId:需要删除的流程执行实例标识。
executionIsCompleted:是否完成此流程执行实例。查看
DeleteMultiInstanceExecutionCmd这个类的源码,主要关注方法execute(),此方法就是加签操作实现的关键。
关键的代码加注释,如下:
@Override
public Void execute(CommandContext commandContext) {
ExecutionEntityManager executionEntityManager = CommandContextUtil.getExecutionEntityManager();
ExecutionEntity execution = executionEntityManager.findById(executionId);
//获得BPMN模型中节点的配置信息
BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId());
Activity miActivityElement = (Activity) bpmnModel.getFlowElement(execution.getActivityId());
MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = miActivityElement.getLoopCharacteristics();
if (miActivityElement.getLoopCharacteristics() == null) {
throw new FlowableException("No multi instance execution found for execution id " + executionId);
}
if (!(miActivityElement.getBehavior() instanceof MultiInstanceActivityBehavior)) {
throw new FlowableException("No multi instance behavior found for execution id " + executionId);
}
if (Flowable5Util.isFlowable5ProcessDefinitionId(commandContext, execution.getProcessDefinitionId())) {
throw new FlowableException("Flowable 5 process definitions are not supported");
}
//删除指定的流程执行实例和与其关联的数据
ExecutionEntity miExecution = getMultiInstanceRootExecution(execution);
executionEntityManager.deleteChildExecutions(execution, "Delete MI execution", false);
executionEntityManager.deleteExecutionAndRelatedData(execution, "Delete MI execution", false);
//获得循环的索引值,以便之后重新设置
int loopCounter = 0;
if (multiInstanceLoopCharacteristics.isSequential()) {
//如果是串行,则获得当前的索引值
SequentialMultiInstanceBehavior miBehavior = (SequentialMultiInstanceBehavior) miActivityElement.getBehavior();
loopCounter = miBehavior.getLoopVariable(execution, miBehavior.getCollectionElementIndexVariable());
}
//如果设置为流程执行实例已经完成,则已完成数量+1,并且索引值也+1
//如果设置为流程执行实例未完成,则流程实例数量-1,索引值不变
if (executionIsCompleted) {
Integer numberOfCompletedInstances = (Integer) miExecution.getVariable(NUMBER_OF_COMPLETED_INSTANCES);
miExecution.setVariableLocal(NUMBER_OF_COMPLETED_INSTANCES, numberOfCompletedInstances + 1);
loopCounter++;
} else {
Integer currentNumberOfInstances = (Integer) miExecution.getVariable(NUMBER_OF_INSTANCES);
miExecution.setVariableLocal(NUMBER_OF_INSTANCES, currentNumberOfInstances - 1);
}
//生成一个新的流程执行实例(个人觉得这个是专为串行准备的)
ExecutionEntity childExecution = executionEntityManager.createChildExecution(miExecution);
childExecution.setCurrentFlowElement(miExecution.getCurrentFlowElement());
//如果是串行,需要执行一次生成Task,并且设置正确的loopCounter
if (multiInstanceLoopCharacteristics.isSequential()) {
SequentialMultiInstanceBehavior miBehavior = (SequentialMultiInstanceBehavior) miActivityElement.getBehavior();
miBehavior.continueSequentialMultiInstance(childExecution, loopCounter, childExecution);
}
return null;
}
所以,通过分析上述源码,如果是并行的多实例节点,并且删除了最后一个流程执行实例,会发现没有了Task,导致整个流程中断。
Flowable6.4 – 绘制流程图
Flowable6.4 – 绘制流程图
一般需要流程图的场景:
-
发起流程时,需要从全局了解整体情况、所涉及的经办人,便于必要时进行催办或者发起线下沟通。
-
流程运行时或结束后,查看流程所经历的办理过程。
在Flowable中,流程图的绘制可以参见:
org.flowable.image.impl.DefaultProcessDiagramGenerator
本文将分成两部分,简单介绍一下流程图的绘制和办理节点的高亮现实。
首先,如何绘制流程图。
总共三个步骤:
-
获得流程定义的BpmnModel。
-
根据BpmnModel获得图片流。
-
输出图片流。
直接上代码了,基本上都是Flowable Api的使用:
/**
* @param type 输入的图片类型(png或jpg)
* @param modelId 流程模型标识
* @param response HttpServletResponse
*/
@ResponseBody
@RequestMapping(value = "diagram/{type}/{modelId}")
public void getJpgDiagram(@PathVariable(value = "type") String type,
@PathVariable(value = "modelId") String modelId,
HttpServletResponse response) {
try {
//根据modelId或者BpmnModel
Model modelData = repositoryService.getModel(modelId);
ExtBpmnJsonConverter jsonConverter = new ExtBpmnJsonConverter();
byte[] modelEditorSource = repositoryService.getModelEditorSource(modelData.getId());
JsonNode editorNode = new ObjectMapper().readTree(modelEditorSource);
BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
//获得图片流
DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
InputStream inputStream = diagramGenerator.generateDiagram(
bpmnModel,
type,
Collections.emptyList(),
Collections.emptyList(),
"宋体",
"宋体",
"宋体",
null,
1.0,
false);
//输出为图片
IOUtils.copy(inputStream, response.getOutputStream());
response.setHeader("Content-Disposition", "attachment; filename=" + bpmnModel.getMainProcess().getId() + "." + type);
response.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
}
}
需要注意的是,如果未指定文字信息,中文就会以方框显示。如果设定了文字,但是输出还是以方框显示,可以尝试在设置Configuration时,加入:
configuration.setActivityFontName("宋体");
configuration.setLabelFontName("宋体");
效果如下:
然后,如何高亮显示已经办理的节点。
除了上面的三步之外,需要预先提取高亮的节点和Sequence Flow。即设置上文中的diagramGenerator.generateDiagram方法中的两个emptyList。
依然是直接上代码:
/**
* @param type 输入的图片类型(png或jpg)
* @param modelId 流程模型标识
* @param instId 流程实例标识
* @param response HttpServletResponse
*/
@ResponseBody
@RequestMapping(value = "acitvityDiagram/{type}/{modelId}/{instId}")
public void getJpgActivityDiagram(@PathVariable(value = "type") String type,
@PathVariable(value = "modelId") String modelId,
@PathVariable(value = "instId") String instId,
HttpServletResponse response) {
//获得已经办理的历史节点
List<HistoricActivityInstance> activityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(instId).orderByHistoricActivityInstanceStartTime().asc().list();
List<String> activties = new ArrayList<>();
List<String> flows = new ArrayList<>();
for (HistoricActivityInstance activityInstance : activityInstances) {
if ("sequenceFlow".equals(activityInstance.getActivityType())) {
//需要高亮显示的连接线
flows.add(activityInstance.getActivityId());
} else {
//需要高亮显示的节点
activties.add(activityInstance.getActivityId());
}
}
try {
//根据modelId或者BpmnModel
Model modelData = repositoryService.getModel(modelId);
ExtBpmnJsonConverter jsonConverter = new ExtBpmnJsonConverter();
byte[] modelEditorSource = repositoryService.getModelEditorSource(modelData.getId());
JsonNode editorNode = new ObjectMapper().readTree(modelEditorSource);
BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
//获得图片流
DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator();
InputStream inputStream = diagramGenerator.generateDiagram(
bpmnModel,
type,
activties,
flows,
"宋体",
"宋体",
"宋体",
null,
1.0,
false);
//输出图片
IOUtils.copy(inputStream, response.getOutputStream());
response.setHeader("Content-Disposition", "attachment; filename=" + bpmnModel.getMainProcess().getId() + "." + type);
response.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
}
}
最终的效果如下:
flowable 任务节点多实例使用
flowable 任务节点多实例使用
我们在使用Flowable 工作流引擎的时候,最常用的肯定是任务节点,因为在OA系统、审批系统、办公自动化系统中核心的处理就是流程的运转,在流程运转的时候,可能我们有这样的一个需求,在一个任务节点的时候,我们需要多个人对这个节点进行审批,比如实际中这样一个例子,假如是一个部门的投票,这个部门有5个人,那么当5个人都投票的时候大概分为如下几种:
-
部门所有人都去投票,当所有人都投票完成的时候,这个节点结束,流程运转到下一个节点。(所有的人都需要投票)
-
部门所有人都去投票,只要有任意2/3的人同意,这个节点结束,流程运转到下一个节点。(部分人投票只要满足条件就算完成)。
-
部门中有一个部门经理,只要部门经理投票过了,这个节点结束,流程运转到下一个节点(一票否决权)。
-
部门中根据职位不同,不同的人都不同的权重,当满足条件的时候,这个节点结束,流程运转到下一个节点。比如说所有的人员权重加起来是1,a有0.2的权重,其他的四个人分别是0.1的权重,我们可以配置权重达到0.3就可以走向下一个节点,换言之a的权重是其他人的2倍,那就是a的投票相当于2个人投票。这种需求还是很常见的。
-
部门所有人都去投票,a投票结束到b,b开始投票结束到c,一直如此,串行执行。最终到最后一个人再统计结果,决定流程的运转。
上面的五种情况,我们可以提取出来一些信息,我们的activiti 工作流引擎,必须支持如下功能,才能满足上面的需求:
-
任务节点可以配置自定义满足条件。
-
任务节点必须支持串行、并行。
-
任务节点必须支持可以指定候选人或者候选组。
-
任务节点必须支持可以循环的次数。
-
任务节点必须支持可以自定义权重。
-
任务节点必须支持加签、减签。(就是动态的修改任务节点的处理人)
因为实际上的需求可能比上面的几种情况更加的复杂,上面的6个满足条件,工作流支持前4个,后面的2个条件是不支持的,所以我们必须要扩展activiti 工作流引擎才能使用5、6等的功能。下面我们将详细的介绍前四种条件的使用,在掌握基本使用之后,我们在后面的章节中将详细的介绍,5、6这两种功能以及可能更加复杂的操作。
串行、并行配置
为了演示如何使用,我们采用由浅入深的使用,结合流程图、流程定义xml、以及代码和数据库的变化来阐释每一个配置的使用以及含义。
流程的详细定义如下图所示:
流程的详细定义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">
<process id="multiInstance" name="multiInstance" isExecutable="true">
<documentation>multiInstance</documentation>
<startEvent id="startEvent1"></startEvent>
<userTask id="A" name="多实例测试" flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext="" shareniu:shareniuextboolen="false">
<multiInstanceLoopCharacteristics isSequential="true">
<loopCardinality>2</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" sourceRef="startEvent1" targetRef="A"></sequenceFlow>
<userTask id="shareniu-B" name="shareniu-B" xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext="" shareniu:shareniuextboolen="false"></userTask>
<sequenceFlow id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" sourceRef="A" targetRef="shareniu-B"></sequenceFlow>
<endEvent id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>
<sequenceFlow id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" sourceRef="shareniu-B" targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_multiInstance">
<bpmndi:BPMNPlane bpmnElement="multiInstance" id="BPMNPlane_multiInstance">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="130.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="A" id="BPMNShape_A">
<omgdc:Bounds height="80.0" width="100.0" x="165.0" y="105.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="shareniu-B" id="BPMNShape_shareniu-B">
<omgdc:Bounds height="80.0" width="100.0" x="315.0" y="120.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-4AC81F5B-49F8-4135-B68E-1C182D004080" id="BPMNShape_sid-4AC81F5B-49F8-4135-B68E-1C182D004080">
<omgdc:Bounds height="28.0" width="28.0" x="442.0" y="146.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" id="BPMNEdge_sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47">
<omgdi:waypoint x="414.9499999999902" y="160.0"></omgdi:waypoint>
<omgdi:waypoint x="442.0" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" id="BPMNEdge_sid-BA8FC337-40DC-493B-805C-F213B7C4A17D">
<omgdi:waypoint x="264.95000000000005" y="145.0"></omgdi:waypoint>
<omgdi:waypoint x="290.0" y="145.0"></omgdi:waypoint>
<omgdi:waypoint x="290.0" y="160.0"></omgdi:waypoint>
<omgdi:waypoint x="314.99999999998477" y="160.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" id="BPMNEdge_sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D">
<omgdi:waypoint x="129.94999817301806" y="145.0"></omgdi:waypoint>
<omgdi:waypoint x="165.0" y="145.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
xml配置文件的部分含义如下:
- flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" 这个节点可以4个人审核。
<loopCardinality>2</loopCardinality>
循环2次结束。<multiInstanceLoopCharacteristics isSequential="true">
串行并行的配置。
串行的配置
修改<multiInstanceLoopCharacteristics isSequential="true">
中的isSequential为true是串行,isSequential为false是并行。我们测试串行。下面的代码展示启动流程因为是以一个节点所以部署启动后,直接进入多实例任务。
流程的部署
@Test
public void addBytes() {
byte[] bytes = IoUtil.readInputStream(
ProcessengineTest.class.getClassLoader()
.getResourceAsStream("com/shareniu/shareniu_flowable_study/bpmn/ch3/multiInstance.bpmn20.xml"),
"multiInstance.bpmn20.xml");
String resourceName = "multiInstance.bpmn";
Deployment deployment = repositoryService.createDeployment().addBytes(resourceName, bytes).deploy();
System.out.println(deployment);
}
流程的启动
@Test
public void startProcessInstanceByKey() {
String processDefinitionKey = "multiInstance";
ProcessInstance startProcessInstanceByKey = runtimeService.startProcessInstanceByKey(processDefinitionKey);
System.out.println(startProcessInstanceByKey);
}
我们按照上面的步骤启动一个流程看一下数据库的变化。
ACT_RU_TASK表的数据有了如下图所示:
ACT_RU_IDENTITYLINK表的数据有了如下图所示:
ACT_RU_IDENTITYLINK权限表中,确实把我们设置的人员信息设置进去了,shareniu1,shareniu2,shareniu3,shareniu4现在就有代办信息了。
ACT_RU_VARIABLE表的数据有了如下图示:
上面重要的变量需要解释一下,要不然还真不好理解,多任务是怎么运转的。
-
nrOfInstances 实例总数。
-
nrOfCompletedInstances 当前还没有完成的实例 nr是number单词缩写 。
-
loopCounter 已经循环的次数。
-
nrOfActiveInstances 已经完成的实例个数。
下面我们结束一个任务看一下,流程走到那个节点了。
完成任务
@Test
public void complete() {
String taskId="15011";
taskService.complete(taskId);
}
接下来看一下数据库表的变化。
ACT_RU_VARIABLE表的数据有了如下图所示:
上面我们仔细的发现,可以看到
nrOfCompletedInstances、loopCounter、nrOfActiveInstances都加1了,确实多任务就是参考这几个值的变化进行判断的。
因为我们设置了循环2次,所以我们看看ACT_RU_IDENTITYLINK还有一个任务,因为我们是并行处理的。
所以我们在结束新的任务看一下流程是不是真的结束了,如果结束了,那么我们循环次数的配置就是正确的。
完成任务
@Test
public void complete() {
String taskId="17502";
taskService.complete(taskId);
}
下面看一下ACT_RU_TASK,里面没有任务信息了,所以侧面证明循环次数的配置就是正确的。
接下来我们测试并行任务。除了isSequential="false",其他的配置是一样的。
并行的配置测试
除了isSequential="false",其他的配置跟上面的串行是一样一样的。
重新部署测试,部署后我们启动一个新的流程测试。
ACT_RU_TASK表如下:
一次性的有2个任务需要处理,因为我们循环的是2次,所以直接就是2个。
ok串行、并行就讲解到这里。
串行、并行总结
我们配置的是循环2次,看以看到不管是并行还是串行,两个代办任务结束之后,流程直接跳转到下一个状态,但是
我们并没有配置结束条件,所以上面的例子,也可以看出来,如果不配置默认的通过条件,则默认条件是1,后面的源码章节会给大家说明这一点的。
通过条件的配置
在上面的串行、并行实例中,我们没有设置通过条件,但是程序按照配置的循环的次数,然后跳转到了下一个状态,可以侧面印证,如果不配置通过条件则默认值就是1.
下面的代码详细的介绍通过条件的配置,具体的配置代码如下:
<process id="multiInstance" name="multiInstance"
isExecutable="true">
<documentation>multiInstance</documentation>
<startEvent id="startEvent1"></startEvent>
<userTask id="A" name="多实例测试"
flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4">
<multiInstanceLoopCharacteristics
isSequential="true">
<loopCardinality>4</loopCardinality>
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow
id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" sourceRef="startEvent1"
targetRef="A"></sequenceFlow>
<userTask id="shareniu-B" name="shareniu-B"
xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext=""
shareniu:shareniuextboolen="false"></userTask>
<sequenceFlow
id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" sourceRef="A"
targetRef="shareniu-B"></sequenceFlow>
<endEvent id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>
<sequenceFlow
id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" sourceRef="shareniu-B"
targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>
</process>
配置描述
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>
nrOfCompletedInstances、nrOfInstances 变量描述上面已经描述了,我们这里设置的条件是大于1/4的人完成任务,任务就结束了。下面我们代码部署流程,启动流程后进行测试:
ACT_RU_TASK表如下:
我们随便结束一个任务,看一下ACT_RU_TASK表变化。
@Test
public void complete() {
String taskId="40003";
taskService.complete(taskId);
}
执行上面的代码,我们很神奇的发现,ACT_RU_TASK表中的其他任务没有了,因为我们配置了4个人,通过条件是1/4,所以任意一个人结束了,流程就结束了。这里我们测试的是并行,串行也是一样的,读者可以自行测试验证。
动态的配置
上面的几种方式,我们定义xml的时候,循环的次数是固定写在xml中的,也就是说我们配置的是循环2次,那么所有的流程实例都是循环2次,这样就不灵活了,程序当然是灵活了比较好,所以在实际开发中,我们可以使用下面的这种方式操作,使程序更加的灵活。
程序的xml配置如下所示:
<process id="multiInstance" name="multiInstance"
isExecutable="true">
<documentation>multiInstance</documentation>
<startEvent id="startEvent1"></startEvent>
<userTask id="A" name="多实例测试" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics
isSequential="false" flowable:collection="assigneeList"
flowable:elementVariable="assignee">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}
</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
<sequenceFlow
id="sid-6D482A8A-F796-4E2B-AB2C-ABBFD2DA428D" sourceRef="startEvent1"
targetRef="A"></sequenceFlow>
<userTask id="shareniu-B" name="shareniu-B"
xmlns:shareniu="http://www.shareniu.com/" shareniu:shareniuext=""
shareniu:shareniuextboolen="false"></userTask>
<sequenceFlow
id="sid-BA8FC337-40DC-493B-805C-F213B7C4A17D" sourceRef="A"
targetRef="shareniu-B"></sequenceFlow>
<endEvent id="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></endEvent>
<sequenceFlow
id="sid-DF962BB4-EA4C-4D86-9A91-214EB2EC1A47" sourceRef="shareniu-B"
targetRef="sid-4AC81F5B-49F8-4135-B68E-1C182D004080"></sequenceFlow>
</process>
动态配置如下所示:
<userTask id="usertask1" name="多实例任务" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="assigneeList" activiti:elementVariable="assignee">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
参数说明:
-
activiti:assignee="${assignee}"
-
activiti:elementVariable="assignee" 多实例任务依赖上面的配置${assignee}
-
activiti:collection="assigneeList"
三个参数结合决定了,当前节点的处理人来自assigneeList集合,注意这里是集合信息而不是字符串,所以程序的运行时候变量的赋值,如下所示:
@Test
public void startProcessInstanceByKey() {
Map<String, Object> vars = new HashMap<>();
String[] v = { "shareniu1", "shareniu2", "shareniu3", "shareniu4" };
vars.put("assigneeList", Arrays.asList(v));
String processDefinitionKey = "multiInstance";
ProcessInstance startProcessInstanceByKey = runtimeService.startProcessInstanceByKey(processDefinitionKey,
vars);
// Sys
ok了,测试一下,确实程序如预期的所示,大功告成。
总结
参数的使用总结
-
flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" 这个节点可以4个人审核。
-
loopCardinality>2</loopCardinality>
循环2次结束。 -
<multiInstanceLoopCharacteristics isSequential="true">
串行并行的配置。<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition>
完成条件的配置。
这里我们还可以得出一个结论:
如果使用串行方式操作nrOfActiveInstances 变量始终是1,因为并行的时候才会去+1操作。
遗留点
上面的程序已经解决了常用的问题,关于会签、加签、减签、退签、权重配置、自定义通过条件配置(条件自定义通过)
这些问题,这个章节还没有具体的实现,关于这些问题,由于本章内容有限,我们就后续章节讲解吧。