基于Activiti扩展的工作流引擎OpenWebFlow

开源地址:https://github.com/bluejoe2008/openwebflow(欢迎star)

1. OpenWebFlow概述

OpenWebFlow是基于Activiti扩展的工作流引擎。Activiti (官方网站http://activiti.org/,代码托管在https://github.com/Activiti/Activiti)是一个新兴的基于 Apache 许可的支持 BPMN 2.0 标准的开源 BPM 产品,它是一个轻量级,可嵌入的 BPM 引擎,并且提供了功能丰富的开发和流程设计工具。OpenWebFlow与业务应用系统之间的关系如下图所示。

image

相对于Activiti,OpenWebFlow扩展的功能包括:

  1. 完全接管了Activiti对活动(activity)权限的管理。
    Activiti允许在设计model的时候指定每个活动的执行权限,但是,业务系统可能需要根据实际情况动态设置这些任务的执行权限(如:动态的Group)。OpenWebFlow完全实现了与流程定义时期的解耦,即用户对活动的访问控制信息单独管理(而不是在流程定义中预先写死),这样有利于动态调整权限,详见自定义活动权限管理;

  2. 完全接管了Activiti对用户表(IDENTITY_XXX表)的管理。
    在标准的工作流定义中,每个节点可以指定其候选人和候选用户组,但是比较惨的是,Activiti绑架了用户信息表的设计!这个是真正致命的,因为几乎每个业务系统都会属于自己的用户信息结构(包括User/Group/Membership),但不一定它存储在Activiti喜欢的那个库中,表的结构也不一定一样,有的时候,某些信息(如:动态的Group)压根儿就不采用表来存储。OpenWebFlow剥离了用户信息表的统一管理,客户程序可以忘掉Activiti的用户表、群组表、成员关系表,详见自定义用户成员关系管理;

  3. 允许运行时定义activity!
    彻底满足“中国特色”,并提供了安全的(同时也是优雅的)催办、代办、加签(包括前加签/后加签)、自由跳转(包括前进/后)、分裂节点等功能;

2. 快速上手

2.1 引入OpenWebFlow框架

2.1.1 以jar的方式引入OpenWebFlow

OpenWebFlow的发布形式是一组正常的jar,其中openwebflow-core.XXX.jar包含了核心的工作流控制模块,以及基于内存的管理器实现模块。

此外,OpenWebFlow还提供了几个jar:openwebflow-mgr-hibernate.XXX.jar,openwebflow-mgr-mybatis.XXX.jar,它们提供了管理器的SQL实现模块,分别选取hibernate和mybatis作为ORM模型。还有一个是openwebflow-mgr-test.XXX.jar,它包含了几个测试类。

最新版本的下载地址:

https://bluejoe2008.github.io/openwebflow/openwebflow-core/target/openwebflow-core-0.9-SNAPSHOT.jar

注意这些jar具有较多的依赖:

  • activation-1.1.jar
  • activiti-bpmn-converter-5.16.1.jar
  • activiti-bpmn-layout-5.16.1.jar
  • activiti-bpmn-model-5.16.1.jar
  • activiti-crystalball-5.16.1.jar
  • activiti-engine-5.16.1.jar
  • activiti-explorer-5.16.1.jar
  • activiti-image-generator-5.16.1.jar
  • activiti-json-converter-5.16.1.jar
  • activiti-process-validation-5.16.1.jar
  • activiti-simple-workflow-5.16.1.jar
  • activiti-spring-5.16.1.jar
  • aopalliance-1.0.jar
  • commons-collections-2.0.jar
  • commons-dbcp-1.4.jar
  • commons-email-1.2.jar
  • commons-io-2.4.jar
  • commons-lang-2.6.jar
  • commons-lang3-3.3.2.jar
  • commons-logging-1.1.1.jar
  • commons-pool-1.5.4.jar
  • dcharts-widget-0.10.0.jar
  • groovy-all-2.1.3.jar
  • h2-1.3.168.jar
  • hamcrest-core-1.3.jar
  • imgscalr-lib-4.2.jar
  • jackson-annotations-2.2.3.jar
  • jackson-core-2.2.3.jar
  • jackson-databind-2.2.3.jar
  • javaGeom-0.11.1.jar
  • jcl-over-slf4j-1.7.6.jar
  • jgraphx-1.10.4.1.jar
  • joda-time-2.1.jar
  • junit-4.12.jar
  • log4j-1.2.17.jar
  • mail-1.4.1.jar
  • mybatis-3.2.8.jar
  • mybatis-spring-1.2.2.jar
  • mysql-connector-java-5.1.32.jar
  • servlet-api-2.5.jar
  • slf4j-api-1.7.2.jar
  • slf4j-jdk14-1.7.2.jar
  • slf4j-log4j12-1.7.6.jar
  • spring-aop-3.2.4.RELEASE.jar
  • spring-beans-3.2.4.RELEASE.jar
  • spring-context-3.2.4.RELEASE.jar
  • spring-core-3.2.4.RELEASE.jar
  • spring-expression-3.2.4.RELEASE.jar
  • spring-jdbc-3.2.4.RELEASE.jar
  • spring-orm-3.2.4.RELEASE.jar
  • spring-tx-3.2.4.RELEASE.jar
  • spring-web-3.2.4.RELEASE.jar
  • spring-webmvc-3.2.4.RELEASE.jar
  • vaadin-6.8.8.jar

2.1.2 以maven的方式引入OpenWebFlow

以maven的方式引入OpenWebFlow比较简单,pom.xml中的依赖项写成:

<dependency>
     <groupId>org.openwebflow</groupId>
     <artifactId>openwebflow-core </artifactId>
     <version>0.9-SNAPSHOT</version>
</dependency>

在引入依赖项之前可能需要先在本地仓库中安装OpenWebFlow项目。具体操作是在eclipse中选择OpenWebFlow项目,【右键菜单】【Maven】【install】。

2.2 配置文件

准备SpringIoC配置文件,分别是settings.properties 、activiti.cfg.core.xml和activiti.cfg.mem.xml(或者是activiti.cfg.sql.XXX.xml):

  • settings.properties:公共属性设置
  • activiti.cfg.core.xml:用以配置工作流引擎的基本配置信息;
  • activiti.cfg.mem.xml:用以定义一些用以支持OpenWebFlow工作的manager,注意名字中的mem,它暗示着仅提供了那些manager的基于内存实现的版本,类似的配置文件还可以是activiti.cfg.sql.XXX.xml;

2.2.1 settings.properties

settings.properties文件是一个正常的属性文件,用以spring IOC文件加载。如下是一个属性文件的内容:

mail.host=smtp.bluejoe.cn
mail.port=25
mail.username=xxx@xxx.cn
mail.password=xxx
mail.from=xxx@xxx.cn
model.dir=../models
alarm.mail.template=classpath:/alarm-template.txt
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.hbm2ddl.auto=none
activitidb.url=jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000
activitidb.driver=org.hDriver
activitidb.username=sa
activitidb.password=
owfdb.url=jdbc:mysql://localhost:3306/openwebflow?useUnicode=true&amp;characterEncoding=UTF-8
owfdb.driver=com.mysql.jdbc.Driver
owfdb.username=root
owfdb.password=1

各属性的含义如下:

属性名 示例值 含义
mail.host smtp.bluejoe.cn 催办邮件发件服务器主机地址
mail.port 25 催办邮件发件服务器端口号
mail.username xxx@xxx.cn 催办邮件发件账号名
mail.password xxx催办邮件发件账号密码
mail.from xxx@xxx.cn 催办邮件发件人
model.dir ../models 自动加载的BPMN模型路径
alarm.mail.template classpath:/alarm-template.txt 催办邮件正文模板
hibernate.dialect org.hibernate.dialect.MySQLDialect Hibernate方言
hibernate.hbm2ddl.auto none Hibernate DDL设置
activitidb.url jdbc:h2:mem:activiti;DB_CLOSE_DELAY Activiti数据库JDBC URL
activitidb.driver org.h2.Driver Activiti数据库JDBC驱动
activitidb.username sa Activiti数据库账号名
activitidb.password Activiti数据库账号密码
owfdb.url jdbc:mysql://localhost:3306/openwebflow?useUnicode OpenWebFlow数据库JDBC URL
owfdb.driver com.mysql.jdbc.Driver OpenWebFlow数据库JDBC驱动
owfdb.username root OpenWebFlow数据库账号名
owfdb.password 1 OpenWebFlow数据库账号密码

2.2.2 activiti.cfg.core.xml配置

activiti.cfg.core.xml的配置与Activiti要求的那个配置文件有点相似,但可以多一些内容,如下是个例子:

<!-- 工作流核心数据库配置 -->
<bean id="activitiDataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${activitidb.driver}" />
    <property name="url" value="${activitidb.url}" />
    <property name="username" value="${activitidb.username}" />
    <property name="password" value="${activitidb.password}" />
    <property name="initialSize" value="20" />
    <property name="maxActive" value="50" />
    <property name="maxIdle" value="20" />
    <property name="minIdle" value="10" />
</bean>

<!-- 任务催办配置 -->
<bean id="myTaskAlarmService" class="org.openwebflow.alarm.impl.TaskAlarmServiceImpl">
    <!-- 截止日期提前量 -->
    <property name="periodInAdvance" value="P2D" />
    <!-- 设置消息通知机制 -->
    <property name="messageNotifier">
        <!-- 采用邮件发送 -->
        <bean class="org.openwebflow.alarm.impl.MailMessageNotifier">
            <property name="subjectTemplate" value="请尽快处理#{'$'}{task.name}任务" />
            <property name="messageTemplateResource" value="${alarm.mail.template}" />
            <property name="mailSender">
                <bean class="org.openwebflow.alarm.impl.MailSender">
                    <property name="serverHost" value="${mail.host}" />
                    <property name="serverPort" value="${mail.port}" />
                    <property name="authUserName" value="${mail.username}" />
                    <property name="authPassword" value="${mail.password}" />
                    <property name="mailFrom" value="${mail.from}" />
                </bean>
            </property>
        </bean>
    </property>
    <property name="membershipManager" ref="myMembershipManager" />
    <property name="userDetailsManager" ref="myUserDetailsManager" />
    <property name="taskNotificationManager" ref="myTaskNotificationManager" />
</bean>

<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="activitiDataSource" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

<!-- 配置对象 -->
<bean id="processEngineConfiguration" class="org.openwebflow.cfg.ProcessEngineConfigurationEx">
    <property name="dataSource" ref="activitiDataSource" />
    <property name="transactionManager" ref="transactionManager" />
    <property name="databaseSchemaUpdate" value="true" />
    <property name="jobExecutorActivate" value="false" />
    <property name="startEngineEventListeners">
        <list>
            <!-- 加载自定义表单元素类型 -->
            <bean class="org.openwebflow.cfg.LoadDummyFormTypes">
                <property name="typeNames" value="user" />
            </bean>
            <!-- 自定义成员关系管理 -->
            <bean class="org.openwebflow.cfg.ReplaceMembershipManager">
                <property name="customMembershipManager" ref="myMembershipManager" />
            </bean>
            <!-- 自定义活动权限管理 -->
            <bean class="org.openwebflow.cfg.ReplaceTaskAssignmentHandler">
                <!-- 授权处理器列表,会组成一个链,越靠后优先级越高(越靠外) -->
                <property name="handlers">
                    <list>
                        <!-- 自定义授权项列表 -->
                        <bean
                            class="org.openwebflow.assign.permission.ActivityPermissionAssignmentHandler">
                            <property name="activityPermissionManager" ref="myActivityPermissionManager" />
                        </bean>
                        <!-- 允许授权代理 -->
                        <bean
                            class="org.openwebflow.assign.delegation.TaskDelagationAssignmentHandler">
                            <property name="delegationManager" ref="myDelegationManager" />
                            <property name="membershipManager" ref="myMembershipManager" />
                            <property name="hideDelegated" value="false" />
                        </bean>
                    </list>
                </property>
            </bean>
            <!-- 自动导入流程模型 -->
            <bean class="org.openwebflow.cfg.ImportDefinedProcessModels">
                <property name="modelDir" value="${model.dir}" />
            </bean>
            <!-- 启动催办管理器 -->
            <bean class="org.openwebflow.cfg.StartTaskAlarmService">
                <property name="taskAlarmService" ref="myTaskAlarmService" />
                <property name="runOnStartup" value="false" />
            </bean>
            <!-- 加载自定义activity -->
            <bean class="org.openwebflow.cfg.LoadRuntimeActivityDefinitions">
                <property name="activityDefinitionManager" ref="myActivityDefinitionManager" />
            </bean>
        </list>
    </property>
</bean>

<!-- processEngine -->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
    <property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>

<!-- 工作流流转服务对象工厂 -->
<bean class="org.openwebflow.ctrl.impl.DefaultTaskFlowControlServiceFactory" />

<!-- processEngineTool -->
<bean id="processEngineTool" class="org.openwebflow.util.ProcessEngineTool" />

其中processEngineConfiguration是增强型的工作流引擎配置对象,可以设置自定义用户群组成员关系管理策略自定义活动权限管理策略等。

完整的例子参见:https://github.com/bluejoe2008/openwebflow/blob/master/openwebflow-test/src/test/resources/activiti.cfg.core.xml

2.2.3 activiti.cfg.mem.xml配置

在activiti.core.xml中会用到一些manager,activiti.mem.xml定义基于内存的manager实现,默认的activiti.mem.xml内容如下:

<!-- 自定义成员关系管理 -->
<bean id="myMembershipManager" class="org.openwebflow.mgr.mem.InMemoryMembershipManager" />
<bean id="myUserDetailsManager" class="org.openwebflow.mgr.mem.InMemoryUserDetailsManager" />

<!-- 自定义的活动权限表管理 -->
<bean id="myActivityPermissionManager"
    class="org.openwebflow.mgr.mem.InMemoryActivityPermissionManager" />

<!-- 代理关系管理 -->
<bean id="myDelegationManager" class="org.openwebflow.mgr.mem.InMemoryDelegationManager" />

<!-- 自定义的动态自定义活动管理 -->
<bean id="myActivityDefinitionManager"
    class="org.openwebflow.mgr.mem.InMemoryRuntimeActivityDefinitionManager" />

<bean id="myTaskNotificationManager" class="org.openwebflow.mgr.mem.InMemoryTaskNotificationManager" />

这里面定义了6个manager:

Manager类别 含义
myMembershipManager 自定义成员关系管理
myUserDetailsManager 自定义用户详细信息管理
myActivityPermissionManager 自定义的活动权限表管理
myDelegationManager 代理关系管理
myActivityDefinitionManager 自定义的动态自定义活动管理
myTaskNotificationManager 任务通知信息管理

完整的例子参见:https://github.com/bluejoe2008/openwebflow/blob/master/openwebflow-test/src/test/resources/activiti.cfg.mem.xml

与activiti.cfg.core.xml类似的可替代文件为activiti.cfg.sql.hibernate.xml和activiti.cfg.sql.mybatis.xml。

2.2.4 activiti.cfg.sql.hibernate.xml配置

activiti.sql.hibernate.xml提供基于SQL的manager实现,采用的ORM框架为Hibernate 4。

各manager的定义如下:

<!-- 代理记录管理 -->
<bean id="myDelegationManager"
    class="org.openwebflow.mgr.hibernate.service.SqlDelegationManager" />
<!-- 自定义成员关系管理 -->
<bean id="myMembershipManager"
    class="org.openwebflow.mgr.hibernate.service.SqlMembershipManager" />
<!-- 自定义用户表 -->
<bean id="myUserDetailsManager"
    class="org.openwebflow.mgr.hibernate.service.SqlUserDetailsManager" />
<!-- 自定义的活动权限表管理 -->
<bean id="myActivityPermissionManager"
    class="org.openwebflow.mgr.hibernate.service.SqlActivityPermissionManager" />
<!-- 自定义的动态自定义活动管理 -->
<bean id="myActivityDefinitionManager"
    class="org.openwebflow.mgr.hibernate.service.SqlRuntimeActivityDefinitionManager" />
<bean id="myTaskNotificationManager"
    class="org.openwebflow.mgr.hibernate.service.SqlTaskNotificationManager" />

此外,还需要定义数据源、Hibernate Session工厂,以及事务。

<!-- 数据库脚本见openwebflow.sql -->
<bean id="owfDataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${owfdb.driver}" />
    <property name="url" value="${owfdb.url}" />
    <property name="username" value="${owfdb.username}" />
    <property name="password" value="${owfdb.password}" />
    <property name="initialSize" value="20" />
    <property name="maxActive" value="50" />
    <property name="maxIdle" value="20" />
    <property name="minIdle" value="10" />
</bean>

<!-- 配置SessionFactory -->
<bean id="sessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="owfDataSource" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
            <!-- <prop key="hibernate.dialect">org.hibernate.dialect.SQLServer2008Dialect</prop> -->
            <!-- 服务启动通过实体创建数据库表信息 -->
            <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.jdbc.batch_size">20</prop>
            <prop key="hibernate.connection.release_mode">auto</prop>
            <prop key="hibernate.autoReconnect">false</prop>
            <prop key="hibernate.connection.autocommit">true</prop>
            <prop key="hibernate.temp.use_jdbc_metadata_defaults">false</prop>
            <prop key="hibernate.jdbc.use_streams_for_binary">true</prop>
            <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext
            </prop>
            <!--解决weblogic无法使用hql的问题 -->
            <prop key="hibernate.cglib.use_reflection_optimizer">true</prop>
        </props>
    </property>

    <!-- 自动扫描备注解的实体 -->
    <property name="packagesToScan">
        <list>
            <value>org.openwebflow.mgr.hibernate.entity</value>
        </list>
    </property>
</bean>

<!-- 配置一个事务管理器 -->
<bean id="transactionManager"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

采用HibernateORM,openwebflow-mgr-hibernate的代码结构如下:

image

其中,DAO类、实体类、服务类分别存放在dao、entity、service包下面。service类的事务声明以及entity的映射皆采取注解方式。

完整的例子参见:https://github.com/bluejoe2008/openwebflow/blob/master/openwebflow-test/src/test/resources/activiti.cfg.sql.hibernate.xml

2.2.5 activiti.cfg.sql.mybatis.xml配置

activiti.sql.mybatis.xml提供基于SQL的manager实现,采用的ORM框架为mybatis 3。

各manager的定义如下:

<!-- 代理记录管理 -->
<bean id="myDelegationManager"
    class="org.openwebflow.mgr.mybatis.service.SqlDelegationManager" />
<!-- 自定义成员关系管理 -->
<bean id="myMembershipManager"
    class="org.openwebflow.mgr.mybatis.service.SqlMembershipManager" />
<!-- 自定义用户表 -->
<bean id="myUserDetailsManager"
    class="org.openwebflow.mgr.mybatis.service.SqlUserDetailsManager" />
<!-- 自定义的活动权限表管理 -->
<bean id="myActivityPermissionManager"
    class="org.openwebflow.mgr.mybatis.service.SqlActivityPermissionManager" />
<!-- 自定义的动态自定义活动管理 -->
<bean id="myActivityDefinitionManager"
    class="org.openwebflow.mgr.mybatis.service.SqlRuntimeActivityDefinitionManager" />
<bean id="myTaskNotificationManager"
    class="org.openwebflow.mgr.mybatis.service.SqlTaskNotificationManager" />

此外,还需要定义数据源、SqlSessionFactory,以及事务。

<!-- 数据库脚本见openwebflow.sql -->
<bean id="owfDataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${owfdb.driver}" />
    <property name="url" value="${owfdb.url}" />
    <property name="username" value="${owfdb.username}" />
    <property name="password" value="${owfdb.password}" />
    <property name="initialSize" value="20" />
    <property name="maxActive" value="50" />
    <property name="maxIdle" value="20" />
    <property name="minIdle" value="10" />
</bean>

<!-- 创建SqlSessionFactory,同时指定数据源-->
<bean id="owlSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="owfDataSource" />
</bean>

<!-- 配置一个事务管理器 -->
<bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="owfDataSource" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

采用Mybatis ORM,openwebflow-mgr-mybatis的代码结构如下:

其中,Mapper接口、实体类、服务类分别存放在mapper、entity、service包下面。service类的事务声明以及Mapper的映射皆采取注解方式。

完整的例子参见:https://github.com/bluejoe2008/openwebflow/blob/master/openwebflow-test/src/test/resources/activiti.cfg.sql.mybatis.xml

2.3 数据库设计

首先,Activiti引擎本身需要用到一系列的数据表,设置好数据源后,Activiti会自动生成这些表。

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

  • ACT_RE_*: 'RE'表示repository。这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。
  • ACT_RU_*: 'RU'表示runtime。这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据, 在流程结束时就会删除这些记录。这样运行时表可以一直很小速度很快。
  • ACT_ID_*: 'ID'表示identity。这些表包含身份信息,比如用户,组等等。
  • ACT_HI_*: 'HI'表示history。这些表包含历史数据,比如历史流程实例,变量,任务等等。
  • ACT_GE_*: 通用数据, 用于不同场景下。

OpenWebFlow为一系列manager提供了基于数据库的实现,需要用到一些数据表,对应的建库脚本参见https://github.com/bluejoe2008/openwebflow/tree/master/doc目录下的:

  • openwebflow-mysql4.sql:MySQL4脚本
  • openwebflow-mysql5.sql:MySQL5脚本
  • openwebflow-sqlserver2008.sql:SQLServer2008脚本
  • openwebflow-oracle10g.sql:Oracle脚本

其中共定义了6张表格:

image
  • OWF_ACTIVITY_CREATION:用以存储自定义的活动定义信息
  • OWF_ACTIVITY_PERMISSION:用以存储自定义的活动权限信息
  • OWF_DELEGATION:用以存储用户代理信息
  • OWF_NOTIFICATION:用以存储催办通知记录
  • OWF_MEMBERSHIP:用以存储用户组成员关系
  • OWF_USER:用以存储用户信息

注意:OWF_MEMBERSHIP和OWF_USER仅为测试使用,建议用户使用自己的数据表(OpenWebFlow本身努力的一个方向就是将用户及成员关系管理与工作流引擎剥离开),并包装自己的Manager。

2.4 使用ApplicationContext定义的bean

采用Spring IoC框架加载完XML配置文件之后,ApplicationContext中会包含如下变量,可供客户程序使用:

  • processEngine:工作流引擎对象,标准的Activiti对象
  • processEngineTool:针对processEngine提供了一些工具方法
  • defaultTaskFlowControlServiceFactory:任务流控制器的工厂对象
  • repositoryService:提供了管理和控制发布包和流程定义的操作
  • RuntimeService:负责启动一个流程定义的新实例
  • TaskService:与任务相关相关的操作
  • IdentityService:管理(创建,更新,删除,查询...)群组和用户
  • FormService:提供了启动表单和任务表单两个概念
  • HistoryService:提供了Activiti引擎手机的所有历史数据
  • ManagementService:可以查询数据库的表和表的元数据
image

如下是使用OpenWebFlow的示例代码,可以看出来与Activiti的用法完全一致:


ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:activiti.cfg.mem.xml");  
ProcessEngineTool tool = ctx.getBean(ProcessEngineTool.class);  
ProcessEngine processEngine = tool.getProcessEngine();  
// 启动流程实例  
ProcessInstance instance = processEngine.getRuntimeService().startProcessInstanceByKey("test1");  
TaskService taskService = processEngine.getTaskService();  
//会自动跳转到第一个task  
//management可以访问该task  
Assert.assertEquals(1, taskService.createTaskQuery().taskCandidateGroup("management").count());  

2.5 运行测试用例

在openwebflow-test项目源代码中,用户可以找到一组测试用例来对工作流引擎的功能进行测试,它们分别是:

  • MemProcessEngineTest:基于内存的manager测试
  • SqlHibernateProcessEngineTest:基于hibernateORM的manager测试
  • SqlMybatisProcessEngineTest:基于mybatisORM的manager测试

以上3个测试类都继承于AbstractProcessEngineTest:

AbstractProcessEngineTest提供了测试方法:

方法 用途
void testActivityPermission() 测试流程动态授权
void testAlarm() 测试催办功能
void testCachedDefinitions() 测试TaskDefinition
void testDelegation() 测试代理功能
void testInsertTasksAfter() 测试后加签
void testInsertTasksBefore() 测试前加签
void testInsertTasksWithPersistence() 测试加签功能的持久化
void testModelDeployment() 测试流程模型部署
void testMove() 测试自由跳转
void testMultiInstancesLoop() 测试多实例节点
void testSplit() 测试测试节点分裂

其中为了配合测试设计了一个复杂的流程(models/test2.bpmn)如图所示:

选择指定的测试单元(如:MemProcessEngineTest),以“JUnit测试”的方式运行(Run As…),即可观察到测试结果:

也可以选择运行测试套件AllTests:

3. 熟悉OpenWebFlow代码

3.1 下载源码

用户可以下载OpenWebFlow的zip包,下载地址为:https://github.com/bluejoe2008/openwebflow/archive/master.zip

也可以通过git方式获取到最新源码,git资源库地址为:https://github.com/bluejoe2008/openwebflow.git

3.2 代码结构

OpenWebFlow源码包含5个maven工程,其中openwebflow工程是父工程,它声明了包含openwebflow-core、openwebflow-mgr-hibernate、openwebflow-mgr-mybatis、openwebflow-test等4个model。

  • openwebflow-core:核心工程,包含OpenWebFlow扩展引擎的所有核心内容、以及基于内存的manager实现。
  • openwebflow-mgr-hibernate:依赖于openwebflow-core,提供了基于数据库的manager实现,ORM框架采用Hibernate。
  • openwebflow-mgr-mybatis:依赖于openwebflow-core,提供了基于数据库的manager实现,ORM框架采用MyBatis。
  • openwebflow-test:依赖于以上项目,提供了测试用例,包括配置文件、测试类等。

3.3 build项目

获取到的项目源码可以采用Maven完成build和install,如下为build截图:

3.4 核心对象

3.4.1 ProcessEngineConfigurationEx

ProcessEngineConfigurationEx是针对Activiti提供的ProcessEngineConfiguration类的派生类:

二者大部分参数完全一致,唯一不同的是,ProcessEngineConfigurationEx提供了一个属性:startEngineEventListeners。

startEngineEventListeners用以定义工作流引擎启动的时候需要同时启动的其它任务,startEngineEventListeners是个List,因此可以随意增加新的任务,默认的core.xml中会加载如下任务:LoadDummyFormTypes、ReplaceMembershipManager、ReplaceTaskAssignmentHandler、ImportDefinedProcessModels、StartTaskAlarmService、LoadRuntimeActivityDefinitions。各任务及类属性列表如下:

任务类 用途 属性名 属性含义
LoadDummyFormTypes 加载一些无用的Form类型,用以屏蔽一些自定义Form带来的错误 typeNames 需要屏蔽的Form类型名,以;分隔,如:user
ReplaceMembershipManager 直接接管用户组成员关系 customMembershipManager 指定客户程序自定义的管理器
ReplaceTaskAssignmentHandler 接管Activiti的用户权限管理,如果你想实现动态的节点权限分配,那必须要打开它。 handlers 定义一个授权处理器列表,值类型为List,运行时刻各授权处理器列表会组成一个链,越靠后优先级越高(越靠外)
ImportDefinedProcessModels 自动从指定目录导入BPMN模型 modelDir 用以指定模型的路径,可以是classpath:等路径
StartTaskAlarmService 启动任务催办服务 taskAlarmService 设置催办服务对象 runOnStartup 是否一开始就启动(默认为true)
LoadRuntimeActivityDefinitions 加载运行时的节点定义,主要用来支持在运行时刻定义新的节点 activityDefinitionManager 指定节点定义管理器

3.4.2 ProcessEngineTool

返回值 方法摘要
org.activiti.engine.repository.Model createNewModel(java.lang.String name, java.lang.String description)``创建一个空白的Model对象
org.activiti.engine.repository.Deployment deployModel``(java.lang.String modelId)``部署一个已注册的model
org.activiti.engine.impl.pvm.process.ActivityImpl getActivity``(java.lang.String processDefId, java.lang.String activityId)``获取指定名字的活动
java.util.Map<java.lang.String,java.lang.Object> getHistoricProcessVariables``(java.lang.String processId)``获取指定历史流程的变量列表
org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity getProcessDefinition``(java.lang.String processDefId)``获取指定ID的流程定义
org.activiti.engine.ProcessEngine getProcessEngine``()
void grantPermission``(org.activiti.engine.impl.pvm.process.ActivityImpl activity, java.lang.String assigneeExpression, java.lang.String candidateGroupIdExpressions, java.lang.String candidateUserIdExpressions)``设置指定活动的用户权限,包括钦定用户、候选用户、候选组
void grantPermission``(java.lang.String processDefId, java.lang.String activityId, java.lang.String assigneeExpression, java.lang.String candidateGroupIdExpressions, java.lang.String candidateUserIdExpressions)``设置指定活动的用户权限,包括钦定用户、候选用户、候选组
void setProcessEngine``(org.activiti.engine.ProcessEngine processEngine)

ProcessEngineTool提供了一些工具方法,这些方法的功能一般很难通过ProcessEngine直接拿到:

返回值 方法摘要
org.activiti.engine.repository.Model createNewModel(java.lang.String name, java.lang.String description)``创建一个空白的Model对象
org.activiti.engine.repository.Deployment deployModel``(java.lang.String modelId)``部署一个已注册的model
org.activiti.engine.impl.pvm.process.ActivityImpl getActivity``(java.lang.String processDefId, java.lang.String activityId)``获取指定名字的活动
java.util.Map<java.lang.String,java.lang.Object> getHistoricProcessVariables``(java.lang.String processId)``获取指定历史流程的变量列表
org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity getProcessDefinition``(java.lang.String processDefId)``获取指定ID的流程定义
org.activiti.engine.ProcessEngine getProcessEngine``()
void grantPermission``(org.activiti.engine.impl.pvm.process.ActivityImpl activity, java.lang.String assigneeExpression, java.lang.String candidateGroupIdExpressions, java.lang.String candidateUserIdExpressions)``设置指定活动的用户权限,包括钦定用户、候选用户、候选组
void grantPermission``(java.lang.String processDefId, java.lang.String activityId, java.lang.String assigneeExpression, java.lang.String candidateGroupIdExpressions, java.lang.String candidateUserIdExpressions)``设置指定活动的用户权限,包括钦定用户、候选用户、候选组
void setProcessEngine``(org.activiti.engine.ProcessEngine processEngine)

3.4.3 各种Utils

OpenWebFlow提供了一些常见的工具类,如下所示:

类名 功能
CloneUtils 实现对象的克隆功能
ExpressionUtils 实现常见类型的expression的包装和转换
IdentityUtils 实现用户、成员关系等相关操作
ModelUtils 包装了对BPMN模型的部署、注册等功能
ProcessDefinitionUtils 流程定义相关操作的封装

3.4.4 TaskFlowControlService

TaskFlowControlService用以实现流程的自由控制,它提供的方法如下:

返回值 方法摘要
org.activiti.engine.impl.pvm.process.ActivityImpl[] insertTasksAfter``(java.lang.String targetTaskDefinitionKey, java.lang.String... assignees)``后加签
org.activiti.engine.impl.pvm.process.ActivityImpl[] insertTasksBefore``(java.lang.String targetTaskDefinitionKey, java.lang.String... assignees)``前加签
void moveBack``()``后退一步
void moveBack``(org.activiti.engine.impl.persistence.entity.TaskEntity currentTaskEntity)``后退至指定活动
void moveForward``()``前进一步
void moveForward``(org.activiti.engine.impl.persistence.entity.TaskEntity currentTaskEntity)``前进至指定活动
void moveTo``(java.lang.String targetTaskDefinitionKey)``跳转(包括回退和向前)至指定活动节点
void moveTo``(java.lang.String currentTaskId, java.lang.String targetTaskDefinitionKey)``跳转(包括回退和向前)至指定活动节点
void moveTo``(org.activiti.engine.impl.persistence.entity.TaskEntity currentTaskEntity, java.lang.String targetTaskDefinitionKey)``跳转(包括回退和向前)至指定活动节点
org.activiti.engine.impl.pvm.process.ActivityImpl split``(java.lang.String targetTaskDefinitionKey, boolean isSequential, java.lang.String... assignees)``分裂某节点为多实例节点
org.activiti.engine.impl.pvm.process.ActivityImpl split``(java.lang.String targetTaskDefinitionKey, java.lang.String... assignee)``分裂某节点为多实例节点

TaskFlowControlService需要一个TaskFlowControlServiceFactory来创建,可以从applicationcontext中获取到该工厂对象。

4. 核心功能的设计与使用【略】

5. 使用管理器接口实现自定义扩展

OpenWebFlow需要用户提供6类管理器的接口,它们分别是:

  • RuntimeActivityDefinitionManager:负责获取活动的定义信息,用以支持运行时期的新建活动
  • ActivityPermissionManager:负责获取活动的权限设置信息
  • TaskNotificationManager:负责存取任务催办通知信息
  • DelegationManager:负责获取用户的代理信息
  • UserDetailsManager:负责获取用户的信息(包括E-mail、昵称、手机号等),主要用以发送催办通知
  • IdentityMembershipManager:负责获取用户组成员关系,获取某用户的候选任务队列时,需要通过用户名获取到用户组

除了这些Manager之外,用户会发现OpenWebFlow还提供了一系列的ManagerEx接口:

  • ActivityPermissionManagerEx:负责保存活动的权限设置信息
  • TaskNotificationManagerEx:负责保存任务催办通知信息
  • DelegationManagerEx:负责保存用户的代理信息
  • UserDetailsManagerEx:负责保存用户的信息
  • IdentityMembershipManagerEx:负责保存用户组成员关系

可以简单的认为,Manager接口主要用以信息读取(read),ManagerEx接口主要用以信息写入(write),注意使用OpenWebFlow引擎时,ManagerEx不是必须要提供相应实现的!OpenWebFlow引擎的所有操作只会调用到Manager而非ManagerEx,提供ManagerEx的唯一用处是为了测试(如果没有写入,读取返回的永远是空白,测试就无法正常进行了)。

5.1 活动定义管理

客户程序往往需要在运行时候调整某个工作流的流程,如:让活动step5执行完之后跳转至step2,这样的操作需要创建一个新的路径,为了保证后续流程的正常执行(特别是应用重启之后),这样的路径需要保存和加载。

5.1.1 RuntimeActivityDefinitionManager

RuntimeActivityDefinitionManager包含如下方法:

返回值 方法摘要
java.util.List <RuntimeActivityDefinitionEntity> list()获取所有的活动定义信息,引擎会在启动的时候加载这些活动定义并进行注册
void removeAll()删除所有活动定义
void save(RuntimeActivityDefinitionEntity entity) 新增一条活动定义的信息

5.1.2 RuntimeActivityDefinitionEntity

RuntimeActivityDefinitionEntity对应于一条活动的定义信息:

返回值 方法摘要
void deserializeProperties()反序列化PropertiesText到Map
java.lang.String getFactoryName()获取工厂名
java.lang.String getProcessDefinitionId()获取流程定义的ID
java.lang.String getProcessInstanceId()获取流程实例的ID
java.lang.String getPropertiesText()获取PropertiesText,它是一个JSON字符串
<T> T getProperty(java.lang.String name)获取指定的属性值
void serializeProperties()序列化Map至PropertiesText
void setFactoryName(java.lang.String factoryName)设置工厂名
void setProcessDefinitionId(java.lang.String processDefinitionId)设置流程定义ID
void setProcessInstanceId(java.lang.String processInstanceId)设置流程实例ID
void setPropertiesText(java.lang.String propertiesText)设置PropertiesText
<T> void setProperty(java.lang.String name, T value)

5.2 活动权限管理

5.2.1 ActivityPermissionManager

活动权限的管理接口为ActivityPermissionManager,它的定义如下:

返回值 方法摘要
ActivityPermissionEntity load(java.lang.String processDefinitionId, java.lang.String taskDefinitionKey, boolean addOrRemove)获取指定活动的权限定义信息

5.2.2 ActivityPermissionEntity

非常简单,它只需要一个方法,该方法返回一个ActivityPermissionEntity,该实体的定义如下:

返回值 方法摘要
java.lang.String getAssignee()获取直接授权人
java.lang.String[] getGrantedGroupIds()获取候选组列表
java.lang.String[] getGrantedUserIds()获取候选用户列表

5.2.3 ActivityPermissionManagerEx

ActivityPermissionManagerEx实现对活动权限表的“写”操作:

返回值 方法摘要
void removeAll()删除所有权限定义信息
void save(java.lang.String processDefId, java.lang.String taskDefinitionKey, java.lang.String assignee, java.lang.String[] candidateGroupIds, java.lang.String[] candidateUserIds)`保存一条权限定义信息

5.3 任务催办通知管理

5.3.1 TaskNotificationManager

TaskNotificationManager负责读取和设置任务催办的状态,该接口也很简单:

返回值 方法摘要
boolean isNotified(java.lang.String taskId) 判断指定任务是否通知过
void setNotified(java.lang.String taskId) 设置指定任务的通知状态

可以理解成TaskNotificationManager维护了一个队列,记录了每个任务的通知状态(已通知为true,未通知为false)。

5.3.2 TaskNotificationManagerEx

TaskNotificationManagerEx实现了对通知记录的“写”操作:

返回值 方法摘要
void removeAll() 删除所有通知记录

5.4 用户代理关系管理

5.4.1 DelegationManager

DelegationManager用以维护用户之间的代理关系。接口包含2个方法:

返回值 方法摘要
java.lang.String[] getDelegates(java.lang.String delegated)获取指定用户的代理人列表
java.util.List<DelegationEntity> listDelegationEntities() 获取所有的代理信息列表,引擎会在启动的时候加载

5.4.2 DelegationEntity

DelegationEntity``描述一条代理关系:

返回值 方法摘要
java.lang.String getDelegate() 获取当前代理记录的代理人
java.lang.String getDelegated()获取当前代理记录的被代理人

5.4.3 DelegationManagerEx

DelegationManagerEx用以实现对代理记录的“写”操作:

| 返回值|方法摘要 |
| void |removeAll()删除所有代理信息 |
| void |saveDelegation(java.lang.String delegated, java.lang.String delegate) 保存一条代理信息 |

5.5 用户详细信息管理

5.5.1 UserDetailsManager

UserDetailsManager负责获取用户的信息(包括E-mail、昵称、手机号等),主要用以发送催办通知。UserDetailsManager接口包含的方法如下:

返回值 方法摘要
UserDetailsEntity findUserDetails(java.lang.String userId) 根据用户名获取用户详细信息

5.5.2 UserDetailsEntity

UserDetailsEntity用以描述用户详细信息,注意它没有强制要求提供诸如getName()这样的方法,而是提供了一个getProperty(Stringname)方法:

返回值 方法摘要
<T> T getProperty(java.lang.String name)获取指定属性的值
java.lang.String[] getPropertyNames()获取所有的属性名
java.lang.String getUserId()获取用户的ID
<T> void setProperty(java.lang.String name, T value)设置指定属性的值

UserDetailsEntity同时提供了几个字符串常量:

返回值 字段摘要
static java.lang.String STRING_PROPERTY_EMAIL EMAIL属性名
static java.lang.String STRING_PROPERTY_MOBILE_PHONE_NUMBER 手机号码属性名
static java.lang.String STRING_PROPERTY_NICK_NAME 昵称属性名
static java.lang.String STRING_PROPERTY_USER_ID 用户ID属性名

5.5.3 UserDetailsManagerEx

UserDetailsManagerEx用以实现用户信息的“写”操作:

返回值 方法摘要
void removeAll() 删除所有用户信息
void saveUserDetails(UserDetailsEntity userDetails)保存某个用户的信息

5.6 用户组成员关系管理

5.6.1 IdentityMembershipManager

IdentityMembershipManager负责获取用户组成员关系,简单点,就是获取指定用户所在的组的ID列表,以及指定组内的成员ID列表。

返回值 方法摘要
java.util.List <java.lang.String> findGroupIdsByUser(java.lang.String userId)获取指定的用户所在的组ID列表
java.util.List <java.lang.String> findUserIdsByGroup(java.lang.String groupId)获取指定组的成员用户ID列表

5.6.2 IdentityMembershipManagerEx

IdentityMembershipManagerEx提供了对成员关系信息的“写”操作:

返回值 方法摘要
void removeAll() 删除所有成员关系
void saveMembership(java.lang.String userId, java.lang.String groupId) 保存成员关系

6. 其他帮助

本文档的下载地址为:https://github.com/bluejoe2008/openwebflow/tree/master/doc,可通过该地址及时查阅最新版本。

如果用户需要查阅OpenWebFlow的Java API,可以参考javadoc(https://bluejoe2008.github.io/openwebflow/openwebflow-core/doc/javadoc)。

另外可以关注Wiki(https://github.com/bluejoe2008/openwebflow/wiki),提交话题(https://github.com/bluejoe2008/openwebflow/issues),以及与作者bluejoe2008@gmail.com直接联系。

7. Activiti的BUG及对策

目前(2014年)作者发现Activiti框架存在2个bug,主要表现在:

第一个bug是在BaseBpmnJsonConverter将BPMN模型转存为JSON格式的时候,会忽略对true布尔值的输出,这个bug会造成JsonConverterUtil.getPropertyValueAsBoolean()获取到false值(因为此时的判断标准变成Yes或No)。该bug的报告地址:https://github.com/Activiti/Activiti/pull/464#event-204722250

另外一个bug是在BPMN文件加载的时候,当本地字符集为非UTF-8(如:GB2312)时,会出现模型加载的错误。该bug的报告地址:https://github.com/Activiti/Activiti/pull/486#event-220121880

目前作者已经针对以上两个bug提交了bugfix并被master版本合并。不过考虑到版本稳定性问题,OpenWebFlow最新版本还是采用了其他方法来避免了如上bug的发生。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容