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 基础知识

1、基本概念 工作流:业务过程的部分或整体在计算机应用环境下的自动化 工作流管理系统:工作流的定义和管理,按照在系统中预定义好的工作流规则进行工作流实例的执行。 工作流管理系统的目标:管理工作流程以确保工作在正确的时间被期望的人员执行–在自动化进行的业务过程中插入人工的执行和干预。 Activiy是什么 Activiti是一个针对企业用户、开发人员、系统管理员的轻量级工作流业务管理平台,其核心是使用Java开发的快速、稳定的BPMN2.0流程引擎。 Activiti的特点 数据持久化,底层使用MyBatis 引擎Service接口 流程设计器 原生支持Spring 分离运行时与历史数据

Activiti 基础知识

什么是 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就可以解决业务需求变更时,源代码不需要更新,更新的是业务流程图?

原理?

图片附件

holiday.png

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>

流程图: 2242271-c5438bc5dc6b468a.png demo流程

代码:

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

框架初始化2242271-4becaf2056d3c51f.webp

ProcessEngine类图

ProcessEngine

ProcessEngine是Activiti框架的门面,ProcessEngine本身不提供任何功能,通过getXXXService方法可以获取到对应的Service对象执行操作。Demo中涉及到的两个Service:

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

2242271-bc6f111998288acc.webp 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));
}

2242271-e6a84ab68b549bf4.webp CommandExecutor类图

在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

2242271-dbf9c4a66c79d3c9.webp 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

2242271-eb4810b9b4a64a04.webp DeploymentEntityManager类图

DeploymentEntityManager的deploy方法中循环调用Deployer对象的deploy方法,Activiti默认的Deployer是BpmnDeployer。

另外DeploymentEntityManager中还缓存了解析好的流程定义对象和Bpmn模型对象。

Activiti持久化的是流程图xml文件,每次系统重新启动都要执行一次“deploy”操作,生成ProcessDefinitionEntity对象。

BpmnDeployer

2242271-68d44c88944fc225.webp 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);
        }
        ...
    }
}

BpmnParse

2242271-c0d8c16d9fbf42fc.webp 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模型

2242271-0d7054f46acfb7dd.webp Bpmn模型

PVM模型

2242271-ce5026e930258c97.webp 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:

2242271-5a8bc87dc9fd0f46.webp ExecutionEntity

ExecutionEntity实现了一些重要接口:

在ExecutionEntity中维护类一个属性:activity。activity属性代表当前执行到哪个节点,在创建ExecutionEntity过程中会设置activity,使流程从某一个节点开始,默认是开始节点。

最后StartProcessInstanceCmd还调用ExecutionEntity的start方法开始驱动流程:

public void start() {
    performOperation(AtomicOperation.PROCESS_START);
}

驱动流程

Activiti框架的流程运行于PVM模型之上,在流程运行时主要涉及到PVM中几个对象:ActivityImpl、TransitionImpl和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做了几件事:

PROCESS_START_INITIAL也实现了类似的功能:

在Demo流程执行中涉及的AtomicOperation的链路主要包括:

以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 基础知识

Activiti 核心 API


Activiti 核心 API

7大接口

核心API

.1: ProcessEngine

说明:

  1. 在Activiti中最核心的类,其他的类都是由他而来。

  2. 产生方式:

在前面看到了两种创建ProcessEngine(流程引擎)的方式,而这里要简化很多,调用ProcessEngines的getDefaultProceeEngine方法时会自动加载classpath下名为activiti.cfg.xml文件。

  1. 可以产生RepositoryService

  2. 可以产生RuntimeService

  3. 可以产生TaskService

各个Service的作用:

RepositoryService 管理流程定义
RuntimeService 执行管理,包括启动、推进、删除流程实例等操作
TaskService 任务管理
HistoryService 历史管理(执行完的数据的管理)
IdentityService 组织机构管理
FormService 一个可选服务,任务表单管理
ManagerService 使用Activiti的定制环境中基本上不会用到。 它可以查询数据库的表和表的元数据。另外,它提供了查询和管理异步操作的功能。

.2:RepositoryService

是Activiti的仓库服务类。所谓的仓库指流程定义文档的两个文件:bpmn文件和流程图片。

  1. 产生方式

  2. 可以产生DeploymentBuilder,用来定义流程部署的相关参数

  3. 删除流程定义

.3:RuntimeService

是activiti的流程执行服务类。可以从这个服务类中获取很多关于流程执行相关的信息。

.4:TaskService

是activiti的任务服务类。可以从这个类中获取任务的信息。

.5:HistoryService

是activiti的查询历史信息的类。在一个流程执行完成后,这个对象为我们提供查询历史信息。

.6:ProcessDefinition

流程定义类。可以从这里获得资源文件等。

.7:ProcessInstance

代表流程定义的执行实例。如范冰冰请了一天的假,她就必须发出一个流程实例的申请。一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大的流程分支,即一个流程中流程实例只有一个。

.8:Execution

Activiti用这个对象去描述流程执行的每一个节点。在没有并发的情况下,Execution就是同ProcessInstance。流程按照流程定义的规则执行一次的过程,就可以表示执行对象Execution。

如图为ProcessInstance的源代码:

