开源地址: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与业务应用系统之间的关系如下图所示。
相对于Activiti,OpenWebFlow扩展的功能包括:
完全接管了Activiti对活动(activity)权限的管理。
Activiti允许在设计model的时候指定每个活动的执行权限,但是,业务系统可能需要根据实际情况动态设置这些任务的执行权限(如:动态的Group)。OpenWebFlow完全实现了与流程定义时期的解耦,即用户对活动的访问控制信息单独管理(而不是在流程定义中预先写死),这样有利于动态调整权限,详见自定义活动权限管理;完全接管了Activiti对用户表(IDENTITY_XXX表)的管理。
在标准的工作流定义中,每个节点可以指定其候选人和候选用户组,但是比较惨的是,Activiti绑架了用户信息表的设计!这个是真正致命的,因为几乎每个业务系统都会属于自己的用户信息结构(包括User/Group/Membership),但不一定它存储在Activiti喜欢的那个库中,表的结构也不一定一样,有的时候,某些信息(如:动态的Group)压根儿就不采用表来存储。OpenWebFlow剥离了用户信息表的统一管理,客户程序可以忘掉Activiti的用户表、群组表、成员关系表,详见自定义用户成员关系管理;允许运行时定义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&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是增强型的工作流引擎配置对象,可以设置自定义用户群组成员关系管理策略、自定义活动权限管理策略等。
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 |
任务通知信息管理 |
与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的代码结构如下:
其中,DAO类、实体类、服务类分别存放在dao、entity、service包下面。service类的事务声明以及entity的映射皆采取注解方式。
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的映射皆采取注解方式。
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张表格:
- 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:可以查询数据库的表和表的元数据
如下是使用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的发生。