赞
踩
推荐网址
flowable是一个工作流,实现审批流程的功能;
变量:注意bpmn中变量使用${xxxx}来指定,在启动流程创建流程实例的时候指定变量的具体值,然后在后边还可以给变量覆盖改值
在申请人设置那里,可以使用变量和审核人中间表来实现已有流程中更改审核人的需求
设置审核人、组(使用候选组) flowable:assignee=“ a s s i g n e e 1 " 、 f l o w a b l e : c a n d i d a t e G r o u p s = " {assignee1} " 、 flowable:candidateGroups=" assignee1"、flowable:candidateGroups="{assignee2}”
候选人或者候选组 就是添加更多的人可以审核
在代码里面也可以使用变量的,表达如上
审核的时候使用户还是组都可以用变量代替
审核人的值可以不走flowable的用户体系,直接设置值,比如1-1(用户-用户id),2-4(组-部门id),此时都用 flowable:assignee="${assignee1} ",而不用组的审核设置
总入口点是ProcessEngine;使用ProcessEngine,可以获得各种提供工作流/BPM方法的服务。ProcessEngine与服务对象都是线程安全的,因此可以在服务器中保存并共用同一个引用。
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // spring的项目可以直接导入pom依赖,然后@Autowired导入使用
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
默认的其实就是默认application.yml文件下的数据库配置
也可以不适用默认 的getDefaultProcessEngine,可以用如下的配置放在启动类中
表单数据的管理;
是可选服务,也就是说Flowable没有它也能很好地运行,而不必牺牲任何功能。这个服务引入了开始表单(start form)与任务表单(task form)的概念。 开始表单是在流程实例启动前显示的表单,而任务表单是用户完成任务时显示的表单。Flowable可以在BPMN 2.0流程定义中定义这些表单。表单服务通过简单的方式暴露这些数据。再次重申,表单不一定要嵌入流程定义,因此这个服务是可选的
formService.getStartFormKey() // 获取表单key
formService.getRenderedStartForm() // 查询表单json(无数据)
提供了在编辑和发布审批流程的api。主要是模型管理和流程定义的业务api
这个服务提供了管理与控制部署(deployments)与流程定义(process definitions)的操作
1.提供了带条件的查询模型流程定义的api
repositoryService.createXXXQuery()
例如:
repositoryService.createModelQuery().list() 模型查询
repositoryService.createProcessDefinitionQuery().list() 流程定义查询
repositoryService.createXXXXQuery().XXXKey(XXX) (查询该key是否存在)
2.提供一大波模型与流程定义的通用方法
模型相关
repositoryService.getModel() (获取模型)
repositoryService.saveModel() (保存模型)
repositoryService.deleteModel() (删除模型)
repositoryService.createDeployment().deploy(); (部署模型)
repositoryService.getModelEditorSource() (获得模型JSON数据的UTF8字符串)
repositoryService.getModelEditorSourceExtra() (获取PNG格式图像)
3.流程定义相关
repositoryService.getProcessDefinition(ProcessDefinitionId); 获取流程定义具体信息
repositoryService.activateProcessDefinitionById() 激活流程定义
repositoryService.suspendProcessDefinitionById() 挂起流程定义
repositoryService.deleteDeployment() 删除流程定义
repositoryService.getProcessDiagram()获取流程定义图片流
repositoryService.getResourceAsStream()获取流程定义xml流
repositoryService.getBpmnModel(pde.getId()) 获取bpmn对象(当前进行到的那个节点的流程图使用)
4.流程定义授权相关
repositoryService.getIdentityLinksForProcessDefinition() 流程定义授权列表
repositoryService.addCandidateStarterGroup()新增组流程授权
repositoryService.addCandidateStarterUser()新增用户流程授权
repositoryService.deleteCandidateStarterGroup() 删除组流程授权
repositoryService.deleteCandidateStarterUser() 删除用户流程授权
处理正在运行的流程
runtimeService.createProcessInstanceBuilder().start() 发起流程
runtimeService.deleteProcessInstance() 删除正在运行的流程
runtimeService.suspendProcessInstanceById() 挂起流程定义
runtimeService.activateProcessInstanceById() 激活流程实例
runtimeService.getVariables(processInstanceId); 获取表单中填写的值
runtimeService.getActiveActivityIds(processInstanceId)获取以进行的流程图节点 (当前进行到的那个节点的流程图使用)
runtimeService.createChangeActivityStateBuilder().moveExecutionsToSingleActivityId(executionIds, endId).changeState(); 终止流程
在用户发起审批后,会生成流程实例。historyService为处理流程实例的api,但是其中包括了已完成的和未完成的流程实例;
如果是处理正在运行的流程实例,请使用runtimeService;
暴露Flowable引擎收集的所有历史数据。当执行流程时,引擎会保存许多数据(可配置),例如流程实例启动时间、谁在执行哪个任务、完成任务花费的事件、每个流程实例的执行路径,等等。这个服务主要提供查询这些数据的能力
historyService.createHistoricProcessInstanceQuery().list() 查询流程实例列表(历史流程,包括未完成的)
historyService.createHistoricProcessInstanceQuery().list().foreach().getValue() 可以获取历史中表单的信息
historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); 根绝id查询流程实例
historyService.deleteHistoricProcessInstance() 删除历史流程
historyService.deleteHistoricTaskInstance(taskid); 删除任务实例
historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list() 流程实例节点列表 (当前进行到的那个节点的流程图使用)
flowable 有api查看act_hi_varinst里面的数据吗
HistoricVariableInstanceQuery query = historyService.createHistoricVariableInstanceQuery().processInstanceId(instance.getId());
HistoricVariableInstance operate = query.variableName("operate").singleResult();
if (operate.getValue().toString().equals("1")) { // 表示流程同意}
对流程实例的各个节点的审批处理
流转的节点审批
taskService.createTaskQuery().list() 待办任务列表
taskService.createTaskQuery().taskId(taskId).singleResult(); 待办任务详情
taskService.saveTask(task); 修改任务
taskService.setAssignee() 设置审批人
taskService.addComment() 设置审批备注
taskService.complete() 完成当前审批
taskService.getProcessInstanceComments(processInstanceId); 查看任务详情(也就是都经过哪些人的审批,意见是什么)
taskService.delegateTask(taskId, delegater); 委派任务
taskService.claim(taskId, userId);认领任务
taskService.unclaim(taskId); 取消认领
taskService.complete(taskId, completeVariables); 完成任务
任务授权
taskService.addGroupIdentityLink()新增组任务授权
taskService.addUserIdentityLink() 新增人员任务授权
taskService.deleteGroupIdentityLink() 删除组任务授权
taskService.deleteUserIdentityLink() 删除人员任务授权
主要是执行自定义命令
managementService.executeCommand(new classA()) 执行classA的内部方法
在自定义的方法中可以使用以下方法获取repositoryService
ProcessEngineConfiguration processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
RepositoryService repositoryService = processEngineConfiguration.getRepositoryService();
也可以获得流程定义方法集合
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
ProcessDefinitionEntityManager processDefinitionEntityManager =
processEngineConfiguration.getProcessDefinitionEntityManager();
如findById/findLatestProcessDefinitionByKey/findLatestProcessDefinitionByKeyAndTenantId等。
用于身份信息获取和保存,这里主要是获取身份信息
用于管理(创建,更新,删除,查询……)组与用户。请注意,Flowable实际上在运行时并不做任何用户检查。例如任务可以分派给任何用户,而引擎并不会验证系统中是否存在该用户。这是因为Flowable有时要与LDAP、Active Directory等服务结合使用
identityService.createUserQuery().userId(userId).singleResult(); 获取审批用户的具体信息
identityService.createGroupQuery().groupId(groupId).singleResult(); 获取审批组的具体信息
可用于修改流程定义中的部分内容,而不需要重新部署它。例如可以修改流程定义中一个用户任务的办理人设置,或者修改一个服务任务中的类名。
标准的审批流系统都有一套标准化的业务流程下文,介绍如何操作审批流系统。
一般在系统中的模块名如下, 请个字对应。
这里的任务管理可以查看到一个流程每个审批节点的具体信息。
flowable为我们提供了47张表和7个常用api
常常查看的数据表
PROC_INST_ID_ 是流程实例的唯一id
表分类 | 表名 | 表说明 |
---|---|---|
一般数据(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 | 流程定义信息 |
bpmn例子:
我们基本上使用不到这么多,主要是使用 启动、结束事件,用户任务事件,服务事件,有些事件需要设定指定审批人;
格式
“用户任务(user task)”指需要人工执行的任务。当流程执行到达用户任务时,流程实例会停止等待,直到用户触发完成任务动作。
用户任务在XML中如下定义。其中id是必须属性,name是可选属性。
<userTask id="theTask" name="重要任务" />
每个任务都可以设置到期日期(due date)。
可以指定固定时间或相对时间,比如,当dueDate为“PT30M”时,表示到达任务30分钟后到期。
到期日期必须符合java.util.Date或java.util.String(ISO8601格式)。
实际应用,我们指定为变量值。
<userTask id="theTask" name="Important task" flowable:dueDate="${dateVariable}"/>
任务的到期日期可以使用TaskService,或者在TaskListener中使用传递的DelegateTask修改。
任务指派
指派确定的办理人
<userTask id="theTask" name="重要任务" flowable:assignee="jinyangjie"/>
指派潜在办理人
<userTask id="theTask" name="重要任务" flowable:candidateUsers="jinyangjie, zhangsan" />
指派潜在办理组
<userTask id="theTask" name="重要任务" flowable:candidateGroups="leader, manager" />
更多任务指派的内容,已在“用户和组”的篇章中介绍,这里不再赘述。
会签用户任务属性设置
多实例类型
可以选择并行会签(parallel)、顺序会签(sequential),其中并行会签的意思是 多个人同时执行任务。顺序会签是按顺序执行任务。
集合多实例
我这里设置assigneeList。这个是会签的人员的ID集合的变量名称。可以随意命名。
元素变量(多实例)
就是循环的变量名称,我这里设置assignee。 可以随意命名。
分配用户
需要和元素变量(多实例)的名称保持一致,比如元素变量(多实例)设置a,那么分配用户就要设置
a
。由于我元素变量(多实例)设置的是
a
s
s
i
g
n
e
e
,所以分配用户我这里值是
{a}。由于我元素变量(多实例)设置的是assignee,所以分配用户我这里值是
a。由于我元素变量(多实例)设置的是assignee,所以分配用户我这里值是{assignee}.
完成条件
${nrOfCompletedInstances/nrOfInstances == 1}
nrOfCompletedInstances: 完成的实例数
nrOfInstances:会签实例总数
比如设置${nrOfCompletedInstances/nrOfInstances == 1} 是指必须审批人全部同意才会到下一个任务节点。
格式
Java Service任务(Java service task)用于调用Java类。Java Service不属于BPMN2.0规范,而是Flowable的自定义扩展。
有三种方法声明如何调用Java逻辑
第一种: 调用固定的类
使用flowable:class属性提供全限定类名(fully qualified classname),指定流程执行时调用的类,该类必须实现JavaDelegate或ActivityBehavior接口。
<serviceTask id="javaService" flowable:class="com.example.service.MyJavaDelegate" />
第二种:调用动态类
使用flowable:delegateExpression属性提供委托对象(delegation object)的表达式。该功能和flowable:class类似,同样需要实现JavaDelegate或ActivityBehavior接口,只不过这里不是指定一个具体的实现类,而是查询指定名称的Bean对象。
<serviceTask id="javaService" flowable:delegateExpression="${myDelegateExpressionBean}" />
myDelegateExpressionBean是一个实现了JavaDelegate接口的bean,定义在Spring容器中。
第三种:调用类的指定方法或属性值
使用flowable:expression属性指定类的方法或属性值。同样的,该类需要实现JavaDelegate或ActivityBehavior接口。
<serviceTask id="javaService" flowable:expression="#{printer.printMessage()}" />
将在名为printer的对象上调用printMessage方法(不带参数)。当然也可以为表达式中使用的方法传递变量。
属性值示例:
<serviceTask id="javaService" flowable:expression="#{printer.ready}" />
会调用名为printer的bean的ready参数的getter方法,getReady(不带参数)。该值会被解析为执行的流程变量。
例子
可以查看4.2.5
下面是一个Java类的示例,用于将流程变量String改为大写。这个类通过实现org.flowable.engine.delegate.JavaDelegate接口,可以在流程执行中被调用。
同时,需要重写execute(DelegateExecution)方法实现业务逻辑。这个方法就是引擎将调用的方法。另外,通过该方法中的DelegateExecution参数可以访问流程实例的各种信息。
public class ToUppercase implements JavaDelegate {
public void execute(DelegateExecution execution) {
String var = (String) execution.getVariable("input");
var = var.toUpperCase();
execution.setVariable("input", var);
}
}
如果实现org.flowable.engine.impl.delegate.ActivityBehavior接口,可以访问更强大的引擎功能,例如,可以影响流程的控制流程。但注意这并不是好的实践,需要避免这么使用。
任务的返回值
服务执行的返回值(仅对使用表达式的服务任务),可以通过为服务任务定义的’flowable:resultVariable’属性设置为流程变量。可以是已经存在的,或者新的流程变量。 如果指定为已存在的流程变量,则流程变量的值会被服务执行的返回值覆盖。 如果不指定结果变量名,则服务任务的返回值将被忽略。
<serviceTask id="aMethodExpressionServiceTask"
flowable:expression="#{myService.doSomething()}"
flowable:resultVariable="myVar" />
在上例中,服务执行的结果(调用’doSomething()'方法的返回值),在服务执行完成后,会设置为名为’myVar’的流程变量。
格式
脚本任务(script task)是自动执行的活动。当流程执行到达脚本任务时,会执行相应的脚本。
脚本任务使用script与scriptFormat元素定义。
<scriptTask id="theScriptTask" scriptFormat="groovy">
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
</scriptTask>
默认情况下,JavaScript包含在每一个JDK中,因此不需要添加任何JAR文件。如果想使用其它脚本引擎,则需要在classpath中添加相应的jar,并使用适当的名字。例如,Flowable单元测试经常使用Groovy。Groovy脚本引擎与groovy-all JAR捆绑在一起。添加如下依赖:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.x.x<version>
</dependency>
脚本中的变量
到达脚本引擎的执行中,所有的流程变量都可以在脚本中使用。在这个例子里,脚本变量’inputArray’实际上就是一个流程变量(一个integer的数组)。
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
在脚本中设置变量的例子:
<script>
def scriptVar = "test123"
execution.setVariable("myVar", scriptVar)
</script>
注意:下列名字是保留字,不能用于变量名:out,out:print,lang:import,context,elcontext。
脚本任务的结果
脚本任务的返回值,可以通过为脚本任务定义的’flowable:resultVariable’属性设置为流程变量。可以是已经存在的,或者新的流程变量。如果指定为已存在的流程变量,则流程变量的值会被脚本执行的结果值覆盖。如果不指定结果变量名,则脚本结果值将被忽略。
<scriptTask id="theScriptTask" scriptFormat="juel" flowable:resultVariable="myVar">
<script>#{echo}</script>
</scriptTask>
在上面的例子中,脚本执行的结果(解析表达式’#{echo}'的值),将在脚本完成后,设置为名为’myVar’的流程变量。
格式
在企业应用中,推荐做法是使用可维护的规则库来管理复杂多变的业务规则,将业务代码和规则分开维护,一旦规则有变动,只需修改预设规则即可,而不会影响到业务代码。
业务规则任务可以根据流程变量的值处理预设的业务规则。Flowable支持目前最流行的规则引擎——Drools。只需把含有业务规则任务的流程文件和规则引擎文件“.drl”一同打包部署到系统中,同时添加Drools的jar包,即可实现Flowable驱动规则引擎。
要执行业务规则,需要定义输入与结果变量。输入变量可以用流程变量的列表定义,使用逗号分隔。输出变量只能有一个变量名,如果没有指定结果变量名,默认为org.flowable.engine.rules.OUTPUT。
<process id="simpleBusinessRuleProcess">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="businessRuleTask" />
<businessRuleTask id="businessRuleTask" flowable:ruleVariablesInput="${order}"
flowable:resultVariable="rulesOutput" />
<sequenceFlow sourceRef="businessRuleTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
也可以将业务规则任务配置为只执行部署的.drl文件中的一组规则。要做到这一点,需要指定规则名字的列表,用逗号分隔。
<businessRuleTask id="businessRuleTask" flowable:ruleVariablesInput="${order}"
flowable:rules="rule1, rule2" />
这样只会执行rule1与rule2。
也可以定义需要从执行中排除的规则列表。
<businessRuleTask id="businessRuleTask" flowable:ruleVariablesInput="${order}"
flowable:rules="rule1, rule2" exclude="true" />
这个例子中,除了rule1与rule2之外,其它所有与流程定义一起部署的规则都会被执行。
格式
手动任务(manual task)用来定义在BPM引擎不能完成的任务。对于引擎来说,手动任务将当做一个空任务来处理,在流程执行到达手动任务时,自动继续执行流程。
<manualTask id="myManualTask" name="Call client for more information" />
Java接收任务
接收任务(receive task),是等待特定消息到达的简单任务。当流程执行到达接收任务时,将保持等待状态,直到引擎接收到特定的消息,触发流程穿过接收任务继续执行。
<receiveTask id="waitState" name="wait" />
使用方法
要使流程实例从接收任务的等待状态中继续执行,需要使用到达接收任务的执行id,调用runtimeService.signal(executionId)。下面的代码片段展示了如何操作:
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState")
.singleResult();
runtimeService.trigger(execution.getId());
格式
Shell任务(Shell task)可以运行Shell脚本与命令。请注意Shell任务不是BPMN 2.0规范的“官方”任务(因此也没有专用图标)。
<serviceTask id="shellEcho" flowable:type="shell">
Shell任务参数
Shell任务通过字段注入配置。这些参数的值可以使用EL表达式,将在流程执行运行时解析。可以设置下列参数:
使用示例
下面的XML代码片段是使用Shell任务的例子。将会运行"cmd /c echo EchoTest" Shell脚本,等待其结束,并将其结果存入resultVar。
<serviceTask id="shellEcho" flowable:type="shell" >
<extensionElements>
<flowable:field name="command" stringValue="cmd" />
<flowable:field name="arg1" stringValue="/c" />
<flowable:field name="arg2" stringValue="echo" />
<flowable:field name="arg3" stringValue="EchoTest" />
<flowable:field name="wait" stringValue="true" />
<flowable:field name="outputVariable" stringValue="resultVar" />
</extensionElements>
</serviceTask>
Web Service任务:调用外部的Web Service资源。
网关相当于判断(与或非),最长用的三种网关分别是 互斥/排他网关、并行网关、包容网关
相当于判断,举例:如果输入值大于20走A节点,小20走B节点
符号:
例子:
与相容网关成对出现,表示网关中的人全部同意才能进入下一节点
它可以将执行分支(fork)为多条路径,也可以合并(join)多条入口路径的执行
符号
例子
互斥网关与并行网关的结合体,如果满足A,B都互斥条件,则都需要流转,如果只有一个满足,那么久只流转满足条件的
包容网关与排他网关一样,可以在包容网关的出口顺序流上定义条件,包容网关会计算条件。然而主要的区别是,包容网关与并行网关一样,可以同时选择多于一条出口顺序流。
符号
例子
说明:
1.当审批申请人提交完成后进入审批阶段
2.两位项目经理进行审批,需要两人都同意进入下一节点
3.经理进行审批,此处业务为经理职位(mamage)的人都可以执行审批。(此处有认领业务此处不描述)
4.总经理/董事长审批,如果大于20天需要董事长审批,如果小于20天总经理审批。
分为两种,一个是执行监听器,一个是任务监听器
下面的代码片段为流程定义增加了2个监听器。第一个监听器接收任何类型的事件,使用完全限定类名定义。第二个监听器只在作业成功执行或失败时被通知,使用流程引擎配置中beans参数定义的bean作为监听器。
<process id="testEventListeners">
<extensionElements>
<flowable:eventListener class="org.flowable.engine.test.MyEventListener" />
<flowable:eventListener delegateExpression="${testEventListener}" events="JOB_EXECUTION_SUCCESS,JOB_EXECUTION_FAILURE" />
</extensionElements>
...
</process>
entityType可用的值有:attachment(附件), comment(备注), execution(执行), identity-link(身份关联), job(作业), process-instance(流程实例), process-definition(流程定义), task(任务)。
执行监听器(execution listener)可以在流程执行中发生特定的事件时,执行外部Java代码或计算表达式。可以被捕获的事件有:
有三种类型:start(开始任务)、task(执行任务)、end(结束任务)
下面的流程定义包含了三个执行监听器:
<process id="executionListenersProcess">
<extensionElements>
<flowable:executionListener
class="org.flowable.examples.bpmn.executionlistener.ExampleExecutionListenerOne"
event="start" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="secondTask">
<extensionElements>
<flowable:executionListener
class="org.flowable.examples.bpmn.executionListener.ExampleExecutionListenerTwo" />
</extensionElements>
</sequenceFlow>
<userTask id="secondTask" >
<extensionElements>
<flowable:executionListener
expression="${myPojo.myMethod(execution.event)}"
event="end" />
</extensionElements>
</userTask>
<sequenceFlow sourceRef="secondTask" targetRef="thirdTask" />
<userTask id="thirdTask" />
<sequenceFlow sourceRef="thirdTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
第一个执行监听器将在流程启动时收到通知。这个监听器是一个外部Java类(ExampleExecutionListenerOne),并且需要实现org.flowable.engine.delegate.ExecutionListener接口。当该事件发生时(这里是start事件),会调用notify(ExecutionListenerExecution execution)方法。
public class ExampleExecutionListenerOne implements ExecutionListener {
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("variableSetInExecutionListener", "firstValue");
execution.setVariable("eventReceived", execution.getEventName());
}
}
注意不同版本的flowable可能方法会有不同,比如下面6.3.0的
也可以使用实现了org.flowable.engine.delegate.JavaDelegate接口的委托类。这些委托类也可以用于其他的结构,如服务任务的委托。
第二个执行监听器在流程执行转移时被调用。请注意listener元素并未定义event,因为在转移上只会触发take事件。当监听器定义在转移上时,event属性的值将被忽略。
最后一个执行监听器在secondTask活动结束时被调用。监听器声明中没有使用class,而是定义了expression。这个表达式将在事件触发时计算/调用。
<flowable:executionListener expression="${myPojo.myMethod(execution.eventName)}" event="end" />
与其他表达式一样,可以使用与解析execution变量。
下面的代码片段展示了一个简单的示例流程,带有一个使用了字段注入的执行监听器。
<process id="executionListenersProcess">
<extensionElements>
<flowable:executionListener
class="org.flowable.examples.bpmn.executionListener.ExampleFieldInjectedExecutionListener"
event="start">
<flowable:field name="fixedValue" stringValue="Yes, I am " />
<flowable:field name="dynamicValue" expression="${myVar}" />
</flowable:executionListener>
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
ExampleFieldInjectedExecutionListener类将连接两个字段(一个是固定值-fixedValue,另一个是动态值-dynamicValue),并将其存储在’var’流程变量中。
@Deployment(resources = {
"org/flowable/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("myVar", "listening!");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
"executionListenersProcess", variables);
Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
assertNotNull(varSetByListener);
assertTrue(varSetByListener instanceof String);
// 结果为固定注入字段及注入表达式的连接
assertEquals("Yes, I am listening!", varSetByListener);
}
格式
任务监听器(task listener)用于在特定的任务相关事件发生时,执行自定义的Java逻辑或表达式。
任务监听器只能在流程定义中作为用户任务的子元素。请注意,任务监听器是一个Flowable自定义结构,因此也需要作为BPMN 2.0 extensionElements,放在flowable命名空间下。
<userTask id="myTask" >
<extensionElements>
<flowable:taskListener event="create" class="com.example.MyTaskCreateListener" />
</extensionElements>
</userTask>
任务监听器属性
event: 触发任务监听器的任务事件类型,必填项。可用的事件有:
create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。
assignment(指派):当任务已经指派给某人时触发。
请注意:当流程执行到达用户任务时,在触发create事件之前,会首先触发 assignment事件。这顺序看起来不太自然,但是有实际 原因的:当收到create事件时,我们通常希望能看到任务的所有参数,包括办理人。
complete(完成):当任务已经完成,从运行时数据中删除前触发。
delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
class:要调用的委托类。这个类必须实现org.flowable.engine.delegate.TaskListener接口。
public class MyTaskCreateListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
// 任务类型
String eventName = delegateTask.getEventName();
switch (eventName) {
case EVENTNAME_CREATE:
System.out.println("创建-发邮件");
break;
case EVENTNAME_ASSIGNMENT:
System.out.println("任务分配审核人1");
break;
case EVENTNAME_COMPLETE:
System.out.println("发邮件 11111111");
break;
case EVENTNAME_DELETE:
System.out.println("删除任务");
break;
}
}
}
也可以使用字段注入,为委托类传递流程变量或执行。请注意委托类的实例在流程部署时创建(与Flowable中其它的委托类一样),这意味着该实例会在所有流程实例执行中共享。
<flowable:taskListener event="create" expression="${myObject.callMethod(task, task.eventName)}" />
<flowable:taskListener event="create" delegateExpression="${myTaskListenerBean}" />
格式
多实例活动(multi-instance activity)是在业务流程中,为特定步骤定义重复的方式。在编程概念中,多实例类似for each结构:可以为给定集合中的每一条目,顺序或并行地,执行特定步骤,甚至是整个子流程。
网关和事件不能设置为多实例。
按照BPMN2.0规范的要求,用于为每个实例创建执行的父执行,会提供下列变量:
nrOfInstances:实例总数。
nrOfActiveInstances:当前活动的(即未完成的)实例数量。对于顺序多实例,这个值总为1。
nrOfCompletedInstances:已完成的实例数量。
可以调用execution.getVariable(x)方法获取这些值。
另外,每个被创建的执行,都有局部变量(对其他执行不可见,也不存储在流程实例级别):
loopCounter:给定实例在for-each循环中的index。
要将活动变成多实例,该活动的XML元素必须有multiInstanceLoopCharacteristics子元素
<multiInstanceLoopCharacteristics isSequential="false|true">
...
</multiInstanceLoopCharacteristics>
isSequential属性代表了活动的实例为顺序还是并行执行。
有4种不同方法可以配置数量。
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>
<userTask id="miTasks" name="My Task ${loopCounter}" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>assigneeList</loopDataInputRef>
<inputDataItem name="assignee" />
</multiInstanceLoopCharacteristics>
</userTask>
假设变量assigneeList包含[kermit, gonzo, fozzie]。上面的代码会创建三个并行的用户任务。每一个执行都有一个名为assignee的(局部)流程变量,含有集合中的一项,并在这个例子中被用于指派用户任务。
loopDataInputRef与inputDataItem的缺点是名字很难记,并且由于BPMN 2.0概要的限制,不能使用表达式。Flowable通过在multiInstanceCharacteristics上提供collection与elementVariable属性解决了这些问题:
<userTask id="miTasks" name="My Task" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="${myService.resolveUsersForTask()}" flowable:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>
请注意collection属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,这个字符串都会被当做变量名,在流程变量中用于获取实际的集合。
例如,下面的代码片段会让引擎查找存储在assigneeList流程变量中的集合:
<userTask id="miTasks" name="My Task" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="assigneeList" flowable:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>
<userTask id="miTasks" name="My Task" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false"
flowable:collection="assigneeList" flowable:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
在这个例子里,会为assigneeList集合中的每个元素创建并行实例。当60%的任务完成时,其他的任务将被删除,流程继续运行。
给任务设置多个候选人或者候选人组,可以从候选人中选择参与者来完成任务。
其实就是,第二审核人;
定义流程图
部署和启动流程实例
部署流程,并且在启动流程实例的时候对UEL表达式赋值
/**
* 部署流程
*/
@Test
public void deploy(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("请假流程-候选人.bpmn20.xml")
.name("请求流程-候选人")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println(deploy.getName());
}
/**
* 启动流程实例
*/
@Test
public void runProcess(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
// 给流程定义中的UEL表达式赋值
Map<String,Object> variables = new HashMap<>();
variables.put("candidate1","张三");
variables.put("candidate2","李四");
variables.put("candidate3","王五");
runtimeService.startProcessInstanceById("holiday-candidate:1:4",variables);
}
任务的查询
根据当前登录的用户,查询对应的候选任务
/**
* 根据登录的用户查询对应的可以拾取的任务
*
*/
@Test
public void queryTaskCandidate(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-candidate:1:4")
.taskCandidateUser("李四") # 注意
.list();
for (Task task : list) {
System.out.println("task.getId() = " + task.getId());
System.out.println("task.getName() = " + task.getName());
}
}
任务的拾取
知道了我有可拾取的任务后,拾取任务。
/**
* 拾取任务
* 一个候选人拾取了这个任务之后其他的用户就没有办法拾取这个任务了
* 所以如果一个用户拾取了任务之后又不想处理了,那么可以退还
*/
@Test
public void claimTaskCandidate(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-candidate:1:4")
.taskCandidateUser("李四")
.singleResult();
if(task != null){
// 拾取对应的任务
taskService.claim(task.getId(),"李四");
System.out.println("任务拾取成功");
}
}
任务的归还
拾取任务后不想操作那么就归还任务
/**
* 退还任务
* 一个候选人拾取了这个任务之后其他的用户就没有办法拾取这个任务了
* 所以如果一个用户拾取了任务之后又不想处理了,那么可以退还
*/
@Test
public void unclaimTaskCandidate(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-candidate:1:4")
.taskAssignee("张三")
.singleResult();
if(task != null){
// 拾取对应的任务
taskService.unclaim(task.getId());
System.out.println("归还拾取成功");
}
}
任务的交接
拾取任务后如果不想操作也不想归还可以直接交接给另外一个人来处理
/**
* 任务的交接
* 如果我获取了任务,但是不想执行,那么我可以把这个任务交接给其他的用户
*/
@Test
public void taskCandidate(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-candidate:1:4")
.taskAssignee("李四")
.singleResult();
if(task != null){
// 任务的交接
taskService.setAssignee(task.getId(),"王五");
System.out.println("任务交接给了王五");
}
}
任务的完成
正常的任务处理
/**
* 完成任务
*/
@Test
public void completeTask(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-candidate:1:4")
.taskAssignee("王五")
.singleResult();
if(task != null){
// 完成任务
taskService.complete(task.getId());
System.out.println("完成Task");
}
}
当候选人很多的情况下,我们可以分组来处理。先创建组,然后把用户分配到这个组中。
创建用户
/**
* 维护用户
*/
@Test
public void createUser(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 通过 IdentityService 完成相关的用户和组的管理
IdentityService identityService = processEngine.getIdentityService();
User user = identityService.newUser("田佳");
user.setFirstName("田");
user.setLastName("jia");
user.setEmail("tianjia@qq.com");
identityService.saveUser(user);
}
Group管理
/**
* 创建用户组
*/
@Test
public void createGroup(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
// 创建Group对象并指定相关的信息
Group group = identityService.newGroup("group2");
group.setName("开发部");
group.setType("type1");
// 创建Group对应的表结构数据
identityService.saveGroup(group);
}
用户分配组
用户和组是一个多对多的关联关联,我们需要做相关的分配,后台对应的表结构是ACT_ID_MEMBERSHIP
/**
* 将用户分配给对应的Group
*/
@Test
public void userGroup(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
// 根据组的编号找到对应的Group对象
Group group = identityService.createGroupQuery().groupId("group1").singleResult();
List<User> list = identityService.createUserQuery().list();
for (User user : list) {
// 将用户分配给对应的组
identityService.createMembership(user.getId(),group.getId());
}
}
候选人组应用
然后我们把流程部署和运行,注意对UEL表达式赋值,关联上Group
/**
* 部署流程
*/
@Test
public void deploy(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deploy = repositoryService.createDeployment()
.addClasspathResource("请假流程-候选人组.bpmn20.xml")
.name("请求流程-候选人")
.deploy();
System.out.println("deploy.getId() = " + deploy.getId());
System.out.println(deploy.getName());
}
/**
* 启动流程实例
*/
@Test
public void runProcess(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
Group group = identityService.createGroupQuery().groupId("group1").singleResult();
RuntimeService runtimeService = processEngine.getRuntimeService();
// 给流程定义中的UEL表达式赋值
Map<String,Object> variables = new HashMap<>();
// variables.put("g1","group1");
variables.put("g1",group.getId()); // 给流程定义中的UEL表达式赋值
runtimeService.startProcessInstanceById("holiday-group:1:17504",variables);
}
任务的拾取和完成
然后完成任务的查询拾取和处理操作
/**
* 根据登录的用户查询对应的可以拾取的任务
*
*/
@Test
public void queryTaskCandidateGroup(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 根据当前登录的用户找到对应的组
IdentityService identityService = processEngine.getIdentityService();
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember("邓彪").singleResult();
TaskService taskService = processEngine.getTaskService();
List<Task> list = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-group:1:17504")
.taskCandidateGroup(group.getId())
.list();
for (Task task : list) {
System.out.println("task.getId() = " + task.getId());
System.out.println("task.getName() = " + task.getName());
}
}
/**
* 拾取任务
* 一个候选人拾取了这个任务之后其他的用户就没有办法拾取这个任务了
* 所以如果一个用户拾取了任务之后又不想处理了,那么可以退还
*/
@Test
public void claimTaskCandidate(){
String userId = "田佳";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 根据当前登录的用户找到对应的组
IdentityService identityService = processEngine.getIdentityService();
// 当前用户所在的组
Group group = identityService.createGroupQuery().groupMember(userId).singleResult();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-group:1:17504")
.taskCandidateGroup(group.getId())
.singleResult();
if(task != null) {
// 任务拾取
taskService.claim(task.getId(),userId);
System.out.println("任务拾取成功");
}
}
/**
* 完成任务
*/
@Test
public void completeTask(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
Task task = taskService.createTaskQuery()
//.processInstanceId("2501")
.processDefinitionId("holiday-group:1:17504")
.taskAssignee("邓彪")
.singleResult();
if(task != null){
// 完成任务
taskService.complete(task.getId());
System.out.println("完成Task");
}
}
public ProcessEngineConfiguration configuration =null;
@Before
public void buildConfiguration() {
configuration = new StandaloneProcessEngineConfiguration();
configuration.setJdbcDriver("com.mysql.jdbc.Driver");
configuration.setJdbcUsername("root");
configuration.setJdbcPassword("root");
configuration.setJdbcUrl("jdbc:mysql://localhost:3306/test?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai");
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
}
// 创建
@Test
public void createFlowUser() {
ProcessEngine processEngine = configuration.buildProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
User user = identityService.newUser("liangchao"); // 实际上是 ID_ 字段的值
user.setFirstName("Liang");
user.setLastName("Chao");
user.setEmail("18879493586");
identityService.saveUser(user);
// 验证是有保存成功
User result = identityService.createUserQuery().userId("liangchao").singleResult();
Assert.assertNotNull(result);
}
// 查看
@Test
public void getFlowUser() {
ProcessEngine processEngine = configuration.buildProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
System.out.println("开始查看flowable的用户");
List<User> list =
identityService.createUserQuery().list();
for (User user : list) {
System.out.println("=========="+user.getId());
}
}
// 删除
//删除用户
identityService.deleteUser("hello_");
userInDb = identityService.createUserQuery().userId("hello_").singleResult();
Assert.assertNull(userInDb);
/**
* 创建组
*/
@Test
void createGroup() {
ProcessEngine processEngine = configuration.buildProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
Group group1 = identityService.newGroup("depart1");
group1.setName("运维部门");
identityService.saveGroup(group1);
Group group2 = identityService.newGroup("depart2");
group2.setName("事业部门");
identityService.saveGroup(group2);
}
/**
* 查看当前已有组
*/
@Test
void getGroups() {
ProcessEngine processEngine = configuration.buildProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
List<Group> list = identityService.createGroupQuery().list();
System.out.println("------------------");
for (Group group : list) {
System.out.println("===============" + group.getId());
System.out.println("===============" + group.getName());
}
}
/**
* 用户分组
*/
@Test
void assignGroup() {
//将用户wumeishan(就是user表里面的ID_字段数据)放入deptLeader中
ProcessEngine processEngine = configuration.buildProcessEngine();
IdentityService identityService = processEngine.getIdentityService();
identityService.createMembership("wumeishan", "depart1");
List<User> users = identityService.createUserQuery().memberOfGroup("depart1").list();
for (User user : users) {
System.out.println("这个部门下有:"+ user.getId());
}
}
下面就是一个bpmn.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" exporter="Flowable Open Source Modeler" exporterVersion="6.7.2">
<process id="UploadItem" name="upload_item" isExecutable="true">
<documentation>素材上刊</documentation>
<startEvent id="startEvent1" flowable:formFieldValidation="true"></startEvent>
<userTask id="sid-EEE0EF41-F2F3-4D9D-9F8C-56E460930DAB" name="事业部部长审核" flowable:assignee="${assignee1}" flowable:formFieldValidation="true">
<extensionElements>
<modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete>
</extensionElements>
</userTask>
<sequenceFlow id="sid-BEC48776-BABE-43A1-8917-E8A03AF78E75" sourceRef="startEvent1" targetRef="sid-EEE0EF41-F2F3-4D9D-9F8C-56E460930DAB"></sequenceFlow>
<userTask id="sid-3009CB91-F98C-420E-B81A-ADC429624425" name="运维部审核" flowable:candidateGroups="${assignee2}" flowable:formFieldValidation="true"></userTask>
<sequenceFlow id="sid-B31CA8A5-21A5-4576-8968-94500E44F6C6" sourceRef="sid-EEE0EF41-F2F3-4D9D-9F8C-56E460930DAB" targetRef="sid-3009CB91-F98C-420E-B81A-ADC429624425"></sequenceFlow>
<endEvent id="sid-D12EA697-6789-421D-AE1C-3D9247F628B7"></endEvent>
<sequenceFlow id="sid-F7EE2C93-7E18-4C77-8D0E-6364C0456A99" sourceRef="sid-3009CB91-F98C-420E-B81A-ADC429624425" targetRef="sid-D12EA697-6789-421D-AE1C-3D9247F628B7"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_UploadItem">
<bpmndi:BPMNPlane bpmnElement="UploadItem" id="BPMNPlane_UploadItem">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-EEE0EF41-F2F3-4D9D-9F8C-56E460930DAB" id="BPMNShape_sid-EEE0EF41-F2F3-4D9D-9F8C-56E460930DAB">
<omgdc:Bounds height="80.0" width="100.0" x="175.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-3009CB91-F98C-420E-B81A-ADC429624425" id="BPMNShape_sid-3009CB91-F98C-420E-B81A-ADC429624425">
<omgdc:Bounds height="80.0" width="100.0" x="320.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-D12EA697-6789-421D-AE1C-3D9247F628B7" id="BPMNShape_sid-D12EA697-6789-421D-AE1C-3D9247F628B7">
<omgdc:Bounds height="28.0" width="28.0" x="465.0" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-B31CA8A5-21A5-4576-8968-94500E44F6C6" id="BPMNEdge_sid-B31CA8A5-21A5-4576-8968-94500E44F6C6" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="274.9499999999907" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="319.9999999999807" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-F7EE2C93-7E18-4C77-8D0E-6364C0456A99" id="BPMNEdge_sid-F7EE2C93-7E18-4C77-8D0E-6364C0456A99" flowable:sourceDockerX="50.0" flowable:sourceDockerY="40.0" flowable:targetDockerX="14.0" flowable:targetDockerY="14.0">
<omgdi:waypoint x="419.95000000000005" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="465.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-BEC48776-BABE-43A1-8917-E8A03AF78E75" id="BPMNEdge_sid-BEC48776-BABE-43A1-8917-E8A03AF78E75" flowable:sourceDockerX="15.0" flowable:sourceDockerY="15.0" flowable:targetDockerX="50.0" flowable:targetDockerY="40.0">
<omgdi:waypoint x="129.9499984899576" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="174.9999999999917" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
部署一个流程定义意味着:
将流程定义部署至Flowable引擎,需要使用RepositoryService,其可以从ProcessEngine对象获取。使用RepositoryService,可以通过XML文件的路径创建一个新的部署(Deployment),并调用deploy()方法实际执行:
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml").name("起名字:请假流程").
.deploy();
此时数据存储进了act_re_deployment(存储部署的流程),act_ge_bytearray(存储xml文件),act_re_procder(存储流程信息)
查询验证流程定义已经部署在引擎中的流程bpmn文件名
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId()) // 这儿可以查询多个 deploymentIds
.singleResult();
System.out.println("Found process definition : " + processDefinition.getName());
我们现在可以通过API查询验证流程定义已经部署在引擎中(并学习一些API)。通过RepositoryService创建的ProcessDefinitionQuery对象实现。
@Autowired
public ProcessEngine processEngine;
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId()) // act_re_deployment里面的id
.singleResult();
System.out.println("Found process definition : " + processDefinition.getName());
sql是执行这个
RepositoryService repositoryService = processEngine.getRepositoryService();
/**
* deleteDeployment
* Deployment deployment
* 第一参数是流程id(deployment.getId()) 如果流程启动将不允许删除
* 第二参数数级联删除 如果流程启动了也可以删除 相关的任务也会删除
*/
repositoryService.deleteDeployment(deployment.getId());
repositoryService.deleteDeployment(deployment.getId(), true);
现在已经在流程引擎中部署了流程定义,因此可以使用这个流程定义作为“蓝图”启动流程实例。
要启动流程实例,需要提供一些初始化流程变量。一般来说,可以通过呈现给用户的表单,或者在流程由其他系统自动触发时通过REST API,来获取这些变量。在这个例子里,我们简化为使用java.util.Scanner类在命令行输入一些数据
Scanner scanner= new Scanner(System.in);
System.out.println("Who are you?");
String employee = scanner.nextLine();
System.out.println("How many holidays do you want to request?");
Integer nrOfHolidays = Integer.valueOf(scanner.nextLine());
System.out.println("Why do you need them?");
String description = scanner.nextLine();
// 启动流程操作
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<String, Object>(); // 流程变量
variables.put("employee", employee); // key是自己设置的,流程申请人
variables.put("nrOfHolidays", nrOfHolidays); //key是自己设置的,请假天数
variables.put("description", description); //key是自己设置的,请假原因
// 这个key就是BPMN 2.0 XML文件中设置的id属性 <process id="XXX" name="Holiday Request" isExecutable="true">
ProcessInstance shenhe = runtimeService.startProcessInstanceByKey("shenhe", variables);
// ProcessInstance instance = runtimeService.startProcessInstanceById("materialuploadreview:1:4", variables); // act_re_procder里面的ID_
/** 设置流程发起人
Authentication.setAuthenticatedUserId("赵六");
ProcessInstance instance = runtimeService.startProcessInstanceById("UploadItem:1:4", variables);
Authentication.setAuthenticatedUserId(null);
**/
此时,act_ru_variable(存储流程变量),act_ru_task(存储流程任务),act_ru_execution(存储流程执行情况)
(流程实例需要的审核人,操作人可以查看进度)
usertask等等
我们还没有为用户任务配置办理人。我们想将第一个任务指派给"经理(managers)"组,而第二个用户任务指派给请假申请的提交人。因此需要为第一个任务添加candidateGroups属性:(flowable:candidateGroups指定一个组)
<userTask id="approveTask" name="Approve or reject request" flowable:candidateGroups="managers"/>
并如下所示为第二个任务添加assignee属性。请注意我们没有像上面的’managers’一样使用静态值,而是使用一个流程变量动态指派。这个流程变量是在流程实例启动时传递的:(flowable:assignee指定具体的人)
<userTask id="holidayApprovedTask" name="Holiday approved" flowable:assignee="${employee}"/>
要获得实际的任务列表,需要通过TaskService创建一个TaskQuery。我们配置这个查询只返回’managers’组的任务:
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("managers").list();
System.out.println("You have " + tasks.size() + " tasks:");
for (int i=0; i<tasks.size(); i++) {
System.out.println((i+1) + ") " + tasks.get(i).getName());
}
可以使用任务Id获取特定流程实例的变量,并在屏幕上显示实际的申请:(flowable:assignee=“$INITIATOR” 流程发起人)
System.out.println("Which task would you like to complete?");
int taskIndex = Integer.valueOf(scanner.nextLine());
Task task = tasks.get(taskIndex - 1);
Map<String, Object> processVariables = taskService.getVariables(task.getId());
System.out.println(processVariables.get("employee") + " wants " +
processVariables.get("nrOfHolidays") + " of holidays. Do you approve this?");
也可以通过流程key和任务处理人来查询任务
List<Task> list = taskService.createTaskQuery().processDefinitionKey("holidayRequest") //xml里面的id
.taskAssignee("zhangsan").list(); // 处理人会和task表对应名字
经理现在就可以完成任务了。在现实中,这通常意味着由用户提交一个表单。表单中的数据作为流程变量传递。在这里,我们在完成任务时传递带有’approved’变量(这个名字很重要,因为之后会在顺序流的条件中使用!)的map来模拟:
variables = new HashMap<String, Object>();
variables.put("approved", false); // key是排他网关那边你定义的变量名
TaskService taskService = processEngine.getTaskService();
Task task= taskService.createTaskQuery().processDefinitionKey("holidayRequest") //xml里面的id
.taskAssignee("zhangsan").singleResult();
taskService.complete(task.getId(), variables); // variables y或n
流程走完后就会把act_ru_variable,act_ru_task,act_ru_execution里面的数据都会没有了
act_hi_actinst里面会有历史记录
act_hi_varinst存储历史变量,里面也会存储完成时添加的变量
// 完成任务-不带参数
@Test
void completeTask() {
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskAssignee("莫珊珊").list(); // 人审批
for (Task task : tasks) {
System.out.println("------------------");
System.out.println("完成任务:" + taskService.getVariables(task.getId()).get("reason"));
//taskService.complete(task.getId());
}
}
// 完成任务-带参数
@Test
void rejectTask() {
ProcessEngine processEngine = configuration.buildProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("运维部门").list(); // 组审批
for (Task task : tasks) {
System.out.println("------------------");
System.out.println("描述:" + taskService.getVariables(task.getId()).get("reason"));
HashMap<String, Object> map = new HashMap<>();
map.put("wxoutcome", "运维部门-通过");
taskService.complete(task.getId(),map);
}
}
设置假如你是拒绝,就直接终止删除流程实例
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
String processInstanceId = task.getProcessInstanceId();
runtimeService.deleteProcessInstance(processInstanceId, reason);
拼图还缺了一块:我们还没有实现申请通过后执行的自动逻辑。在BPMN 2.0 XML中,这是一个服务任务(service task):
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
flowable:class="org.flowable.CallExternalSystemDelegate"/>
在现实中,这个逻辑可以做任何事情:向某个系统发起一个HTTP REST服务调用,或调用某个使用了好几十年的系统中的遗留代码。我们不会在这里实现实际的逻辑,而只是简单的日志记录流程。
具体操作如下:
创建一个类实现JavaDelegate接口。并且实现execute方法
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
public class CallExternalSystemDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) {
System.out.println("Calling the external system for employee "
+ execution.getVariable("employee"));
}
}
当执行到达服务任务时,会初始化并调用BPMN 2.0 XML中所引用的类。
上述例子是输入y,就会走这个了;
选择使用Flowable这样的流程引擎的原因之一,是它可以自动存储所有流程实例的审计数据或历史数据。这些数据可以用于创建报告,深入展现组织运行的情况,瓶颈在哪里,等等。
例如,如果希望显示流程实例已经执行的时间,就可以从ProcessEngine获取HistoryService,并创建历史活动(historical activities)的查询。在下面的代码片段中,可以看到我们添加了一些额外的过滤条件:
只选择一个特定流程实例的活动
只选择已完成的活动
结果按照结束时间排序,代表其执行顺序。
HistoryService historyService = processEngine.getHistoryService();
List<HistoricActivityInstance> activities =
historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstance.getId())
.finished()
.orderByHistoricActivityInstanceEndTime().asc()
.list();
for (HistoricActivityInstance activity : activities) {
System.out.println(activity.getActivityId() + " took "
+ activity.getDurationInMillis() + " milliseconds");
}
再次运行例子,可以看到控制台中显示:
startEvent took 1 milliseconds
approveTask took 2638 milliseconds
decision took 3 milliseconds
externalSystemCall took 1 milliseconds
可以使用与之前完全相同的方式获取并申领审核任务。完成这个第二个任务会将流程执行移至结束事件,并结束流程实例。这个流程实例,及所有相关的运行时执行数据都会从数据库中移除。
也可以通过编程方式,使用historyService验证流程已经结束
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
ProcessEngine processEngine = configuration.buildProcessEngine();
HistoryService historyService = processEngine.getHistoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
.startedBy("赵六")
.orderByProcessInstanceStartTime().desc()
.list();
/**
* 获取某个发起人的所有流程
*/
@RequestMapping("/getFlowStartBy")
public void getFlowStartBy(@RequestParam("userId") int userId) {
HistoryService historyService = processEngine.getHistoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
TaskService taskService = processEngine.getTaskService();
RepositoryService repositoryService = processEngine.getRepositoryService();
// 得到这个用户下的所有申请的流程实例
List<HistoricProcessInstance> startProcessList = historyService.createHistoricProcessInstanceQuery()
.startedBy(String.valueOf(userId))
.orderByProcessInstanceStartTime().desc()
.list();
//HistoricProcessInstance instance = list.get(0);
List<JSONObject> flows = new ArrayList<>();
// 得到各个流程实例到了哪里
for (HistoricProcessInstance instance : startProcessList) {
// 该流程的节点信息
//List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().processInstanceId(instance.getId()).list();
ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(instance.getId()).singleResult();
if (pi == null) {
return;
}
// task[id=2508,name=事业部部长审核] 节点任务信息
String applyId = runtimeService.getVariable(pi.getId(), "applyId").toString();
System.out.println("========"+applyId);
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
JSONObject jsonObject = new JSONObject();
//List<ProcessDefinition> list1 = repositoryService.createProcessDefinitionQuery().deploymentId(instance.getDeploymentId()).list();
System.out.println("==========");
//jsonObject.put("apply_id", list1.get(0).getName());
//jsonObject.put("flow_id", task.getProcessInstanceId());
jsonObject.put("flow_id", instance.getId());
jsonObject.put("description", instance.getDescription()); // 流程实例的描述
jsonObject.put("currentNode", task.getName()); // 当前节点名字
jsonObject.put("createTime", instance.getStartTime()); // 流程实例创建时间
flows.add(jsonObject);
}
for (JSONObject flow : flows) {
System.out.println(flow);
}
}
/**
* 查看用户的代办任务
*/
@RequestMapping("/getUserTask")
public void getUserTask(@RequestParam("userId") int userId) { // 默认是莫珊珊的代办
PcUser user = userMapper.getUserById(userId);
TaskService taskService = processEngine.getTaskService();
HistoryService historyService = processEngine.getHistoryService();
PcGroup group = groupMapper.getGroupById(user.getGroupId());
List<Task> userTasks = taskService.createTaskQuery().taskAssignee(userId +"-"+user.getName()).list();
//List<Task> tasks = taskService.createTaskQuery().list();
List<Task> groupTasks = taskService.createTaskQuery().taskCandidateGroup(userId +"-"+group.getName()).list();
for (Task task : userTasks) {
HistoricProcessInstance hi = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.singleResult();
String startUserId = hi.getStartUserId();
PcUser userById = userMapper.getUserById(Integer.valueOf(startUserId));
System.out.println("------------------");
System.out.println("个人任务" + task.getId() + ":用户为" + userById.getName() + "发起了:" + taskService.getVariables(task.getId()).get("flowName"));
}
for (Task task : groupTasks) {
HistoricProcessInstance hi = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.singleResult();
String startUserId = hi.getStartUserId();
PcUser userById = userMapper.getUserById(Integer.valueOf(startUserId));
System.out.println("------------------");
System.out.println("部门任务" + task.getId() + ":" + userById.getName() + "发起了:" + taskService.getVariables(task.getId()).get("reason"));
}
}
有两种方式
一种是项目集成flowable-ui或者bpmn.js
另一种就是使用代码实现,查看 5其他 里面的 部署流程的6种方式
代码自定义的前端页面可以参考这个
/**
* 自定义流程
*/
@Test
public void createProcess() {
ProcessEngine processEngine = configuration.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
/*Scanner scanner= new Scanner(System.in);
System.out.println("流程名称?");
String processName = scanner.nextLine();
System.out.println("审核节点1名称?");
Integer node1Name = Integer.valueOf(scanner.nextLine());
System.out.println("审核节点1审核对象?");
String approve1 = scanner.nextLine();
System.out.println("审核节点2名称?");
Integer node2Name = Integer.valueOf(scanner.nextLine());
System.out.println("审核节点2审核对象?");
String approve2 = scanner.nextLine();*/
SequenceFlow flow1 = new SequenceFlow(); // SequenceFlow 是连接线
flow1.setId("flow1");
flow1.setName("开始节点->任务节点1");
flow1.setSourceRef("start1");
flow1.setTargetRef("userTask1"); // 下一节点id
// 任务节点1->任务节点2
SequenceFlow flow2 = new SequenceFlow();
flow2.setId("flow2");
flow2.setName("任务节点1->任务节点2");
flow2.setSourceRef("userTask1");
flow2.setTargetRef("userTask2");
// 任务节点1->任务节点2
SequenceFlow flow3 = new SequenceFlow();
flow3.setId("flow3");
flow3.setName("任务节点2->结束节点");
flow3.setSourceRef("userTask2");
flow3.setTargetRef("endEvent");
String resource = "customize_test444";
// 声明BpmnModel对象
BpmnModel bpmnModel = new BpmnModel();
// 声明Process对象 一个BpmnModel可以包含多个Process对象
Process process = new Process(); // <process id就是流程的key name>
process.setId("TestProcess444"); // 流程的key
process.setName("test_process444"); //流程的名字
process.setDocumentation("自定义流程测试使用444"); //描述信息
// 开始节点的封装
StartEvent start = new StartEvent();
start.setName("开始节点");
start.setId("start1");
start.setOutgoingFlows(Arrays.asList(flow1));
// 任务节点1
UserTask userTask1 = new UserTask();
userTask1.setName("任务节点1");
userTask1.setId("userTask1");
userTask1.setAssignee("${assignee1}");
userTask1.setIncomingFlows(Arrays.asList(flow1));
userTask1.setOutgoingFlows(Arrays.asList(flow2));
// 任务节点2
UserTask userTask2 = new UserTask();
userTask2.setName("任务节点2");
userTask2.setId("userTask2");
userTask2.setAssignee("${assignee2}");
userTask2.setIncomingFlows(Arrays.asList(flow2));
userTask2.setOutgoingFlows(Arrays.asList(flow3));
// 结束节点
EndEvent endEvent = new EndEvent();
endEvent.setName("结束节点");
endEvent.setId("endEvent");
endEvent.setIncomingFlows(Arrays.asList(flow3));
// 将所有的FlowElement添加到process中
process.addFlowElement(start);
process.addFlowElement(flow1);
process.addFlowElement(userTask1);
process.addFlowElement(flow2);
process.addFlowElement(userTask2);
process.addFlowElement(flow3);
process.addFlowElement(endEvent);
bpmnModel.addProcess(process);
Deployment deploy = repositoryService.createDeployment()
.addBpmnModel(resource+".bpmn20.xml", bpmnModel).name("自定义测试流程444")
.deploy();
System.out.println();
}
public class TenMinuteTutorial {
public static void main(String[] args) {
// 创建Flowable流程引擎(spring项目可以直接注入使用)
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();
// 获取Flowable服务
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
// 部署流程定义
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
// 启动流程实例
String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();
// 获取第一个任务
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
for (Task task : tasks) {
System.out.println("Following task is available for accountancy group: " + task.getName());
// 申领任务
taskService.claim(task.getId(), "fozzie");
}
// 验证Fozzie获取了任务
tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
for (Task task : tasks) {
System.out.println("Task for fozzie: " + task.getName());
// 完成任务
taskService.complete(task.getId());
}
System.out.println("Number of tasks for fozzie: "
+ taskService.createTaskQuery().taskAssignee("fozzie").count());
// 获取并申领第二个任务
tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
System.out.println("Following task is available for management group: " + task.getName());
taskService.claim(task.getId(), "kermit");
}
// 完成第二个任务并结束流程
for (Task task : tasks) {
taskService.complete(task.getId());
}
// 验证流程已经结束
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
}
}
可以看出这个业务流程太简单了,不能实际使用。但只要继续学习Flowable中可用的BPMN 2.0结构,就可以通过以下元素增强业务流程:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--flowable工作流依赖-->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.3.0</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
</dependencies>
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/flowable-spring-boot?characterEncoding=UTF-8
username: root
password: root
flowable:
#关闭定时任务JOB
async-executor-activate: false
也可以配置日志 5.2.2.5 添加日志文件
会生产一大堆flowable需要的表
flowable-ui 是一个画bpmn的画板页面,假如你的流程全靠代码生产的话那就不需要这个;
参考网址:
https://cloud.tencent.com/developer/article/1979771
https://tkjohn.github.io/flowable-userguide/#flowableUIApps
https://blog.csdn.net/qq_37907566/article/details/119914467
下载
链接:https://pan.baidu.com/s/1olsZ4MuklF9RyJDhtFQ-lg
提取码:5406
链接:https://pan.baidu.com/s/1LtUVOKRnckTL3YfR5x85_Q
提取码:meot
第一步:下载Tomcat:https://tomcat.apache.org/download-80.cgi 官网下载后解压缩到非中文目录即可,然后是下载FlowableUI的文件,在Flowable6.6之后把FlowableUI中提供的四个功能合并到了一起。
第二步:然后把解压缩后的两个war包拷贝到Tomcat的解压缩的webapps目录下
第三步:启动Tomcat服务,执行startup.bat文件
如果启动中出现乱码修改Tomcat的conf目录下的 logging.properties 文件中的编码
tomcat启动失败:https://blog.csdn.net/LC_Liangchao/article/details/124499490
在浏览器中访问 http://localhost:8080/flowable-ui, 默认的账号密码是 admin/test
Flowable提供了几个web应用,用于演示及介绍Flowable项目提供的功能:
在建模器应用程序中创建一个bpmn模型
在应用程序中创建应用,发布部署自己的流程
任务应用程序可以启动流程
点击显示图,可以查看流程进度
前面我把usertask指定分配给了流程发起来,所以需要我们自己去审批
在Flowable6.4及之前在FlowableUI中都是分成了几个模块
starter | 描述 |
---|---|
flowable-modeler | 让具有建模权限的用户可以创建流程模型、表单、选择表与应用定义。 |
flowable-idm | 身份管理应用。为所有Flowable UI应用提供单点登录认证功能, 并且为拥有IDM管理员权限的用户提供了管理用户、组与权限的功能 |
flowable-task | 运行时任务应用。提供了启动流程实例、编辑任务表单、完成任务,以及查询流程实例与任务的功能。 |
flowable-admin | 管理应用。让具有管理员权限的用户可以查询BPMN、DMN、Form及Content引擎, 并提供了许多选项用于修改流程实例、任务、作业等。管理应用通过REST API连接至引擎, 并与Flowable Task应用及Flowable REST应用一同部署。 |
flowable-rest | Flowable页面包含的常用REST API |
在当前最新的6.7.2中已经把这几个模块都整合到了一个war包中就大大的简化了我们整合的步骤了。
首先我们需要从官方的GitHub下载最新的源码文件。地址是:https://github.com/flowable/flowable-engine/releases/tag/flowable-6.7.2
下载成功后,解压缩获取里面的flowable-ui的源码,并可以拷贝出来,放到我们独立的工作空间
后拷贝到对应的工作空间,就可以打开运行了。
由于我的8080端口已经占用,在properties改成了8085,也可以修改默认数据库的类型
启动项目,浏览器输入http://localhost:8085/flowable-ui/#/ 输入admin/test
第一步:pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.flowable</groupId>
<artifactId>flowable-ui-parent</artifactId>
<version>6.7.2</version>
</parent>
<groupId>com.boge.flowable</groupId>
<artifactId>boge-flowable-ui</artifactId>
<version>6.7.2</version>
<name>BogeFlowableUI</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-task</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-admin</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-idm</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-ui-modeler</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
</dependency>
<!-- DATABASE -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.javacrumbs.json-unit</groupId>
<artifactId>json-unit-assertj</artifactId>
<scope>test</scope>
</dependency>
<!-- LDAP dependencies needed for testing purposes -->
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- BUILD -->
<build>
<finalName>flowable-ui</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
<profiles>
<profile>
<id>h2mem</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<arguments>
<argument>--com.sun.management.jmxremote.port=4001</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>postgresql</id>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<arguments>
<argument>--spring.datasource.driver-class-name=org.postgresql.Driver</argument>
<argument>--spring.datasource.url=jdbc:postgresql://localhost:5432/flowable</argument>
<argument>--spring.datasource.username=flowable</argument>
<argument>--spring.datasource.password=flowable</argument>
<argument>--com.sun.management.jmxremote.port=4000</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>mysql</id>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<arguments>
<argument>--spring.datasource.driver-class-name=com.mysql.jdbc.Driver</argument>
<argument>--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/flowable?characterEncoding=UTF-8&serverTimezone=UTC</argument>
<argument>--spring.datasource.username=flowable</argument>
<argument>--spring.datasource.password=flowable</argument>
<argument>--com.sun.management.jmxremote.port=4001</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
第二步: flowable-default.properties & spring.factories
第三步: 读取配置
把flowable-ui的启动类和配置类复制过去,修改一下
然后取出FlowableUiApplication的mian方法删除,且@SpringBootApplication改成@Configuration (表示不作为启动类)
在新项目的启动类上加上扫描注解
此时启动,可以看到还是h2数据库,且可以访问上述网址
第四步: 切换数据库
把flowable-default.properties里面的端口和数据库的设置#注释掉,
直接在项目的application.yml里面配置
pom.xml里面插入
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
yml
server:
port: 9001
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/flowable?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
第五步: 添加日志文件
创建log4j.properties
log4j.rootLogger=DEBUG, CA
log4j.appender.CA=org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout=org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
此时启动项目
账号密码
注意-前缀问题
flowable-default.properties 文件里面定义了一个前缀/flowable-ui
在新项目的resources下面创建META-INF, 复制flowable-ui的spring.factories文件
使用文件流stream,部署工作流
使用classpath,部署工作流
使用压缩文件格式(zip)ZipStream,部署工作流
使用纯文本格式text,部署工作流
使用字节数组,部署工作流
使用动态创建的流程图,部署工作流
使用文件流stream,部署工作流
上传的文件必须是XXXbpmn20.xml格式的。resourceName的后缀格式必须是XXXbpmn20.xml。
/**【1】使用文件流stream,部署工作流*/
//假设不是null,忽略此处赋值为null。multipartFile对象是文件上传时用到的对象
MultipartFile multipartFile = null;
//resourceName是文件的全名称(包括文件后缀)
String resourceName = multipartFile.getOriginalFilename();
Deployment deployStream = repositoryService.createDeployment()
.addInputStream(resourceName, multipartFile.getInputStream())
.deploy();
classpath是指项目的classes路径。如下图:
/**【2】使用classpath,部署工作流*/
Deployment deployClasspath = repositoryService.createDeployment()
.name("经理审批")
.addClasspathResource("processes/经理审批.bpmn20.xml")
.deploy();
/**【3】使用压缩文件格式(zip)ZipStream,部署工作流 上传的文件必须是zip压缩文件。resourceName的后缀格式必须是XXXbpmn20.xml。*/
//假设不是null,忽略此处赋值为null。multipartFile对象是文件上传时用到的对象
MultipartFile multipartFile = null;
//resourceName是文件的全名称(包括文件后缀)
String resourceName = multipartFile.getOriginalFilename();
Deployment deployZipStream = repositoryService.createDeployment()
.name(resourceName)
.addZipInputStream(new ZipInputStream(multipartFile.getInputStream()))
.deploy();
/**【4】使用纯文本格式text,部署工作流 resourceName的后缀格式必须是XXXbpmn20.xml。flowableText文本必须符合流程图的xml文件规范(格式)。*/
//resourceName是文件的全名称(包括文件后缀)
String resourceName = "XXXXXbpmn20.xml";
String flowableText = ".....";
Deployment deployText = repositoryService.createDeployment()
.addString(resourceName, flowableText)
.deploy();
/**【5】使用字节数组,部署工作流(这里举的例子的文件上传的方式获取字节数组) 这种方式部署工作流,需要一个字节数组。resourceName的后缀格式必须是XXXbpmn20.xml。*/
//假设不是null,忽略此处赋值为null。multipartFile对象是文件上传时用到的对象
MultipartFile multipartFile = null;
//resourceName是文件的全名称(包括文件后缀)
String resourceName = multipartFile.getOriginalFilename();
byte[] bytes = multipartFile.getBytes();
Deployment deployByte = repositoryService.createDeployment()
.addBytes(resourceName, bytes)
.deploy();
使用动态创建的流程图,部署工作流
这种方式部署工作流,需要自己创建(new)流程图的每个节点,每一条线,每个网关等等,然后将这些对象关联,最后才能部署。这种方式部署比较麻烦,不太建议使用。
/**【6】使用动态创建的流程图,部署工作流*/
BpmnModel bpmnModel = new BpmnModel();
Process process = new Process();
bpmnModel.addProcess(process);
Deployment deployBpmnModel = repositoryService.createDeployment()
.addBpmnModel("动态流程.bpmn20.xml", bpmnModel)
.deploy();
/**
* 自定义流程
*/
@Test
public void createProcess() {
ProcessEngine processEngine = configuration.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
/*Scanner scanner= new Scanner(System.in);
System.out.println("流程名称?");
String processName = scanner.nextLine();
System.out.println("审核节点1名称?");
Integer node1Name = Integer.valueOf(scanner.nextLine());
System.out.println("审核节点1审核对象?");
String approve1 = scanner.nextLine();
System.out.println("审核节点2名称?");
Integer node2Name = Integer.valueOf(scanner.nextLine());
System.out.println("审核节点2审核对象?");
String approve2 = scanner.nextLine();*/
SequenceFlow flow1 = new SequenceFlow(); // SequenceFlow 是连接线
flow1.setId("flow1");
flow1.setName("开始节点->任务节点1");
flow1.setSourceRef("start1");
flow1.setTargetRef("userTask1"); // 下一节点id
// 任务节点1->任务节点2
SequenceFlow flow2 = new SequenceFlow();
flow2.setId("flow2");
flow2.setName("任务节点1->任务节点2");
flow2.setSourceRef("userTask1");
flow2.setTargetRef("userTask2");
// 任务节点1->任务节点2
SequenceFlow flow3 = new SequenceFlow();
flow3.setId("flow3");
flow3.setName("任务节点2->结束节点");
flow3.setSourceRef("userTask2");
flow3.setTargetRef("endEvent");
String resource = "customize_test444";
// 声明BpmnModel对象
BpmnModel bpmnModel = new BpmnModel();
// 声明Process对象 一个BpmnModel可以包含多个Process对象
Process process = new Process(); // <process id就是流程的key name>
process.setId("TestProcess444"); // 流程的key
process.setName("test_process444"); //流程的名字
process.setDocumentation("自定义流程测试使用444"); //描述信息
// 开始节点的封装
StartEvent start = new StartEvent();
start.setName("开始节点");
start.setId("start1");
start.setOutgoingFlows(Arrays.asList(flow1));
// 任务节点1
UserTask userTask1 = new UserTask();
userTask1.setName("任务节点1");
userTask1.setId("userTask1");
userTask1.setAssignee("${assignee1}");
userTask1.setIncomingFlows(Arrays.asList(flow1));
userTask1.setOutgoingFlows(Arrays.asList(flow2));
// 任务节点2
UserTask userTask2 = new UserTask();
userTask2.setName("任务节点2");
userTask2.setId("userTask2");
userTask2.setAssignee("${assignee2}");
userTask2.setIncomingFlows(Arrays.asList(flow2));
userTask2.setOutgoingFlows(Arrays.asList(flow3));
// 结束节点
EndEvent endEvent = new EndEvent();
endEvent.setName("结束节点");
endEvent.setId("endEvent");
endEvent.setIncomingFlows(Arrays.asList(flow3));
// 将所有的FlowElement添加到process中
process.addFlowElement(start);
process.addFlowElement(flow1);
process.addFlowElement(userTask1);
process.addFlowElement(flow2);
process.addFlowElement(userTask2);
process.addFlowElement(flow3);
process.addFlowElement(endEvent);
bpmnModel.addProcess(process);
Deployment deploy = repositoryService.createDeployment()
.addBpmnModel(resource+".bpmn20.xml", bpmnModel).name("自定义测试流程444")
.deploy();
System.out.println();
}
查看所有已部署的流程
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().list();
for (ProcessDefinition result : list) {
System.out.println("=========================================================");
System.out.println("查看当前已有流程名:"+result.getName()); // 来自act_re_procdef
System.out.println("查看当前已有流程部署id:"+result.getDeploymentId()); // id来自act_re_deployment
System.out.println("查看当前已有流程来源:"+result.getDerivedFrom());
System.out.println("查看当前已有流程描述:"+result.getDescription()); // 来自act_re_procdef
}
//ProcessDefinition result = repositoryService.createProcessDefinitionQuery().deploymentId("181f2472-d64e-11ec-b0c4-005056c00001").singleResult(); // id来自act_re_deployment
自定义审核流程(bpmn)
前端可以集成bpmn.js既可以去绘图了,只需要把用户绘制出来的流程图传给后端,然后流程部署吧节点信息存储进数据库就行了
bpmn文件保存在项目磁盘中
可以使前端传过来的bpmn文件
ProcessEngine processEngine=getProcessEngine();
final String PROCESSID ="process01";
final String PROCESSNAME ="测试01";
// 得到流程定义
List<ProcessDefinition> list = processEngine.getRepositoryService().createProcessDefinitionQuery().
orderByProcessDefinitionVersion().asc().
deploymentId(PROCESSID).list();
ProcessDefinition processDefinition = list.get(0);
//得到流程定义对应流程图
InputStream processDiagram = processEngine.getRepositoryService().getProcessDiagram(processDefinition.getId());
FileUtils.copyInputStreamToFile(processDiagram, new File("/deployments/"+PROCESSID+".png"));
// 得到部署
InputStream processBpmn = processEngine.getRepositoryService().getResourceAsStream(processDefinition.getDeploymentId(), PROCESSID+".bpmn");
FileUtils.copyInputStreamToFile(processBpmn,new File("/deployments/"+PROCESSID+".bpmn"));
解决部署后act_re_procdef 表无数据 - 查询流程为空
是查询流程的时候查询的是act_re_procdef, 部署流程的时候假如没有往这里面查数据就会导致为空
camunda流程部署时,有个校验resourceName必须以"bpmn20.xml", "bpmn"结尾,否则校验不通过,不会向表act_re_procdef 插入数据。但是act_re_deployment和act_ge_bytearray 表会有数据。
@Test
public void deploy() throws IOException {
String path="D:\\logs\\123.bpmn";
FileInputStream fileInputStream = new FileInputStream(path);
Deployment deploy = repositoryService.createDeployment()
.addInputStream("测试流程.bpmn", fileInputStream)
.name("测试流程3")
.deploy();
fileInputStream.close();
log.info("流程部署成功,id:{},name:{}",deploy.getId(),deploy.getName());
}
此时要注意bpmn.js画图的时候要勾选上,要不然会报错的,还有吧name填写上表示这个流程的名称。
启动类设置flowable的数据库
正常来说会自动配置,但是就怕会被别人写的框架拦截
public static void main(String[] args) {
ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
.setJdbcDriver("com.mysql.jdbc.Driver");
.setJdbcUsername("root");
.setJdbcPassword("root");
.setJdbcUrl("jdbc:mysql://localhost:3306/flowable?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai");
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngine processEngine = cfg.buildProcessEngine();
}
假如数据库有flowable的表了会报错
Table ‘flowable.act_ge_property’ doesn’t exist
需要在yml的数据库配置中加nullCatalogMeansCurrent=true
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/flowable?nullCatalogMeansCurrent=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.jdbc.Driver
bpmn文件的位置
不是NCName的有效值
我是用代码画BPMN文件时,将用户任务的ID设置为了数字”1”,在部署时,就提示了这个错误。其实是因为xml中或类xml的文件中有些关键属性的值不符合NCName命名规范。
NCName 要以字母或下划线 () 字符开头,后接 XML 规范中允许的任意字母、数字、重音字符、变音符号、句点 (.)、连字符 (-) 和下划线 () 的组合。
不能直接返回task任务信息
flowable中的Task对象(它的实现类是一个懒加载对象)
flowable工作流获取任务Task返回错误
需要自己重新封装task数据
for (Task task : groupTasks) {
/* HistoricProcessInstance hi = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(task.getProcessInstanceId())
.singleResult();*/
ProcessInstance hi = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult();
String startUserId = hi.getStartUserId();
PcUser userById = userMapper.getUserById(Integer.valueOf(startUserId));
JSONObject tempGroupJson = new JSONObject();
tempGroupJson.put("startBy", userById.getName());
tempGroupJson.put("flowName", taskService.getVariables(task.getId()).get("flowName"));
tempGroupJson.put("taskId", task.getId());
tempGroupJson.put("content", applyMapper.getApply(Integer.valueOf(taskService.getVariables(task.getId()).get("applyId").toString())));
groupArray.add(tempGroupJson);
//System.out.println("------------------");
// System.out.println("部门任务,任务id" + task.getId() + ":" + userById.getName() + "发起了:" + taskService.getVariables(task.getId()).get("reason"));
//System.out.println("=====任务详情:"+ applyMapper.getApply(Integer.valueOf(taskService.getVariables(task.getId()).get("applyId").toString())));
}
可能是对应的这些表里面的字段为1,改为0就好了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。