从源代码中可以看出ProcessInstance就是Execution。但在现实意义上有所区别:

在单线流程中,如上图的贷款流程,ProcessInstance与Execution是一致的。

这个例子有一个特点:wire money(汇钱)和archive(存档)是并发执行的。 这个时候,总线路代表ProcessInstance,而分线路中每个活动代表Execution。

总结:

Activiti 基础知识

Activiti 用户手册

Activiti 5.16 用户手册

官方用户手册

Activiti 基础知识

Activiti 数据库表结构

——文档适用于 Activiti 5-6


Activiti 数据库表结构设计说明

Activiti 工作流总共包含 23 张数据表(现在是25张,新增了 ACT_EVT_LOGACT_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_ 开头的。表名的第二部分用两个字母表明表的用途。

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_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 )

任务参与者数据表。主要存储当前节点参与者的信息。

流程与身份关系,用户或者用户组与流程数据之间的关系

字段名称 字段描述 数据类型 主键 为空 取值说明
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 依赖事务监听器(上)


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属性可以有如下三个值:

&middot;           Committed(提交)

&middot;           rolled-back(回滚)

&middot;           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 依赖事务监听器

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:
Activiti 基础知识

Activiti6 特性


了解 Activiti6 新增特性

本文重点分析 Activiti6 新增的一些特性,从而可以更好的了解 Activiti6 的走向。

Activiti6最大的变化点就是对代码进行了重构,该版本修复以往的Bug并不多,但内部实现相对来说变化比较大。其突出的变化如下所示:

Activiti 基础知识

Activiti 6.0 工作流入门


Activiti 6.0 工作流入门学习

工作流介绍

工作流: 是对工作流程及其各操作步骤之间业务规则的抽象、概括描述

工作流建模: 即将工作流程中的工作如何前后组织在一起的逻辑和规则,在计算机中以恰当的模型表达并对其实施计算

要解决的问题: 是为实某个业务目标,利用 计算机在多个参与者之间按某种预定规则自动传递文档、信息或者任务

数据模型设计

数据表分类 描述
ACT_GE_* 通用数据表
ACT_RE_* 流程定义存储表
ACT_ID_* 身份信息表
ACT_RU_* 运行时数据表
ACT_HI_* 历史数据表

BPM2.0元素

审批流程模型化

购物工作流程模型化

部署Activiti

准备环境:

  1. 压缩activiti-6.0.0.zip找到wars把里面的activiti-admin.war,activiti-app.war复制到tomcat的webapps下面并启动tomcat

  2. 浏览器打开访问地址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 基础知识

Activiti 6.0 源码分析 helloword


Activiti 6.0 源码分析 helloword 学习

获取源码

  1. 然后从自己目录下把项目克隆到本地
  2. 切换分支>git checkout -b study6 activiti-6.0.0
  3. 编译>mvn clean test-compile
  4. 导入到编辑器

Activiti6.0模块介绍

基于源码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画好流程图并设置属性

设置form表单信息

填写审批信息表单

主管审批表单

hr审批表单

设置流转条件

提交or取消:

主管审批校验:

人事审批校验:

导出工作流xml文件创建demoActiviti项目

  1. 把导入文件放入项目里
  2. 引入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>
  1. 流程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 基础知识

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>

流程图: 2242271-c5438bc5dc6b468a.png demo流程

代码:

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

框架初始化2242271-4becaf2056d3c51f.webp

ProcessEngine类图

ProcessEngine

ProcessEngine是Activiti框架的门面,ProcessEngine本身不提供任何功能,通过getXXXService方法可以获取到对应的Service对象执行操作。Demo中涉及到的两个Service:

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

2242271-bc6f111998288acc.webp 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));
}

2242271-e6a84ab68b549bf4.webp CommandExecutor类图

在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

2242271-dbf9c4a66c79d3c9.webp 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

2242271-eb4810b9b4a64a04.webp DeploymentEntityManager类图

DeploymentEntityManager的deploy方法中循环调用Deployer对象的deploy方法,Activiti默认的Deployer是BpmnDeployer。

另外DeploymentEntityManager中还缓存了解析好的流程定义对象和Bpmn模型对象。

Activiti持久化的是流程图xml文件,每次系统重新启动都要执行一次“deploy”操作,生成ProcessDefinitionEntity对象。

BpmnDeployer

2242271-68d44c88944fc225.webp 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);
        }
        ...
    }
}

BpmnParse

2242271-c0d8c16d9fbf42fc.webp 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模型

2242271-0d7054f46acfb7dd.webp Bpmn模型

PVM模型

2242271-ce5026e930258c97.webp 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:

2242271-5a8bc87dc9fd0f46.webp ExecutionEntity

ExecutionEntity实现了一些重要接口:

在ExecutionEntity中维护类一个属性:activity。activity属性代表当前执行到哪个节点,在创建ExecutionEntity过程中会设置activity,使流程从某一个节点开始,默认是开始节点。

最后StartProcessInstanceCmd还调用ExecutionEntity的start方法开始驱动流程:

public void start() {
    performOperation(AtomicOperation.PROCESS_START);
}

驱动流程

Activiti框架的流程运行于PVM模型之上,在流程运行时主要涉及到PVM中几个对象:ActivityImpl、TransitionImpl和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做了几件事:

