Bulbasaur
💡 A pluggable, scalable process engine. You can use it to develop business-process, approval-process, retry-process and so on. Hope you enjoy it! 💖 可插拔的精简流程引擎,可快速实现流程、审批、业务失败重试等场景。
Install / Use
/learn @alibaba/BulbasaurREADME
轻量级流程引擎
Bulbasaur 读音 ['bʌlba:sɑu]
Bulbasaur QuickStart
简介
bulbasaur分为四个模块,按需加载使用。分别为:
模块 | 功能
----------------------- | --------------------------
核心模块 bulbasaur-core | 提供核心流程
持久化模块 bulbasaur-persist | 提供流程的存储和失败回滚
调度模块 bulbasaur-schedule | 提供失败重试,定时等调度逻辑
任务模块 bulbasaur-task | 提供人工任务和超时自动执行,目前已经支持单人单任务,多人单任务
概述
- 如果你只是希望使用基于
内存的流程引擎,那么只要使用核心模块即可,流程模板以文件形式维护在业务方系统。 - 如果希望流程引擎有持久化的流程实例和节点,那么要使用
核心模块+持久化模块。 - 如果希望流程引擎有节点失败重试,定时等功能,那么要使用
核心模块+持久化模块+调度模块。 - 如果希望任务审批,多人审批的能力,那么要使用
核心模块+持久化模块+任务模块,至于要不要调度模块都可以。
其中,除了核心模块外,其他模块都需要业务方建表支持。
持久化模块 bulbasaur-persist 需要表:
表名 | 功能
--------------- | ------
xx_bulbasaur_d | 模板
xx_bulbasaur_p | 流程实例
xx_bulbasaur_s | 节点
调度模块bulbasaur-schedule需要表:
表名 | 功能
--------------- | ------
xx_bulbasaur_j | 重试/定时 任务
任务模块bulbasaur-task需要表:
表名 | 功能
--------------- | ------
xx_bulbasaur_t | 审批任务
xx_bulbasaur_ptp | 多人审批任务
以上表需要建在业务方库中,表名可以业务方指定,流程引擎可以识别,比如:业务方库中表都有统一前缀,那么流程引擎表可以都带上统一前缀。
流程引擎使用quartz做分布式调度,也需要在业务方库中建表。
注意: 建表语句文末提供
在项目中引入bulbasaur配置,最全配置如下(业务方根据引人模块酌情配置)
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
default-autowire="byName">
<context:annotation-config/>
<!--提供核心流程-->
<bean id="coreModule" class="CoreModule">
<!--用于区分不同环境-->
<property name="ownSign" value="业务方配置执行,可以区分不同环境隔离"/>
</bean>
<!--提供流程的存储和失败回滚-->
<bean id="persistModule" class="PersistModule">
<property name="dataSource" ref="dataSource"/>
<!-- 默认为true走DB,不走DB 加上下面属性,并置为false -->
<!--<property name="usePersistParser" value="false"/>-->
<!--业务方可自定义表名,在此配置-->
<property name="tableNameP" value="xxxx_bulbasaur_p"/>
<property name="tableNameS" value="xxxx_bulbasaur_s"/>
<property name="tableNameD" value="xxxx_bulbasaur_d"/>
<property name="tableNameJ" value="xxxx_bulbasaur_j"/>
<property name="tableNameT" value="xxxx_bulbasaur_t"/>
<property name="tableNamePtp" value="xxxx_bulbasaur_ptp"/>
</bean>
<!--提供人工任务和超时自动执行,提供失败重试,定时等调度逻辑 -->
<bean id="scheduleModule" class="ScheduleModule">
<!-- 默认不删除超过最大重试次数的job ,不设置则为false,这里为 true(删除)-->
<property name="deleteOverdueJob" value="true"/>
<!-- 指定quartz的表名前缀,如果不指定则默认为 QRTZ_ -->
<property name="quartztablePrefix" value="xxxx"/>
</bean>
<!--提供人工审批-->
<bean id="taskModule" class="TaskModule"/>
<!-- 引擎主bean,所有模块都用的时候 -->
<bean id="bulbasaur" class="Bulbasaur">
<property name="requireModule">
<list>
<ref bean="coreModule"/>
<ref bean="persistModule"/>
<ref bean="scheduleModule"/>
<ref bean="taskModule"/>
</list>
</property>
</bean>
<!--业务方单库数据源-->
<!-- 可以是tddl -->
<bean id="dataSource" class="com.taobao.tddl.jdbc.group.TGroupDataSource"
init-method="init">
<property name="appName" value="xxx" />
<property name="dbGroupKey" value="xxx" />
</bean>
<!-- 也可以其他直连池 -->
<!--<bean id="dataSource"-->
<!--class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">-->
<!--<property name="targetDataSource">-->
<!--<ref local="dataSourceImpl"/>-->
<!--</property>-->
<!--</bean>-->
<!--<bean id="dataSourceImpl" class="com.mchange.v2.c3p0.ComboPooledDataSource"-->
<!--abstract="false" scope="singleton" lazy-init="default" autowire="default"-->
<!--dependency-check="default" destroy-method="close">-->
<!--<property name="driverClass" value="com.mysql.jdbc.Driver"/>-->
<!--<property name="jdbcUrl" value="jdbc:mysql://xxx.xxx.xxx.xxx:3306/xxx?characterEncoding=utf8"/>-->
<!--<property name="user" value="xxx"/>-->
<!--<property name="password" value="xxx"/>-->
<!--<property name="minPoolSize" value="10"/>-->
<!--<property name="maxIdleTime" value="1800"/>-->
<!--<property name="initialPoolSize" value="20"/>-->
<!--</bean>-->
</beans>
代码中使用
try {
//基于内存的流程
//Machine m = machineFactory.newInstance("业务唯一id","流程名称xxx");
//持久化流程
//PersistMachine m = persistMachineFactory.newInstance("业务唯一id","流程名称xxx");
//可调度流程
ScheduleMachine m = scheduleMachineFactory.newInstance("业务唯一id", "流程名称xxx");
m.addContext("goto", 2);// 可放入流程中的变量,上文中可用
m.addContext("_i", 3);// 会被持久化的流程变量,因为带了 "_" 开头
m.run();
} catch (Exception e) {
System.out.println(e);
}
功能节点介绍
节点 | 功能
--------- | ------
start | 特殊开始节点
state | 直接执行invokes内容,并根据paths中配置执行下一个节点,如果配置了多个path,则可以根据表达式判断走哪条
event | 先执行pre-invokes,并暂停。需要外部再次触发,继续执行invokes 。其中 pre-invokes 和invokes 按需不配置
task | 审批流节点,可以在节点上直接配置审批人列表,或者配置服务从业务方获取审批人列表
流程模板示例(本例子比较全,业务方可以按需使用)
<process name="process">
<start name="i'm start">
<paths>
<path to="state1"/>
</paths>
</start>
<state name="state1">
<paths>
<path to="state2" expr="goto==2"/>
<path to="state3" expr="goto==3"/>
</paths>
</state>
<event name="state2">
<pre-invokes>
<script return="info">
"some info"
</script>
</pre-invokes>
<invokes>
<script return="a" pojos="test.TestBean -> test, test.TestBean2 -> test2">
test2.testMethod(test.testMethod(i))
</script>
</invokes>
<paths>
<path to="state3" expr="goto==2"/>
<path to="state4" expr="goto==3"/>
</paths>
</event>
<state name="state3" repeatList="1m 2m 3m 2m 1m">
<!--重复时间的列表,支持 m分钟,h小时,d天-->
<invokes>
<script return="_a" pojos="test.TestBean2 -> test2" expr="test2.judge()">
test2.testMethod(2)
</script>
</invokes>
<paths>
<path to="state4"/>
</paths>
</state>
<task name="state4" candidate-users="00001:测试人员">
<!--candidate-users 可配置列表,: , 分割,例如 00001:测试人员,00002:初审人员-->
<pre-invokes>
<script return="_a" beans="notifyPage" async="true">
<!--业务方接受处理上下文传递出来的task列表,可以做通知-->
notifyPage.doNotify(taskIds)
</script>
</pre-invokes>
<invokes>
<script return="_a" pojos="test.TestBean2 -> test2" expr="test2.judge()">
test2.testState4(6)
</script>
</invokes>
<!--由业务bean,提供审批人列表,格式同candidate-users-->
<assignment-handler>
<script return="_a" pojos="test.TestBean2 -> test2" expr="test2.judge()">
test2.getUsers(2)
</script>
</assignment-handler>
<!--配置超时节点,必须配置超时时间,这里不给xml的配置方式,只由业务方提供接口,并返回特定map -->
<!--<timeout-handler>-->
<!--<script return="_a" pojos="test.TestBean2 -> test2" expr="test2.judge()">-->
<!--test2.getUsers(2)-->
<!--例如:-->
<!--map.put("ignoreWeekend", "true");//是否忽略周末-->
<!--map.put("outGoing", "state3");//超时去向-->
<!--map.put("period", "2minute");//超时时间-->
<!--</script>-->
<!--</timeout-handler>-->
<paths>
<path to="end"/>
</paths>
</task>
<state name="state5">
<paths>
<path to="end"/>
</paths>
</state>
<state name="end"/>
</process>
相关重点说明
关于调度依赖分布式quartz
流程引擎使用了quartz,通过数据库完成分布式调度。配置:quartz.xml
系统内存在的调度处理逻辑:
bulbasaurJobProcessor
任务读取的频率为每30s一次,那么流程引擎中 "失败重试","定时","超时"等逻辑,最小时间单位就是30s
使用人员可以根据业务情况自行修改减小频率bulbasaurCleanerTrigger
清理已经完成的流程和其所关联的节点以及job,如果不需要,可以直接在配置中删除触发bulbasaurJobCleanerTrigger
单独清理已经完成的job数据,如果不需要,可以直接在配置中删除触发
关于重试
repeatList="1m 2m 3m 2m 1m",支持分(m),小时(h)和天(d),该示例意思为:重试5次,每次间隔分别为1分钟,2分钟,3分钟...
task节点
pre-invokes该配置,业务方可以配置bean,接收参数为string,逗号分割的taskId,业务可以处理该task,给用户发旺旺消息,邮件等审核链接。candidate-users可配置列表,: ,分割,例如00001:测试人员,00002:初审人员。assignment-handler配置获得处理人,业务bean返回的格式同。candidate-users,这里bulbasaur只关注id和name,业务系统自行对接hecla或uic或subaccount。其中assignment-handler获取的内容优先级高于candidate-users。- 如果
candidate-users没有配置,并且assignment-handler返回null,则任务该任务节点不需要审核,直接往下走。 timeout-handler配置超时的策略,业务bean返回给bulbasaur一个Map<String,Object>,key分别为:period[超时时间],outGoing[超时之后去到的节点名],ignoreWeekend[是否忽略周末]。
任务处理接口
TaskAccessor.hasTaskTakenPermission
判断用户是否有权限申领指定的任务TaskAccessor.takenTask
申领一个任务TaskAccessor.releaseTask
释放一个任务,任务状态转为createdTaskAccessor.hasTaskDealPermission
判断用户是否有权限处理给定的任务TaskAccessor.completeTask(java.lang.Long, java.lang.String, User, java.lang.String)
完成一个任务,内部再次runTaskAccessor.assignTaskWithResult
当前所有人将任务转给指定用户TaskAccessor.queryTasksByUser
业务方可以使用该接口,获取审批人的任务列表,支持单人单任务,多人单任务TaskAccessor.queryTasks
查询任务TaskAccessor.queryTaskByBizId
查询任务TaskAccessor.update(java.lang.Long, java.lang.String, java.lang.String)
更新处理结果和意见TaskAccessor.update(java.lang.Long, java.lang.Object)
按taskId,全量覆盖bizInfo
建表DDL
模板表
CREATE TABLE `xxxx_bulbasaur_d` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`definition_name` varchar(64) NOT NULL COMMENT 'definition name',
`definition_version` int(11) NOT NULL DEFAULT '1' COMMENT 'definition version',
`own_sign` varchar(64) NOT NULL COMMENT 'sign to separate between different runtime or app',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'status',
`content` text COMMENT 'definition content',
`gmt_create` datetime NOT NULL COMMENT 'gmt_create',
`gmt_modified` datetime DEFAULT NULL COMMENT 'gmt_modified',
`definition_alias` varchar(64) DEFAULT NULL COMMENT '别名,主要维护中文名称',
PRIMARY KEY (`id`),
KEY `idx_name_ownsign_version` (`definition_name`,`own_sign`,`definition_ver
