activiti5入门(四)流程图之六大构成元素
1、Events事件2、Tasks任务(任务无法删除,只能结束。执行任务只要taskId即可,不需要当前用户,因为不排除用户离职的情况)3、Gateways网关4、Container容器5、Connection连接6、Artifacts说明-----------------------------------------------------------仅涉及Task
1、Events事件
2、Tasks任务(任务无法删除,只能结束。执行任务只要taskId即可,不需要当前用户,因为不排除用户离职的情况)
3、Gateways网关
4、Container容器
5、Connection连接
6、Artifacts说明
-----------------------------------------------------------
仅涉及Tasks任务的流程图实例:
根据用户查询当前任务:
String userId = (String) request.getSession().getAttribute("userId");这个userid和流程图xml格式中的 activiti:assignee一致
List<Task> taskLists = taskService.createTaskQuery().taskAssignee(userId).list();
如果我们在流程图里没有指定用户,而是用${userId}指定,那么最好每个usertask都要用不同的${xxx},因为如果相同,就说明这个userTask是同一个人
那我们怎么用起来呢?
在流程启动的时候就要把它用进去:
Map<String, Object> map = new HashMap<>();
map.put("userId", "admin1");map.put()...
runtimeService.startProcessInstanceByKey(pdKey,bussinessId,map);
这样就在代码中指定了用户,这样每个流程实例都可能会有不同的人
注意,这里使用了bussinessId,这个值的作用,可以理解为它是另外一张业务表(非activiti表)的某个主键字段,这个字段会被很多其他业务表引用,当我们查询act_hi_procinst表时就会用到BUSINESS_KEY_这个字段。不用查,用processInstence.getBusinessKey();即可
用户执行任务:
String taskId = request.getParameter("taskId");
taskService.complete(taskId);
可见只要知道任务id即可,不关心真正的用户是谁
每执行一次任务,act_ru_task表中不会增加一条数据,而是更新一条数据,且把ID都更新了,那原来那条数据保存在哪里?保存在act_hi_taskinst中。
----------------------------------
ACT_RU_EXECUTION表中的executionID一般与流程实例ID相同,所以流程实例有多少条,他就有多少条,当一个流程走到某个节点,此时这个节点既有usertask又有消息边界事件,那么ACT_RU_EXECUTION中会多出一个一条数据,假设ID为0001,这条数据的父ID是该流程,在act_ru_task表和act_ru_event_subscr都会出现一条数据,表中的执行ID不再是流程ID,而是0001。正常情况下,执行任务ID和流程ID都是一致的。当存在不知道要走哪个时,就会多出一条数据,即流程ID的延伸流程。出现这种情况如何获取执行ID?
Execution execution = runtimeService.createExecutionQuery().messageEventSubscriptionName("全局消息名").singleResult();
runtimeService.messageEventReceived("全局消息名", execution.getId());//相当于执行了消息边界事件,没有执行usertask。
--------------------------
根据流程实例id查询当前任务(流程实例理解为譬如一张请假单)
List<Task> taskLists = taskService.createTaskQuery().processInstanceId(piId).list();
-------------------
查询所有历史流程实例,涉及act_hi_procinst表
HistoryService historyService = pe.getHistoryService();
List<HistoricProcessInstance> hpiLists = historyService.createHistoricProcessInstanceQuery().orderByProcessInstanceId().asc().list();
根据某历史流程实例id查询对应的历史任务(包括没有执行完的历史流程)涉及act_hi_taskinst表
HistoryService historyService = pe.getHistoryService();
List<HistoricTaskInstance> htiLists = historyService.createHistoricTaskInstanceQuery().processInstanceId(hpiId).list();
----------------------------------------------------
当usertask的用户为候选人时
例如xml中为 activiti:candidateUsers="xxx1,xxx2,xxx13" : 指定候选人.(拉模式),
那么在用流程实例查询时是找不到指定人的,一定要有个指定人去认领任务才有用
TaskQuery tq = taskService.createTaskQuery();
List<Task> candidateUserTaskLists = tq.taskCandidateUser(userId).list();
// 迭代任务
for (Task task : candidateUserTaskLists){
// 领取任务
taskService.claim(task.getId(), userId);//领取任务时,也就是所谓的任务分配时,因为这个时候有assignee了
}
// 根据任务的处理人查询任务
List<Task> taskLists = taskService.createTaskQuery().taskAssignee(userId).list();//注意这里又要去重新获取一把任务查询,因为不重新获取,查的还是老数据,有种默认可重复读的感觉
-----------------------------------------------
当usertask的用户为候选组,且用了变量${group}时,那么在启动流程实例时,
map.put("group", "g1");//这里g1用字符串仅表示组名,不代表组员。因为业务中最后可能是根据用户名查找到组名,然后通过组名查找到这个组的所有任务
runtimeService.startProcessInstanceById(pdId, map);
查询组任务:
String userId = (String) request.getSession().getAttribute("userId");
Map<String, Object> map = new HashMap<>();
String[] arr = { "a", "b", "c" };
List<String> asList = Arrays.asList(arr);
map.put("g1", asList);//这里只是模拟表示g1这个组有abc三个用户
String group = null;
for (Entry<String, Object> entry : map.entrySet()) {
List<String> list = (List<String>) entry.getValue();
if (list.contains(userId)) {//根据用户找到用户所在组
group = entry.getKey();
}
}
List<Task> taskLists1 = new ArrayList<>();
if (group != null) {
taskLists1 = taskService.createTaskQuery().taskCandidateGroup(group).list();//根据用户所在组名找到所有任务
}
for (Task task : taskLists1) {
taskService.claim(task.getId(), userId);//第一个登录的用户将认领任务,同时这个组就不在有这个任务,即taskCandidateGroup(group).list()将没有任务
}
List<Task> taskLists2 = taskService.createTaskQuery().taskAssignee(userId).list();// 当用户认领后,那么act_ru_task中这个任务的Assignee就是他了,它就能查到。没有领取任务的用户就查不到任务,除非是其它非组任务
request.setAttribute("taskLists", taskLists2);
request.getRequestDispatcher("/userTasks.jsp").forward(request, response);
-----------------------------------------------------------
userTask的监听器
总共有4种:
create表示当创建任务时触发。
assignment表示当任务被分配后触发。
complete表示当任务完成后触发。
activiti中的表达式是${}形式,也有#{},后者在监听器中的表达式中有使用,也叫流程变量,在一个流程中有效。在启动流程时或在该usertask之前要做好初始化。放map中。
要使用监听器,我们必须在流程图中找到usertask里面的listener,然后新建,指定我们自定义的监听器。我们写的监听器要实现TaskListener。
以创建时定时器为例子:
public class CreateTaskListener implements TaskListener {
private Expression name;//这个变量是在流程图中指定的,可以用来传入到java代码中
private Expression age;
@Override
public void notify(DelegateTask delegateTask) {//DelegateTask 称为委派任务
System.out.println("事件:" + delegateTask.getEventName());
System.out.println(name.getValue(delegateTask));//使用getValue的方法获取流程图中监听器的变量
System.out.println(age.getValue(delegateTask));
// 分配任务
String userId = (String) name.getValue(delegateTask);
delegateTask.setAssignee(userId);//分配任务到某个人
// 分配完成后,分配监听器开始启动
}
delegateTask的api有很多,比如获取所有流程变量等。
除了java类型的定时器,还有#{}表达式,比如#{user.xxx()}就是一个。我们创建一个User类实现Serializable,因为要存数据库,所以要实现序列化。#{user.xxx()}里面是能传参数的,比如#{user.xxx(‘james’)}。他也分create、create和complete。它是怎么找到user的。一样在启动流程实例时,使用map,map.put("user", new User());
除了,java,expression,还有与spring连用的delegate expression委托表达式,以后会说。
usertask不仅仅有task listener,也有execution listener,与task listener的区别是,后者只有两个阶段,start和end,start会比creat还早。另外实现的监听接口不一样,后者是ExecutionListener。
监听器中获取引擎的方式:delegatetask.getExecution().getEngineServices()就是引擎(ProcessEngine)
taskUser中的表单我们很少用,它要配合监听器使用,为的是在监听器中能获取表单服务。但表单一般我们都有自己的表。
taskuser里面还有一个多实例,即表示一个任务必须多个用户都做好后才能走到下一步。
有几个选项:
Sequential为true表示有顺序,必须一个一个做;为false表示无顺序,用户不必等前一个用户。
collection是一个变量名,如userList。在流程启动时赋值,如
List<String> userList = new ArrayList<>();
userList.add("admin1");
userList.add("admin2");
userList.add("admin3");
userList.add("admin4");
userList.add("admin5");
map.put("userList", userList);
elementVariable是一个变量名(必须的),在assignee用${admin}这个表示后,在multiInstance中就能用admin代表${admin}了
completionCondition条件:比如有5个人完成,我现在只有三个人完成,我也算他任务完成,那就要用${nrOfCompletedInstances/nrOfInstances>=0.6}表示。其中nrOfCompletedInstances和nrOfInstances是activiti在act_ru_variable中生成的字段名,表示已完成实例和总实例。在流程图中我们不需要指定Loop,因为collection里面就能知道循环次数
-----------------------------------------------------------------------
其它任务类型:
ScriptTask脚本任务(自动触发),写一段javascript脚本
ServiceTask服务任务(自动触发),写一个类实现ServiceTaskDelegate,和自定义监听器实现监听器接口类似
ManualTask手工任务(自动触发)
ReceiveTask接收任务(信号触发).这4种我们都很少用
邮件任务(挺有用):例子
依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.4</version>
</dependency>
在部署前调用ProcessEngineConfiguration的
.setMailServerDefaultFrom("xxx@dzkf.com").setMailServerHost("dzkf.com")
.setMailServerUsername("xxx").setMailServerPassword("xxx");
在流程图中选择mail task,在配置中用流程变量指定发送者,接受者等,如${to},${from},${subject}(主题)。它的正文有html格式或者普通文本格式
最后在启动流程实例时
map.put("to", "zzz@dzkf.com");
map.put("from", "xxx@dzkf.com");
map.put("subject", "邮件任务");
即可。
-------------------------------------------
Events事件
用得最多的是Start Event,可以在流程图中定义发起人,但我们可以不用这样做,我们一般将第一个任务看成是流程发起人
TimerStartEvent定时开始事件,它的定时时间由activiti特殊规定,但在使用前,要调用ProcessEngineConfiguration.setJobExecutorActivate(true)激活定时任务
MessageStartEvent消息开始事件,要先定义一个全局消息,在空白处点击,然后就可以在properties界面找到message,定义消息了。然后在开始事件中引用这个消息,启动流程实例要用message,如runtimeService.startProcessInstanceByMessage("xxx");xxx就是前面全局消息的name值。
SignalStartEvent信号开始事件:要先定义一个全局信号,在空白处点击,然后就可以在properties界面找到signal,然后在开始事件中引用这个消息,动流程实例要用signal,
runtimeService.signalEventReviced("xxx");xxx就是前面全局信号的name值。
结束事件 ErrorEndEvent错误结束事件用于子流程。与ErrorBoundaryEvent错误边界事件(接收错误码)子流程连用
边界事件,只能放在任务上,比如任务不处理了,那么就可以由边界事件来完成
TimerBoundaryEvent定时边界事件,比如<timeDuration>PT10S</timeDuration>就是延迟10秒后做
MessageBoundaryEvent消息边界事件上面有提到过
SignalBoundaryEvent信号边界事件与上面是雷同的
定义全局信号,然后在边界事件引用它,
/** 根据信号名查询执行对象 */
Execution execution = runtimeService.createExecutionQuery().signalEventSubscriptionName("mySignal").singleResult();
/** 信号事件接收完成当前信号边界事件 */
runtimeService.signalEventReceived("全局信号名", execution.getId());
ErrorBoundaryEvent错误边界事件在子流程的时候说
中间捕获事件:
TimerCatchingEvent定时捕获事件
MessageCatchingEvent消息捕获事件
SignalCatchingEvent信号捕获事件
NoneThrowingEvent空引发事件
这个事件意义不大
-----------------------------
更多推荐
所有评论(0)