PROCESS_START_INITIAL也实现了类似的功能:

在Demo流程执行中涉及的AtomicOperation的链路主要包括:

以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是没有用的,要把它部署起来】 随后我们就执行该工作流,该工作流就随着我们定义的步骤来一一执行!

Activiti 学习笔记

使用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包进行部署

Activiti 学习笔记

AgileBPM 敏捷工作流开发平台

推荐个人认为做的比较不错的开源工作开发平台AgileBPM 敏捷工作流开发平台—— 开源免费-基于 Activiti 工作流引擎、Flowable

科技部门通过快速迭代支撑业务不断的创新、发展,最终构建出完善的、高效的业务线从而提高产品竞争力、行业壁垒。

AgileBPM 是一个快速开发平台,与众多快速开发平台不一样的地方是,他可以快速的进行业务流程的实施,助力企业快速构建业务流。

文档

摘要

我见过国内很多开发员从零开始整合流程引擎,而后也在使用一些不太合适的实施形式,导致很多开发付出了很沉重的开发代价,浪费了很多时间。

回想下吧,正在走向流程整合道路的您,已经踩过多少坑,比如 Activiti 表单、人员、自由跳转、会签多实例 、难用的API 等等这些坑,您或者已经踩过、或者正在奔坑而去!

不可否认 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
email
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形式 三种不同 服务提供形式

试用地址

普通版本

全新 OA 版本

OA版本是全新的UI,基于Iview-pro开发,拥有更漂亮的 UI、极致的用户体验和细节处理 并且支持国际化。

如果对前端有较高要求可以选择 高级OA版本或者企业OA版本。

试用地址 http://test1.agilebpm.cn

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 版本! 其他版本内容基本已经构建完善。

Activiti 学习笔记

Actviti7 数据库环境

数据库表的命名规则

Activiti 的表都以 ACT_ 开头。第二部分是表示表的用途的两个字母标识。用途也和服务的 API 对应。

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

Activiti 核心 API

创建表 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
-- ----------------------------

holiday.png

Activiti 学习笔记

Activiti 流程定义及部署测试

流程定义

安装 Activiti 插件

 在eclipse或idea中安装activiti-designer插件即可使用,画板中包括以下结点:
 Connection-连接
 Event---事件
 Task--任务
 Gateway---网关
 Container-容器
 Boundary event-边界事件
 Intermediate event- -中间事件
 流程图设计完毕保存生成.bpmn文件。

1.搜索插件

2.开始安装

3.安装进度

4.安装完成

5.查看结果

Activiti-Designer 使用

1.创建BPMN文件

2.绘制流程图

3.导出图片

4.解决中文乱码问题

启动一个流程实例

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 学习笔记

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 学习笔记

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 学习笔记

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 == falseshipOrder == true,将会创建两个任务。如果只有一个流程变量等于true,则只会创建一个任务。如果没有条件计算为true,会抛出异常,并可通过指定出口顺序流

举个例子:启动流程的时候传入下面的参数,执行流程。

HashMap<String, Object> variableMap = new HashMap<String, Object>();

variableMap.put("receivedPayment", true);

variableMap.put("shipOrder", true);

在上面的例子中,只有ship order 一个任务会被创建,当这个任务完成后,第二个包容网关会合并这两个执行,并且由于只有一条出口顺序流,不会再创建并行执行路径,只会激活Archive Order任务继续后面的流程。

case:

当出现这样的场景:你申请请假,如果是病假,找项目经理审批,如果不是找部门经理审批。但是他俩不管谁签完都可以进行个节点。说白了就像是排他跟并行的合体

注意,如果同一个包含节点拥有多个进入和外出顺序流, 它就会同时含有分支和汇聚功能。 这时,网关会先汇聚所有拥有流程token的进入顺序流, 再根据条件判断结果为true的外出顺序流,为它们生成多条并行分支。

基于事件的网关 Event-based Gateway

这个网关相比其他三个网关,用的相对没那么多,主要还是根据业务需求吧

该网关在执行出口处,要连接一个捕获中间事件。当流程执行到该网关时,流程类似处于等待的状态,此时执行被暂停,并创建一个事件订阅

需要订阅什么事件。基于下列约束:

当然,新手看到了这里可能还是不知道什么是事件网关,它到底有什么用处呢?下面,我用一个例子来解释一下:

上图使用了事件网关、定时器事件,信号捕捉事件,当启动流程后,流程实例订阅alert信号事件,并创建一个 5分钟后触发的定时器。这使得流程引擎等待5分钟,并等待信号事件。如果信号在5分钟内触发,则定时器会被取消,执行沿着信号继续。如果信号未被触 发,执行会在定时器到时后继续,并取消信号订阅。

一句话概括事件网关,如果你提交了请假申请,此时流程处于等待状态,当某个事件触发时,流程走到用户任务,办理了你的请假申请,那么等待会被取消,如果超过1天,还是没有触发某个事件,那么你的请假单直接跳过你的上级,直接往后面的流程走

case:

基于事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。 与此同时,会为每个外出顺序流创建相对的事件订阅。

注意基于事件网关的外出顺序流和普通顺序流不同。这些顺序流不会真的"执行"。 相反,它们让流程引擎去决定执行到基于事件网关的流程需要订阅哪些事件。 要考虑以下条件:

