本文会详细讲解activiti6.0的各种java api的操作。从一个流程的创建、完成任务、拾取任务、获取变量、设置变量到结束。也会涉及到流程退回、流程跳过、强制结束流程、获取正在进行中的流程图等操作。
这些方法几乎都是我在工作中遇到的实际操作,然后做的处理,这里分享给大家,也当作一个记录。
activiti的api给的方法很多也很全面,但是毕竟是人家写的东西,好多地方都可能不会符合实际需求,比如用户表你肯定使用你自己的吧?,这里建议自己也可以建立一套流程相关的表,思路是可以设计流程实体表、流程节点表、流转节点表、流程日志记录表等基础表来辅助完成业务。
关于集成springboot+activiti6.0的整合可以参考本人另一篇文章Activiti6.0+springboot,流程图绘制,在线设计器,activiti-explorer整合modeler_Henry-tech的博客-CSDN博客_activiti在线画图,里面会有详细的整合
先画一个流程图,如下图所示,下图设计到了用户服务、服务任务、互斥网关等节点。后面我们就基于这个流程图开始操练,流程图可以各种变化,但是方法都大同小异,只要认真的走一遍就全通了。
初始数据库中没有流程图的话,需要先创建一个可用的流程图。参考我上面给出链接文章的2.1章节
这里repositoryService.getModel(),需要填入一个modelId这个modelId就是创建流程后得到的modelId.
@Test
public void deploy() throws Exception {
//获取模型
Model modelData = repositoryService.getModel("1");
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
if (bytes == null) {
logger.error("未找到对应modelId的流程模板!");
return;
}
JsonNode modelNode = new ObjectMapper().readTree(bytes);
BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
if (model.getProcesses().size() == 0) {
logger.error("请设置一条流程图");
return;
}
byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);
//发布流程
String processName = modelData.getName() + ".bpmn20.xml";
Deployment deployment = repositoryService.createDeployment()
.name(modelData.getName())
.addString(processName, new String(bpmnBytes, "UTF-8"))
.deploy();
modelData.setDeploymentId(deployment.getId());
repositoryService.saveModel(modelData);
}
运行方法,我就可以看到流程发布成功啦,可以得到一个流程实例ID。
然后我们去数据库中也可以看到ACT_RE_DEPLOYMENT表有发布成功的数据。
发布完流程之后就可以基于这个发布的流程开始操作了,首先我们启动一个流程
@Test
public void startFlow() {
Map map = new HashMap<>();
map.put("firstPerson", "henry");
map.put("secondPerson", "sherry");
ProcessInstance processInstance = runtimeService
.startProcessInstanceByKey("TEST_PROCESS", "businessCode01", map);
System.out.println("启动成功");
System.out.println("流程实例名称->" + processInstance.getName());
System.out.println("流程实例ID->" + processInstance.getId());
}
这里map是流程变量对应着流程图上的代理人,注意,如果流程图设置了流程变量但是在启动时没有对这个流程变量赋值,是会报错的哦。当然这里如果图省事儿的话,可以直接将代理人在定义流程时写死
startProcessInstanceByKey方法就是启动流程的方法,第一个参数是processDefinitionKey也就是流程定义的名称,第二个参数是businessKey,也就是自己的业务Key,用于维护自己建立表的唯一标识。
启动过后可以看到数据库ACT_RU_TASK表中也产生了数据,这里要说一下的是,ACT_RU_TASK表中的记录是当前正在运行的节点,当这个节点完成后,在这里表中就会删除。如果要查历史记录需要到ACT_HI_开头的表中查找。
刚刚启动了一条任务,那么流程应该走到了审批人1的节点了,此时我们来查询一下任务。这个方法可以用来校验操作权限。
@Test
public void queryPersonalTaskList() {
// 流程定义key
String processDefinitionKey = "TEST_PROCESS1";
// 任务负责人
String assignee = "henry";
List taskList = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.includeProcessVariables()
.taskAssignee(assignee)
.list();
for (Task task : taskList) {
System.out.println("----------------------------");
System.out.println("流程实例id: " + task.getProcessInstanceId());
System.out.println("任务id: " + task.getId());
System.out.println("任务负责人: " + task.getAssignee());
System.out.println("任务名称: " + task.getName());
System.out.println("----------------------------");
}
}
先根据taskid和assingee(代理人)来查找一下任务做一下简单的权限校验,taskid可以通过上面的查询方法的到。
@Test
public void completTask() {
//任务id
String taskId = "25007";
// 任务负责人
String assingee = "henry";
Task task = taskService.createTaskQuery()
.taskId(taskId)
.taskAssignee(assingee)
.singleResult();
if (task != null) {
HashMap map = new HashMap<>();
map.put("agree", 1);
taskService.complete(taskId, map);
System.out.println("完成任务");
}
}
这里要说下的是complete方法我传了2个参数,有一个map则是完成时需要的参数,结合流程上就是网关过后的直线的流转条件,也就是流程判断。如果你的流程没有设置网关的话,可以直接用compelete传一个taskid结束就可以。
通过processInstanceId节点查询历史节点,processInstanceId可以通过查询任务方法得到也就是流程实例id,也可以到数据库ACT_RU_TASK表中看PROC_INST_ID_字段。
@Test
public void queryHis() {
// 查询所有的历史节点
// List list = historyService.createHistoricActivityInstanceQuery().processInstanceId("25001").finished().list();
// for (HistoricActivityInstance hai : list) {
// System.out.println("=================================");
// System.out.println("活动ID:" + hai.getId());
// System.out.println("流程实例ID:" + hai.getProcessInstanceId());
// System.out.println("活动名称:" + hai.getActivityName());
// System.out.println("办理人:" + hai.getAssignee());
// System.out.println("开始时间:" + hai.getStartTime());
// System.out.println("结束时间:" + hai.getEndTime());
// System.out.println("=================================");
// }
//查询task的历史节点
List list1 = historyService.createHistoricTaskInstanceQuery().processInstanceId("25001").finished().list();
for (HistoricTaskInstance hai : list1) {
System.out.println("=================================");
System.out.println("活动ID:" + hai.getId());
System.out.println("流程实例ID:" + hai.getProcessInstanceId());
System.out.println("活动名称:" + hai.getName());
System.out.println("办理人:" + hai.getAssignee());
System.out.println("开始时间:" + hai.getStartTime());
System.out.println("结束时间:" + hai.getEndTime());
System.out.println("=================================");
}
}
查询候选人任务,候选人就是设置代理人的时候会设置一个候选人,这个候选人可以通过拾取任务来代替原本的受理人完成任务,相当于一个替补的概念。
@Test
public void queryCandidateTaskList() {
// 流程定义key
String processDefinitionKey = "TEST-PROCESS";
// 任务候选人
String candidateUser = "melo";
// 创建TaskService
//查询组任务
List list = taskService.createTaskQuery()
.processDefinitionKey(processDefinitionKey)
.taskCandidateUser(candidateUser)//根据候选人查询
.list();
for (Task task : list) {
System.out.println("----------------------------");
System.out.println("流程实例id:" + task.getProcessInstanceId());
System.out.println("任务id:" + task.getId());
System.out.println("任务负责人:" + task.getAssignee());
System.out.println("任务名称:" + task.getName());
System.out.println("----------------------------");
}
}
这个方法需要注意的是只有在当前受理人为null的情况下才能查询到候选任务。可以看控制台打印的sql,发现activiti查询的时候带了一个条件,ASSIGNEE_ IS NULL
SELECT DISTINCT RES.*
FROM ACT_RU_TASK RES INNER JOIN ACT_RU_IDENTITYLINK I
ON I.TASK_ID_ = RES.ID_
WHERE RES.ASSIGNEE_ IS NULL AND I.TYPE_ = 'candidate' AND ( I.USER_ID_ = 'melo')
这里是不是感觉就离谱?他这候选人是真的地位低啊哈哈!所以这也印证了我之前说的,还是自己设计一套表来保存一些关键信息靠谱,那么怎样把代理人设置为空呢?就需要用到拾取和归还任务。
其实这个代码来说超级简单就两行。
/**
* 归还任务
*/
public void returnTask(){
//归还任务,也就是把assignee字段设置为空
taskService.setAssignee("47507", null);
}
/**
* 拾取任务
*/
@Test
public void claimTask() {
//拾取任务
taskService.claim("47507", "melo");
}
拾取和归还任务后可以去ACT_RU_IDENTITYLINK表查看数据变化。candidate就是候选人类型participant就是代理人类型。
退回在activiti官方是默认不支持的,也就是没有对应的api能够支持退回,那么只能自己来写一个了,思路是通过BpmnModel模型来实现,这个BpmnModel模型是你每启动一个新的流程就会创建一个对应的模型,里面其实就是一个流程图。
思路是,我们要先找到当前节点,和上一节点。把当前节点原本的连线全部断开,指向上一个几点,然后compelete完成任务,此时任务就跑到上一节点了。然后再把原来的连线恢复(记住一定要恢复原本的连线,不然流程就乱了),具体实现逻辑如下图所示
/**
* 退回节点
*/
@Test
public void goBack() throws Exception {
this.revoke("65001", "henry");
System.out.println("退回成功");
}
/**
* 根据名称退回
*/
public void revoke(String processInstanceId, String nowUser) throws Exception {
Task task = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
if (task == null) {
throw new Exception("流程未启动或已执行完成,无法撤回");
}
//通过processInstanceId查询历史节点
List htiList = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByTaskCreateTime()
.asc()
.list();
String myTaskId = null;
HistoricTaskInstance myTask = null;
//找到当前运行的节点
for (HistoricTaskInstance hti : htiList) {
if (nowUser.equals(hti.getAssignee())) {
myTaskId = hti.getId();
myTask = hti;
break;
}
}
if (null == myTaskId) {
throw new Exception("该任务非当前用户提交,无法撤回");
}
String processDefinitionId = myTask.getProcessDefinitionId();
//获取流程模型
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
String myActivityId = null;
//查询已经完成的流程节点,查询到上一条已完成的节点,则跳出循环
List haiList = historyService.createHistoricActivityInstanceQuery()
.executionId(myTask.getExecutionId()).finished().list();
for (HistoricActivityInstance hai : haiList) {
if (myTaskId.equals(hai.getTaskId())) {
myActivityId = hai.getActivityId();
break;
}
}
FlowNode myFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(myActivityId);
Execution execution = runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult();
String activityId = execution.getActivityId();
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityId);
//记录原活动方向
List oriSequenceFlows = new ArrayList();
oriSequenceFlows.addAll(flowNode.getOutgoingFlows());
//清理活动方向
flowNode.getOutgoingFlows().clear();
//建立新方向
List newSequenceFlowList = new ArrayList();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(flowNode);
newSequenceFlow.setTargetFlowElement(myFlowNode);
newSequenceFlowList.add(newSequenceFlow);
flowNode.setOutgoingFlows(newSequenceFlowList);
Authentication.setAuthenticatedUserId(nowUser);
taskService.addComment(task.getId(), task.getProcessInstanceId(), "撤回");
//完成任务
taskService.complete(task.getId());
//恢复原方向
flowNode.setOutgoingFlows(oriSequenceFlows);
logger.info("退回成功!");
}
理解了退回的思想之后,跳转就同理可得了。
@Test
public void jumptoNext() throws Exception {
this.skip("180001");
}
public void skip(String instanceId) throws Exception {
Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult();
if (task == null) {
throw new Exception("流程未启动或已执行完成,无法撤回");
}
String processDefinitionId = task.getProcessDefinitionId();
BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
//获取当前节点
Execution execution = runtimeService.createExecutionQuery().executionId(task.getExecutionId()).singleResult();
String activityId = execution.getActivityId();
FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityId);
//需要跳转的节点
FlowNode toFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement("sid-7BAD1FA7-8E4F-4555-BB6B-A55A02E5AF54");
if (toFlowNode == null) {
throw new Exception("跳转的下一节点为空");
}
//记录原活动方向
List oriSequenceFlows = new ArrayList();
oriSequenceFlows.addAll(flowNode.getOutgoingFlows());
//清理活动方向
flowNode.getOutgoingFlows().clear();
//建立新方向
List newSequenceFlowList = new ArrayList();
SequenceFlow newSequenceFlow = new SequenceFlow();
newSequenceFlow.setId("newSequenceFlowId");
newSequenceFlow.setSourceFlowElement(flowNode);
newSequenceFlow.setTargetFlowElement(toFlowNode);
newSequenceFlowList.add(newSequenceFlow);
flowNode.setOutgoingFlows(newSequenceFlowList);
taskService.addComment(task.getId(), task.getProcessInstanceId(), "跳转指定节点");
//完成任务
taskService.complete(task.getId());
//恢复原方向
flowNode.setOutgoingFlows(oriSequenceFlows);
logger.info("跳转成功,from->{},to->{}", flowNode.getName(), toFlowNode.getName());
}
遇到个需求就是需要在初始化流程的时候根据一些规则自动将代理人变量名赋值
@Test
public void getVariables() throws Exception {
String modelId = "1";
//获取模型
Model modelData = repositoryService.getModel(modelId);
byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
if (bytes == null) {
throw new Exception("模型数据为空,请先设计流程并成功保存,再进行发布!");
}
JsonNode modelNode = null;
try {
modelNode = new ObjectMapper().readTree(bytes);
} catch (IOException e) {
throw new Exception("读取模型数据异常!");
}
BpmnModel bpmnModel = new BpmnJsonConverter().convertToBpmnModel(modelNode);
Collection flowElements = bpmnModel.getMainProcess().getFlowElements();
for (FlowElement flowElement : flowElements) {
if (flowElement instanceof UserTask) {
UserTask userTask = (UserTask) flowElement;
System.out.println("-----------------------------");
System.out.println(flowElement.getName());
System.out.println(flowElement.getId());
String assigneeEl = userTask.getAssignee();
if (StringUtils.isBlank(assigneeEl)) {
continue;
}
if (assigneeEl.startsWith("${") && assigneeEl.endsWith("}") && assigneeEl.length() > 3) {
String assignee = assigneeEl.substring(2, assigneeEl.length() - 2);
System.out.println("assignee:" + assignee);
}
}
}
}
设置一个监听类,当审核人2结束之后,执行业务逻辑会启动,然后自动执行这个监听类中的execute方法,监听类只需要实现JavaDelegate类即可。
public class TestServiceDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
System.out.println("开始处理业务逻辑-->...");
}
}
强制结束的逻辑与跳过和退回差不多,这里不再赘述。
获取运行中流程图图片,具体代码后面给出
总的来说,activiti的操作不算特别难,只是有些方法需要不停的去摸索,而且很多时候它官方的api并不能符合我们的要求,这时候就需要我们进行一些二次开发了。
源码我放在gitee上了,有兴趣的可以下载下来看看https://gitee.com/henryht/activiti-springboot/tree/master
github地址:GitHub - HenryHt-Tech/activiti-springboot: activiti+springboot整合实践