加签的概念
思路:
- 直接需改模板, 在模板中添加节点以及连线, 并修改实例的走向;
- 直接修改路程定义对应的缓存数据, 不修改模板, 新增节点与当前需要加签的实例挂钩.
1. 方案一
1.1. 实现方式
- 找到当前实例对象的模板数据
- 在模板数据的基础上添加新节点以及修改连线, 并更新数据库中的模板.
- 更新模板对应的流程定义缓存, 必须更新缓存否则加签的节点不会生效. 因为 Activiti 在查找流程定义的时候会先尝试从缓存中进行获取.
- 完成新增节点额任务后, 再把新增节点以及连线删除, 即还原模板.
1.2. 优缺点
- 模板是共享的, 因此修改模板就会将所有运行实例对象的模板修改.
- 实例间应该相互独立, 不能让针对某个实例的加签影响到其他实例.
- 修改模板容易导致当前实例影响其他实例, 因此该方案不可取;
2. 方案二
- 模板是共享的, 因此不能修改模板, 否则会影响其他实例.
- 也不需要修改原有流程的入线即出线, 不修改原有的走向.
由于流程运转的过程中, 需要实时的获取该实例对应的模板数据才能知道应该如何运转.
- 从流程定义缓存中获取模板数据
- 如果流程定义缓存丢失, 则需要重新执行模板的解析工作并补充到流程定义缓存中.
因此可以直接修改流程定义缓存数据.
2.1. 思路
- 在流程缓存中添加一个任务节点, 并未任务节点添加出线信息, 出线连接的是需要到达的目标节点.
- 添加的目标节点并没有入线, 并不会影响其他实例, 因此其他流程没有机会走到该节点.
- 加签完成后触发执行实例走到新增的任务节点, 这样当前实例就按照最新的路线进行运转;
- 如果当前节点在加签后不想直接运转到最新节点, 则可以复制一个当前节点, 继续让流程运转.
- 加签的最终目的是让实例按照最新的路线走, 与模板中规划的路线脱离关系.
2.2. 引入的问题
- 新增的任务节点及连线如何存储
- 流程定义缓存如何修改
- 加签的节点以及连线信息如何持久化
- 如果我们重新修改的流程定义缓存丢失, 引擎依然会解析数据库中保存的原有定义, 新增的节点并没有持久化到 DB
- 流程实例结束后, 当前加签的节点以及连线如何删除.
1 | public void testAddOneTask(String taskId, String targetActivityId) { |
但该方法产生跳转后的新 task 仍然无法提交, 会报一下错误:
17:59:18.971 [main] [ERROR] Error while closing command context o.a.e.i.i.CommandContext.logException:122
org.activiti.engine.ActivitiException: Programmatic error: no current flow element found or invalid type: null. Halting.
at org.activiti.engine.impl.agenda.TriggerExecutionOperation.run(TriggerExecutionOperation.java:49)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:73)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:57)
at org.activiti.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:42)
at org.activiti.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:48)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:63)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:29)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:44)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:39)
at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:182)
at org.destiny.activiti.addsign1.ClientTest.complete(ClientTest.java:56)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.activiti.engine.test.ActivitiRule$1.evaluate(ActivitiRule.java:116)
出线错误的原因是加签方法执行完成后, 缓存中的数据已经被释放, complete 的时候无法继续, 需要在 complete 之前重新向缓存中添加之前的节点和连线
在修改流程定义缓存而不修改模板的实现中, 我们需要一个额外的持久化方式去实现加签部分的持久化
2.3. 持久化加签现场数据
1 | CREATE TABLE `ACT_ADD_SIGN` ( |
以及对应的 Mapper 文件:
1 | public interface AddSignMapper { |
2.4. 模型定义
2.4.1. AddSign
1 |
|
2.4.2. TaskModel
1 |
|
2.4.3. TmpActivityModel
1 |
|
2.5. 加签功能实现类
1 |
|
2.6. 测试代码
部署流程后, 通过测试代码
1 |
|
执行结束后我们就已经将 destinyD -> destinyE
两个节点加签到 destinyA
之后, destinyB
之前.
select NAME_ from ACT_RU_TASK where PROC_INST_ID_ = '17504';
的执行结果已经变成: destinyD
现在已经完成了加签的一部分代码, 但此时的任务是不能被正确提交的, 会报如下异常:
org.activiti.engine.ActivitiException: Programmatic error: no current flow element found or invalid type: null. Halting.
at org.activiti.engine.impl.agenda.TriggerExecutionOperation.run(TriggerExecutionOperation.java:49)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:73)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:57)
at org.activiti.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:42)
at org.activiti.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:48)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:63)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:29)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:44)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:39)
at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:182)
at org.destiny.activiti.addsign1.ClientTest.complete(ClientTest.java:61)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.activiti.engine.test.ActivitiRule$1.evaluate(ActivitiRule.java:116)
2.7. 流程引擎启动时从 DB 加载流程定义信息
当流程引擎启动的时候, 如果 ACT_ADD_SIGN
表有数据, 就需要将对应的加签现场数据保存并添加到缓存中.