Activiti 转载文章

整理从其他网站转载比较好的文章

Activiti 转载文章

Activiti7的核心详解


Activiti7的核心详解

流程定义

流程定义是线下按照bpmn2.0标准去描述业务流程,通常使用activiti-explorer(web控制台)或activiti-eclipse-designer插件对业务流程进行建模,这两种方式都遵循bpmn2.0标准。

使用activiti-desinger设计业务流程,会生成.bpmn文件

.png图片需手动生成,在IDEA中步骤如下:

打开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_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.分配任务负责人

在进行业务流程建模时指定固定的任务负责人,在properties视图中,填写Assignee项为任务负责人。

由于固定分配方式,任务只管一步一步执行任务,执行到每一个任务将按照bpmn的配置去分配任务负责人。

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());
}

任务监听器是发生对应的任务相关事件时执行自定义java逻辑或表达式

定义任务监听类,且类必须实现org.activiti.engine.delegate.TaskListener接口

public class MyTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        delegateTask.setAssignee("赵六");
    }
}

@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.流程变量的使用方法

4.使用Global变量控制流程

员工创建请假申请单,由部门经理审核,部门经理审核通过后请假3天及以下由人事经理直接审核,3天以上先由总经理审核,总经理审核通过再由人事经理存档。

请假天数大于3连线条件

在启动流程时设置流程变量,变量的作用域是整个流程实例。通过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());
}

在完成任务时设置流程变量,该流程变量只有在该任务完成后其它结点才可使用该变量,它的作用域是整个流程实例,如果设置的流程变量的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("任务执行完毕");
    }
}
@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()获取流程变量

@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流程变量

任务办理时设置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.办理组任务

@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());
    }
}
/**
 * 用户拾取组任务,该任务变为自己的个人任务
 */
@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("任务拾取成功");
    }
}
@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("用户任务执行完毕...");
    }
}

如果个人不想办理该组任务,可以归还组任务,归还后该用户不再是该任务的负责人

/**
 * 归还组任务,由个人任务变为组任务,还可以进行任务交接
 */
@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);
    }
}
/**
 * 任务交接,前提要保证当前用户是这个任务的负责人,这时候他才可以有权限去将任务交接给其他候选人
 */
@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.排他网关

排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,注意,排他网关只会选择一个为true的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行)

在部门经理审核后,走排他网关,从排他网关出来的分支有两条,一条是判断请假天数是否大于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.并行网关

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:

如果同一个并行网关有多个进入和多个外出顺序流,它就同时具有分支和汇聚功能。这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略

财务结算和行政考勤是两个execution分支,在act_ru_execution表有两条记录分别是财务结算和行政考勤,act_ru_execution还有一条记录表示该流程实例。待财务结算和行政考勤任务全部完成,在汇聚点汇聚,通过parallelGateway并行网关。并行网关在业务应用中常用于会签任务,会签任务即多个参与者共同办理的任务。

流程定义部署-->启动流程设置流程变量-->执行任务

@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.包含网关

包含网关可以看做是排他网关和并行网关的结合体。和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。 包含网关的功能是基于进入和外出顺序流的:

企业体检流程,公司全体员工进行常规项检查、抽血化验,公司管理层除常规检查和抽血化验还要进行增加项检查。

员工类型:通过流程变量userType来表示,如果等于1表示普通员工,如果等于2表示领导

Activiti 转载文章

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技术负责人的你是否有以下选型烦恼:

  1. 选国内闭源产品(比如蓝凌OA),受制于大厂,费用高,周期慢,因产品框架限制不一定能实现较特殊的需求;
  2. 选的技术太旧(比如东软开发平台),开发出的项目,开发扩展困难,和其他业务系统集成困难,开发人员抵触;
  3. 选国内假开源产品,技术能力参差不齐,产品包装的很牛,但用起来各种细节问题;

为什么选基于Liferay + Activiti 开发企业门户产品

  1. Liferay是最优秀的开源企业门户,使用企业数千,使用者数千万,产品稳定性和先进度有保障,功能极具扩展性;
  2. Liferay移动端界面支持好(H5自适应);
  3. Liferay自身的BPM较弱,所以需要集成较强的BPM,而Activiti是非常优秀的BPM产品,
  4. Activiti 使用者众多,功能灵活,上手难度不大,它的前身jBPM在jBoss控制下越来越重,所以不选jBPM;
  5. Activiti BPM满足国际BPMN2.0规范,选用Activiti BPM在流程迁移方面有规范、风险低;
  6. 有代码,不会受制于人,更可控;
  7. 开发风险可控,最核心的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 )类型

有点动态表单的概念,这个和Sharepoint的List Field很相似。

支持的元数据(Metadata )类型有:

控制页面发布展示和过期时间

scheduling web-content publication

内容搜索

liferay的内容搜索异常强大,不多赘述。

详见:What is Liferay Portal

3、管理用户、组织、权限

组织管理

新建组织,并设置上级组织

Type :regular organization or a location,如果选location则没有下级组织

添加用户

用户组

用户组是剥离组织架构的独立逻辑分组,一个用户可以分配给多个用户组。

例如,公司的办公室/部门结构既可以通过组织机构进行建模。也能创建用户组,比如:

