目標
在不使用activiti explore提供的流程實例圖diagram-viewer/index.html以及其繁雜的BaseProcessDefinitionDiagramLayoutResource四個類的情況下,自定義輕量的流程活動圖查詢接口,可以按順序返回串行顯示的流程數據,並標識執行與否,注意,本例僅解決串行顯示的需求,如果需要1:1完全繪製流程,包括排他網關等,則建議複用流程實例圖diagram-viewer/index.html以及後端接口。
返回格式
demo:
{
"code":0,
"data":[
{
"activityId":"start_1",
"activityName":"發起任務",
"assign":"admin",
"endTime":"2020-05-07 13:54:15",
"highLight":true,
"startTime":1588830855000
},
{
"activityId":"exec_4",
"activityName":"執行任務",
"assign":"xx",
"endTime":"",
"highLight":true,
"startTime":1588830855000
},
{
"activityId":"confirm_5",
"activityName":"確認結果",
"assign":"",
"endTime":"",
"highLight":false,
"startTime":0
}
],
"message":"成功",
"success":true
}
後端接口邏輯
首先activiti 的數據表模塊分運行時(act_ru_execution)和歷史(act_hi_procinst、act_hi_actinst),一般來說業務需求中都會有查全部的功能,這個時候需要返回所有的數據,包括運行時和歷史。所以如果用戶從歷史流程列表入口點進流程圖頁面,這個時候他點擊的是已完成的歷史流程,後臺接口根據歷史流程ID採用runtimeService去查,立刻就報錯。而簡易渲染只關注userTask和startEvent,這個時候只需要查出這兩種類型即可。
HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("userTask");
List<HistoricActivityInstance> list = query.list();
HistoricActivityInstance startEvent = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("startEvent").singleResult();
然後再從中取出需要的活動節點信息,並封裝,準備下一步順序拼接。
for (HistoricActivityInstance tmpHisAct : list) {
ProcessActivityModel processActivityModel = new ProcessActivityModel();
if (tmpHisAct != null) {
processActivityModel.setActivityId(tmpHisAct.getActivityId());
processActivityModel.setActivityName(tmpHisAct.getActivityName());
processActivityModel.setHighLight(true);
Date endDate = tmpHisAct.getEndTime();
if (endDate != null) {
processActivityModel.setEndTime(TimeHelper.formatDate(endDate, TimeHelper.LONG_DATE_PATTERN_LINE));
}
Date startDate = tmpHisAct.getStartTime();
if (startDate != null) {
processActivityModel.setStartTime(startDate.getTime());
}
String assignee = tmpHisAct.getAssignee();
………………
}
results.add(processActivityModel);
}
接下來根據節點啓動時間排序,建議增加time1 、time2爲0校驗,出現該特殊情況流程可以直接廢棄,視爲髒數據流程。
results.sort((pro1, pro2) -> {
long time1 = pro1.getStartTime();
long time2 = pro2.getStartTime();
return (int) (time1 - time2);
});
最後需要拼接未完成節點信息,因爲目前是根據act_hi_actinst表取出的已完成的節點,如果是已完成的歷史流程,則列表數據已完整,但是未完成流程還需要獲取後續節點信息。注意加入循環控制,防止死循環。
這就需要根據最後一個節點信息,不斷遍歷獲取下一節點信息,直到null,此外,如果存在多路排他網關,則視爲無法預測的情況,直接跳過。
String actId = results.get(results.size()-1).getActivityId();//獲取最後一個節點
if (hpins.getEndTime() == null) {//未結束流程,則需拼接未完成節點
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(hpins.getProcessDefinitionId());
……
List<ActivityImpl> activities = processDefinition.getActivities();
ActivityImpl lastAct = null;
for (ActivityImpl tmp : activities) {//獲取最後節點定義
if (actId.equals(tmp.getId())) {
lastAct = tmp;
break;
}
}
ActivityImpl nextAct = findNextAct(lastAct);
int loopTimes = 0;//循環次數,防死循環
while(nextAct!=null && loopTimes<10){//次數可根據業務場景變更
loopTimes++;
String type = nextAct.getProperty("type") + "";
if (type.equals("userTask") || type.equals("startEvent")) {
ProcessActivityModel procNex = new ProcessActivityModel();
procNex.setActivityId(nextAct.getId());
procNex.setActivityName(nextAct.getProperties().get("name") + "");
procNex.setStartTime(0l);
results.add(procNex);
}
nextAct = findNextAct(nextAct);
}
}
完整代碼
@ApiOperation("查詢流程活動圖")
@RequestMapping(value = "queryProcessActivities", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public RetMsg queryProcessActivities(@ApiParam(value = "流程實例ID", required = true) @RequestParam(name = "processInstanceId", required = true) String processInstanceId) {
RetMsg retMsg = new RetMsg (true, ErrorEnum.DEFAULT_SUCCESS.getCode(), ErrorEnum.DEFAULT_SUCCESS.getMessage(), null);
HistoricActivityInstanceQuery query = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("userTask");
List<HistoricActivityInstance> list = query.list();
HistoricActivityInstance startEvent = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("startEvent").singleResult();
list.add(0,startEvent);
List<ProcessActivityModel> results = new ArrayList<>();//返回結果集合
if (list == null || list.size() == 0) {
retMsg.setCode(ErrorEnum.ACT_NOT_FOUND.getCode());
retMsg.setMessage(ErrorEnum.ACT_NOT_FOUND.getMessge());
return retMsg;
}
HistoricProcessInstance hpins = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
if (hpins == null) {
retMsg.setCode(ErrorEnum.ACT_NOT_FOUND.getCode());
retMsg.setMessage(ErrorEnum.ACT_NOT_FOUND.getMessge());
return retMsg;
}
//歷史流程封裝
for (HistoricActivityInstance tmpHisAct : list) {
ProcessActivityModel processActivityModel = new ProcessActivityModel();
if (tmpHisAct != null) {
processActivityModel.setActivityId(tmpHisAct.getActivityId());
processActivityModel.setActivityName(tmpHisAct.getActivityName());
processActivityModel.setHighLight(true);
Date endDate = tmpHisAct.getEndTime();
if (endDate != null) {
processActivityModel.setEndTime(TimeHelper.formatDate(endDate, TimeHelper.LONG_DATE_PATTERN_LINE));
}
Date startDate = tmpHisAct.getStartTime();
if (startDate != null) {
processActivityModel.setStartTime(startDate.getTime());
}
String assignee = tmpHisAct.getAssignee();
if (tmpHisAct.getActivityType().equals("startEvent")) {
assignee = hpins.getStartUserId();
}
if(StringUtils.isNotEmpty(assignee)){
Map<String, Object> userInfo = userService.getUserMsg(Long.parseLong(assignee));
if (userInfo != null) {
assignee = userInfo.get("name") + "";
}
}else{
assignee = "";
}
processActivityModel.setAssign(assignee);
}
results.add(processActivityModel);
}
results.sort((pro1, pro2) -> {
long time1 = pro1.getStartTime();
long time2 = pro2.getStartTime();
return (int) (time1 - time2);
});
String actId = results.get(results.size()-1).getActivityId();//獲取最後一個節點
if (hpins.getEndTime() == null) {//未結束流程,則需拼接未完成節點
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(hpins.getProcessDefinitionId());
if (processDefinition == null) {
retMsg.setCode(ErrorEnum.ACT_DEFINE_NOT_FOUND.getCode());
retMsg.setMessage(ErrorEnum.ACT_DEFINE_NOT_FOUND.getMessage());
return retMsg;
}
List<ActivityImpl> activities = processDefinition.getActivities();
ActivityImpl lastAct = null;
for (ActivityImpl tmp : activities) {//獲取最後節點定義
if (actId.equals(tmp.getId())) {
lastAct = tmp;
break;
}
}
ActivityImpl nextAct = findNextAct(lastAct);
int loopTimes = 0;//循環次數,防死循環
while(nextAct!=null && loopTimes<10){
loopTimes++;
String type = nextAct.getProperty("type") + "";
if (type.equals("userTask") || type.equals("startEvent")) {
ProcessActivityModel procNex = new ProcessActivityModel();
procNex.setActivityId(nextAct.getId());
procNex.setActivityName(nextAct.getProperties().get("name") + "");
procNex.setStartTime(0l);
results.add(procNex);
}
nextAct = findNextAct(nextAct);
}
}
retMsg.setData(results);
return retMsg;
}
private ActivityImpl findNextAct(ActivityImpl tmp) {
ActivityImpl nextOne = null;
if (tmp != null) {
List<PvmTransition> pvs = tmp.getOutgoingTransitions();
if(pvs.size()==1){
return (ActivityImpl)pvs.get(0).getDestination();
}else{//多路排他網關則視爲無法獲取下一節點
return null;
}
}
return nextOne;
}