一个用户组可以创建一群人独立于他们的组织机构,使它更容易分配一个或多个角色,比如一次性分配权限给所有的JAVA开发人员。 向属于用户组的用户提供预定义的公共或私有页面。例如:

下面是创建(博客管理员)用户组的演示页面:

角色

角色是用来定义一个特定功能的权限(根据特定范围)

一个角色基本上只是一个定义了一个功能的权限集合,如留言板管理员。这有点容易和用户组混淆,但实际上角色还有范围的管辖权限控制

可以细化到4种范围类型选择(Regular role、Site role、Organization role、Team)

导航到控制面板,然后单击“角色”,可以让您创建角色、分配权限给他们,将用户分配给角色。

一个角色仅具有一定管辖范围的作用。比如

用户验证

支持的用户认证方式:

4、文档管理

发布文件上传

发布元数据(Metadata )数据集

就是通过动态字段建立的数据

分布式集群文档存储

Liferay Portal的文件和媒体文件可以存储在许多不同的服务器或其他媒介方式,

默认情况下,Liferay Portal使用文档库中存储的选择被称为简单的文件存储在文件系统上。

您也可以使用一个完全不同的方法来存储文档和媒体文件:

文件同步客户端

有些类似百度云客户端,在授权的情况下,可以把文档库同步到个人电脑硬盘。

详情:Using Liferay Sync on Your Desktop

移动端访问文档库

移动端编辑文件

5、企业协作

博客

论坛

用户心情

Wiki

书签

企业微博

通知

投票

集成XMPP Web聊天

通过集成jabber方式和openfire通讯

集成Email

Liferay包含一个邮件插件,可以作为web邮件客户端

5、管理应用

Liferay的强大之处在于不仅内置应用繁多,它还提供易于开发的扩展体系,提供即插即用的平台支持,海量应用商店支持。

管理应用

应用商店

应用商店有数千个免费或商业插件

应用的类别:

以上内容为官方文档的微缩版:The Liferay Distinction

6、企业功能

涉及用户数据列表、表单模板、高级表单定制、工作流。

放在下一篇介绍。

多谢观看!

Activiti 转载文章

Liferay-Activiti 企业特性功能介绍(新版Liferay7)

前言

如果你是开发者

你已经是多少次开发一个项目,一次次的用一些框架,一次次的写类似的重复的代码,一次次建表\写类和方法\写HTML\CSS\JAVASCRIPT,一次次测试,一次次的写Bug。。。

如果有一个平台,提供基线的框架,可以是应用程序\网站,支持移动端,不必一次次开发无法重用,一次次造就信息孤岛。

那么试试Liferay。 很多的应用开箱即用,如CMS\博客\企业协作\动态表单\良好的组织架构和权限体系。

先别急着上船,你得接受和适应Liferay的扩展框架体系,比如Portlet,还有应用程序显示模版的机制,这有些代价,但对于真正的JavaEE开发者,并不会困难,另外你还会学到另你获益终身的设计模式思想,OK,想好了就上船吧,这必将是愉快的旅程。

如果你是IT主管

可能选型选择开源不容易,哪怕是Liferay如此成功的产品,实际上Liferay的企业服务费用不算低,要节省成本用社区版,必然要有好的开发服务团队。

这必须非常谨慎,诚然,Liferay还有软肋,这个是其产品定位造成的,比如:

言归正传,介绍Liferay的企业特性。

Liferay7架构 :

模块:

权限和认证体系

Liferay支持 权限、组、用户、团队、组织架构 的权限控制体系,详情见上一篇文章

支持的用户认证方式:

开发扩展方式

Liferay几乎什么都可以定制。应用程序接口可以重新设计,整个用户界面可以定制为主题,菜单项可以添加或删除...

所有的应用程序及扩展,是建立在liferay自定义分布式部署模块(典型的jar文件),用Java开发人员熟悉的方式开发,编译,定义模板,资源,和一些元数据。

它遵循一套非常强大的标准称为OSGi。多模块可以相互依赖、相互沟通,实时部署,不用重启服务器(热部署)。

模块可以有一个或多个组件。创建一个组件和Java类开发一样简单。

一个组件是一个更大的应用程序的最小的构建块,并且应用程序本身是由许多小的组件组成的,就是以重复使用的堆积木的方式开发系统。

组件由组件容器管理,该容器提供安装和激活。组件提供服务,通过一个强大的依赖管理系统,在运行时自动处理。

您可以编写组件以提供新的服务或以重写现有的服务,容器管理所有一切。Liferay是一个激动人心的平台,使开发人员更高效。

扩展方式介绍:

可以使用现有框架,如Struts、Spring。使用Service Builder,很容易创建后台数据库表、对象关系映射。

它还可以生成JSON或SOAP的Web服务,为开发者提供完整的开发元素用于存储和检索数据,用于Web或移动客户端。

工作流

Liferay自身支持的BPM工作流:

  1. Kaleo,集成Liferay表单的内置工作流,极简单,没有图形设计器;

  2. 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支持的数据库:

Activiti的集成开发方式有:

所有的开发重点在UI的集成、用户权限的集成、单点登录、流程设计器、控制面板、和Liferay表单的集成,待办已办待阅面板开发,工程较大。

目前Activiti只有用户组、用户上级、用户三种权限概念(用于流程节点分配);

扩展更复杂更集成业务系统的权限机制的方案: synchronize-or-redesign-user-and-role-for-activiti

一些企业扩展

企业社交代理

通过整合OAuth服务作为一个HTTP代理服务器的应用程序,该插件为您的应用程序提供了一个安全的令牌,可以将类似的网站推特,LinkedIn、微信;

图表插件

Chart portlet

一个使用 Liferay 和 Lucene 实现企业门户智能帮助机器人的方法

很有意思

使用 Liferay 和 Lucene 实现企业门户智能帮助机器人

企业门户智能帮助机器人总体架构图

简易敏捷插件

Scrum Portlet

用户反馈插件

Contour Dispatch – The Feedback Portlet

相册插件

Photo Library Portlet

Image rotator Carousel

MongoDB CRUD 简单应用插件

features.

  1. Insert New Record (in Collection/Table).
  2. Update inserted records.
  3. Delete Single/Multiple Record(s).
  4. Sorting (Ascending/Descending)
  5. Searching (AND search / OR search)

Liferay MongoDB CRUD Application

集成导入插件

Webservice Content Import

使用这个Web服务插件,你可以从其他平台导入内容到Liferay中

更多插件

在应用商店:Marketplace

选择Liferay的好处是除了自带的强大功能,和海量商店应用,它还是可以灵活开发扩展的平台。

Activiti 转载文章

比较Activiti中三种不同的表单及其应用

转自:比较Activiti中三种不同的表单及其应用

开篇语

这个恐怕是初次接触工作流最多的话题之一了,当然这个不是针对Activiti来说的,每个工作流引擎都会支持多种方式的表单。目前大家讨论到的大概有三种。

  1. 动态表单
  2. 外置表单
  3. 普通表单

具体选择哪种方式只能读者根据自己项目的实际需求结合现有技术或者架构、平台选择!!!

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>

下面是一个简单的动态表单的单元测试,读者可以下载运行以便更明确执行过程和判断动态表单能不能在企业项目中使用。

下载之后复制到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>

同样也提供了单元测试:

注意:表单的内容都是以key和value的形式数据保存在引擎表中!!!

3.普通表单

这个是最灵活的一种方式,常用于业务比较复杂的系统中,或者业务比较固定不变的需求中,例如ERP系统。

普通表单的特点是把表单的内容存放在一个页面(jsp、jsf、html等)文件中,存放方式也有两种(一体式、分离式):

  1. 一体式:把整个流程涉及到的表单放在一个文件然后根据处理的任务名称匹配显示,kft-activiti-demo的普通表单模式就是一体式的做法,把表单内容封装在一个div里面,div的ID以节点的名称命名,点击“办理”按钮时用对话框的方式把div的内容显示给用户。

  2. 分离式:对于非Ajax应用来说比较常用,每个任务对应一个页面文件,点击办理的时候根据任务的ID动态指定表单页面。

本博客发布的Activiti入门Demo中有演示: Activiti快速入门项目-kft-activiti-demo

和以上两种方式比较有两点区别:

  1. 表单:和第二种外置表单类似,但是表单的显示、表单字段值填充均由开发人员写代码实现。
  2. 数据表:数据表单独设计而不是和前两种一样把数据以key、value形式保存在引擎表中。

4.从业务数据和流程关联比较

  1. 动态表单:引擎已经自动绑定在一起了,不需要额外配置。
  2. 外置表单:和业务关联是可选的,提供的例子中是没有和业务关联的,如果需要关联只需要在提交StartForm的时候设置businessKey即可。
  3. 普通表单:这个应该是必须和业务关联,否则就是无头苍蝇了……,关联方式可以参考:工作流引擎Activiti使用总结中的2.3 业务和流程的关联方式

5.结束语

技术只是辅助工具,只能决定这件事能不能做,如何选择要看应用场合,希望简单的比较能提供一点思路。
Activiti 转载文章

[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 转载文章

[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【在流程引擎启动和关闭时处理数据库模式】如下(摘自官网)

准备步骤

1.测试项目结构:主要为做工作流部署的两种形式,zip为bpmn与png文件的压缩文件

2.安装eclipse activiti插件

eclipse安装activiti工作流插件

使用IDEA开发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 转载文章

[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 转载文章

[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 转载文章

[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:后台管理

Flowable 学习章节

Activiti与Flowable对比


这里对比 Activiti7Flowable6

对比框架简介

对比框架选择

因此,这里对比activiti7和flowable6

Roadmap对比

Activiti的roadmap

Flowable的roadmap

可以看出:

功能与其他总体对比

公司的个人初步感觉:

结论

附录

Flowable roadmap

Activiti 7 Roadmap

Salaboy

Flowable 学习章节

flowable6和activit7的对比中文翻译列表

Flowable6(比activit6多的功能)

Activiti7(比activiti6多的功能)

总结:

  1. Activiti7好像基本叫Activiti Cloud7,专注于cloud的开发,包括与Zuul、Eureka、Zipkin、Sping Cloud、Docker、Kubernetes、ELK、Jenkins(持续集成)等功能;同时还包括一些我们用不到的云方面的功能,包括Cloud Native(云原生?)、KNative(谷歌的serverless开源框架)、Istio(service mesh的一个开源实现)、JHispter(前端的微服务框架?)、AWS(肯定不会与阿里云整合的)等;还有一些我看不懂的云方面的技术名词。

  2. 而Flowable专注于工作流引擎在NoSQL、消息队列的实现,可以完全不用关系型数据库,通过消息队列异步也可以提高效率;还专注于CMMN、DMN等流程规范、规则引擎方面的功能;还有如JUnit5(单元测试)、Jupiter(代码审查)等的功能;未来也打算开发K8s的整合功能。也就是主要专注于工作流引擎核心的功能。

附录:

Activiti7最新开发路线图(中文翻译)

http://www.shareniu.com/article/176.htm

http://www.shareniu.com/article/151.htm

Flowable最新版(6.4/6.3.1/6.3/6.2/6.1.1/6.1) 新特性(中文翻译)

http://www.shareniu.com/article/200.htm

http://www.shareniu.com/article/199.htm

http://www.shareniu.com/article/194.htm

http://www.shareniu.com/article/178.htm

http://www.shareniu.com/article/120.htm

http://www.shareniu.com/article/107.htm

Flowable v5 和v6版本的区别 http://www.shareniu.com/article/85.htm

Flowable 学习章节

Flowable 框架


Flowable 框架学习笔记

Flowable 入门介绍

官网地址:flowable.org

Flowable BPMN 用户手册 (v 6.3.0)

Flowable BPMN 用户手册 (v 6.4.2-SNAPSHOT)

Flowable BPMN 用户手册 (v 6.5.0-SNAPSHOT)

可以在官网下载对应的jar包在本地部署运行,官方提供了下面的五个应用程序:

初识Flowable五大引擎

Flowable有五大引擎,每个之间都是相互独立互不影响。

ProcessEngine是里面最核心也是最重要的一个引擎,如果失去它那Flowable也就意义了。

五大引擎

流程引擎使用架构

Flowable引擎在使用前需要先通过配置来初始化ProcessEngine。

初始化ProcessEngineConfiguration一般有两种方式:

  1. 通过Spinrg配置文件进行依赖注入,通过flowable.cfg.xml文件来初始化ProcessEngineConfiguration(这里的文件名必须为flowable.cfg.xml,否则Flowable识别不到)

  2. 通过编写程序的方式来构造ProcessEngineConfiguration对象

流程引擎API架构图

模型图

ProcessEngineConfiguration在初始化过程中会同时初始化数据库,如果数据库已经存在,则不会做创建更新操作,如果数据库不存在,则会默认执行数据库创建脚本。

流程引擎初体验

  1. 简单了解Bpmn

    1. Task任务:

      用户任务(userTask)

      系统任务(serviceTask )

    2. Event事件:

      定时器事件(timerEventDefinition)

    3. Gateway网关:

      排他网关(exclusive gateway)

  2. 目标:实现以下简化版的请假流程

请假流程图(简单版)

Flowable的用户权限体系

在接入Flowable的用户权限体系的时候,有四种方式:

  1. 使用Flowable提供的默认IdmEngine进行用户体系管理,该引擎包含了用户、组的概念。

  2. 集成LDAP,实现轻量级用户权限管理。通过IdentityService进行认证,用于由IdentityService处理所有认证业务的场景。

  3. 实现IdmIdentityService接口,自定义实现用户、组的查询

  4. 接入自定义的权限体系

    用户id => 获取到租户id、角色id集、部门id集

    1. 单用户(assignee="用户id")、多用户(candidateUsers="用户id1,用户id2")

    2. 单角色、多角色(candidateGroups=":角色id1,:角色id2")

    3. 单部门、多部门(candidateGroups="部门id1:,部门id2:")

    4. 角色或部门(candidateGroups="角色id1:, :部门id1")

    5. 角色且部门

使用Flowable工作流引擎的时候,不可避免就需要考虑相应的用户权限,

根据官方文档提供的教程,实现Flowable的用户权限体系总共有两大类:

Flowable提供的IdmEngine身份识别引擎

使用Flowable提供的IdmEngine,也有三种方案:

自定义身份识别引擎

有时候在项目上已经实现了自己的用户体系,接入Flowable的工作流引擎时,就需要考虑如何将自己的用户体系映射到Flowable工作流引擎的数据权限上去。Flowable提供的数据权限体系是用户、组、租户的结构体系,因为这里提供一个映射的设计思路:

在实际项目中,往往用户会存在租户、角色、部门之间的对应关系,因而在对应到Flowable的用户、组、租户的时候可以用以下思路来处理:

用户权限体系映射

具体的案例如下:

用户A(id值为1) - 角色A(id值为1) - 部门A(id值为1) - 区域A(id值为1)(某个租户的用户A是角色A,所在部门为部门A,所属区域为区域A)

根据上述的映射关系转换成如下

将映射关系对应到Flowable中

知道如果映射后,现在的问题就是在哪里去实现这部分的映射关系,以下提供几种实现的思路,目前还没正式验证过,仍处于实验阶段:

其中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_RE_DEPLOYMENT 流程部署

ACT_RE_PROCDEF 流程定义表

ACT_RU_EXECUTION 流程实例执行过程的所有节点记录

ACT_RU_TASK 流程实例运行时的任务表

ACT_RU_VARIABLE 流程实例运行时节点的变量表

ACT_GE_BYTEARRAY 资源文件表

Flowable 学习章节

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 学习章节

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.流程图:

Flowable 学习章节

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中直接设置:

Flowable 学习章节

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;
Flowable 学习章节

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

act_ru_execution

此时请求加签方法:

http://localhost:8080/flowabledemo/flow/addExecution/UserTask2/55001/test004  

流程会增加一个新的子实例,并且会增加相对应的参数,如下:

act_ru_task

act_ru_execution

act_ru_variable

此时如果请求减签的方法:

http://localhost:8080/flowabledemo/flow/delExecution/55034/0

流程中相对应的Task和Variable会被删除:

act_ru_task

以上,就是本次试验的内容,需要注意的是,在减签时,如果Task正好是该多实例节点中的最后一个,将导致流程无法继续流转。下次可以分析一下源码,看看为何会这样。

Flowable 学习章节

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,导致整个流程中断。

Flowable 学习章节

Flowable6.4 – 绘制流程图

Flowable6.4 – 绘制流程图

一般需要流程图的场景:

  1. 发起流程时,需要从全局了解整体情况、所涉及的经办人,便于必要时进行催办或者发起线下沟通。

  2. 流程运行时或结束后,查看流程所经历的办理过程。

在Flowable中,流程图的绘制可以参见:

org.flowable.image.impl.DefaultProcessDiagramGenerator

本文将分成两部分,简单介绍一下流程图的绘制和办理节点的高亮现实。

首先,如何绘制流程图。

总共三个步骤:

  1. 获得流程定义的BpmnModel。

  2. 根据BpmnModel获得图片流。

  3. 输出图片流。

直接上代码了,基本上都是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 任务节点多实例使用

我们在使用Flowable 工作流引擎的时候,最常用的肯定是任务节点,因为在OA系统、审批系统、办公自动化系统中核心的处理就是流程的运转,在流程运转的时候,可能我们有这样的一个需求,在一个任务节点的时候,我们需要多个人对这个节点进行审批,比如实际中这样一个例子,假如是一个部门的投票,这个部门有5个人,那么当5个人都投票的时候大概分为如下几种:

  1. 部门所有人都去投票,当所有人都投票完成的时候,这个节点结束,流程运转到下一个节点。(所有的人都需要投票)

  2. 部门所有人都去投票,只要有任意2/3的人同意,这个节点结束,流程运转到下一个节点。(部分人投票只要满足条件就算完成)。

  3. 部门中有一个部门经理,只要部门经理投票过了,这个节点结束,流程运转到下一个节点(一票否决权)。

  4. 部门中根据职位不同,不同的人都不同的权重,当满足条件的时候,这个节点结束,流程运转到下一个节点。比如说所有的人员权重加起来是1,a有0.2的权重,其他的四个人分别是0.1的权重,我们可以配置权重达到0.3就可以走向下一个节点,换言之a的权重是其他人的2倍,那就是a的投票相当于2个人投票。这种需求还是很常见的。

  5. 部门所有人都去投票,a投票结束到b,b开始投票结束到c,一直如此,串行执行。最终到最后一个人再统计结果,决定流程的运转。

上面的五种情况,我们可以提取出来一些信息,我们的activiti 工作流引擎,必须支持如下功能,才能满足上面的需求:

  1. 任务节点可以配置自定义满足条件。

  2. 任务节点必须支持串行、并行。

  3. 任务节点必须支持可以指定候选人或者候选组。

  4. 任务节点必须支持可以循环的次数。

  5. 任务节点必须支持可以自定义权重。

  6. 任务节点必须支持加签、减签。(就是动态的修改任务节点的处理人)

因为实际上的需求可能比上面的几种情况更加的复杂,上面的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配置文件的部分含义如下:

  1. flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" 这个节点可以4个人审核。
  2. <loopCardinality>2</loopCardinality> 循环2次结束。
  3. <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表的数据有了如下图示:

上面重要的变量需要解释一下,要不然还真不好理解,多任务是怎么运转的。

  1. nrOfInstances 实例总数。

  2. nrOfCompletedInstances 当前还没有完成的实例 nr是number单词缩写 。

  3. loopCounter 已经循环的次数。

  4. 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>

参数说明:

三个参数结合决定了,当前节点的处理人来自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了,测试一下,确实程序如预期的所示,大功告成。

总结

参数的使用总结

  1. flowable:candidateUsers="shareniu1,shareniu2,shareniu3,shareniu4" 这个节点可以4个人审核。

  2. loopCardinality>2</loopCardinality> 循环2次结束。

  3. <multiInstanceLoopCharacteristics isSequential="true"> 串行并行的配置。 <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.25}</completionCondition> 完成条件的配置。

这里我们还可以得出一个结论:

如果使用串行方式操作nrOfActiveInstances 变量始终是1,因为并行的时候才会去+1操作。

遗留点

上面的程序已经解决了常用的问题,关于会签、加签、减签、退签、权重配置、自定义通过条件配置(条件自定义通过)

这些问题,这个章节还没有具体的实现,关于这些问题,由于本章内容有限,我们就后续章节讲解吧。