最近又开始忙活工作流的相关工作了,第一次接触工作流也就是我在埃森哲的第一个项目,也是最后一个项目。那时候用的是日本的一整套解决方案好像叫-iMart。而进入金山工作后第二个项目也是和工作流密切相关的项目,那时候才接触到了这个开源工作流引擎-Activiti。那时候也是第一次真正了解这些东西。只是从金山离职后回到现在的公司,没想到还要用这些东西,好像又回到了去年的这个时间。
这一次需要重新集成这个引擎,以插件包的方式。其实工作量不大,因为Activiti已经自己封装的很好了,完全可以在日常开发中直接引用它的Service。闲话少说,这次解决工作流流程图问题。简而言之,在任何一条已启动的流程实例查看流程状态,用流程图片的形式展示一下。
提供的Service方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 获取当前任务流程图 * * @param processInstanceId * @return */ @Override public InputStream generateDiagram(String processInstanceId) { //方法中用到的参数是流程实例ID,其实TaskId也可以转为这个。调用taskService查询即可。 Command<InputStream> cmd = new ProcessInstanceDiagramCmd(processInstanceId, runtimeService, repositoryService, processEngine, historyService); return managementService.executeCommand(cmd); } |
而ProcessInstanceDiagramCmd采用了Activiti的命令模式。就是继承Command接口,其实这个地方完全可以使用纯Service方法去搞定,我之所以这么写,还是受到了国内临远大师的影响。
本次绘制的流程图分为两种情况:1、流程实例还未执行完毕,也就是流程还没有结束,还有运行的任务。2、已经执行完毕的流程,流程已经进入了流程历史。不管属于以上哪种情况的流程图都会绘制流程走向。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 |
import com.google.common.collect.Lists; import org.activiti.bpmn.model.BpmnModel; import org.activiti.engine.HistoryService; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.interceptor.Command; import org.activiti.engine.impl.interceptor.CommandContext; import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.activiti.engine.impl.pvm.PvmTransition; import org.activiti.engine.impl.pvm.process.ActivityImpl; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.image.impl.DefaultProcessDiagramGenerator; import org.activiti.spring.ProcessEngineFactoryBean; import java.io.InputStream; import java.util.*; /** * 根据流程实例ID生成流程图 * * @author Chen Zhiguo */ public class ProcessInstanceDiagramCmd implements Command<InputStream> { protected String processInstanceId; private RuntimeService runtimeService; private RepositoryService repositoryService; private ProcessEngineFactoryBean processEngine; private HistoryService historyService; public ProcessInstanceDiagramCmd(String processInstanceId, RuntimeService runtimeService, RepositoryService repositoryService, ProcessEngineFactoryBean processEngine, HistoryService historyService) { this.processInstanceId = processInstanceId; this.runtimeService = runtimeService; this.repositoryService = repositoryService; this.processEngine = processEngine; this.historyService = historyService; } public InputStream execute(CommandContext commandContext) { ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId).singleResult(); HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); if (processInstance == null && historicProcessInstance == null) { return null; } ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService .getProcessDefinition(processInstance == null ? historicProcessInstance.getProcessDefinitionId() : processInstance.getProcessDefinitionId()); BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); List<String> activeActivityIds = Lists.newArrayList(); if (processInstance != null) { activeActivityIds = runtimeService.getActiveActivityIds(processInstance.getProcessInstanceId()); } else { activeActivityIds.add(historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).activityType("endEvent") .singleResult().getActivityId()); } // 使用spring注入引擎请使用下面的这行代码 Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration()); List<String> highLightedFlows = getHighLightedFlows(processDefinition, processInstanceId); InputStream imageStream = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, "png", activeActivityIds, highLightedFlows); return imageStream; } /** * getHighLightedFlows * * @param processDefinition * @param processInstanceId * @return */ private List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinition, String processInstanceId) { List<String> highLightedFlows = new ArrayList<String>(); // List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery() // .processInstanceId(processInstanceId) // //order by startime asc is not correct. use default order is correct. // //.orderByHistoricActivityInstanceStartTime().asc()/*.orderByActivityId().asc()*/ // .list(); //上面注释掉的代码是官方的rest方法中提供的方案,可是我在实际测试中有Bug出现,所以做了一下修改。注意下面List内容的排序会影响流程走向红线丢失的问题 List<HistoricActivityInstance> historicActivityInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId) .orderByHistoricActivityInstanceStartTime().orderByHistoricActivityInstanceEndTime().asc().list(); LinkedList<HistoricActivityInstance> hisActInstList = new LinkedList<HistoricActivityInstance>(); hisActInstList.addAll(historicActivityInstances); getHighlightedFlows(processDefinition.getActivities(), hisActInstList, highLightedFlows); return highLightedFlows; } /** * getHighlightedFlows * <p/> * code logic: * 1. Loop all activities by id asc order; * 2. Check each activity's outgoing transitions and eventBoundery outgoing transitions, if outgoing transitions's destination.id is in other executed activityIds, add this transition to highLightedFlows List; * 3. But if activity is not a parallelGateway or inclusiveGateway, only choose the earliest flow. * * @param activityList * @param hisActInstList * @param highLightedFlows */ private void getHighlightedFlows(List<ActivityImpl> activityList, LinkedList<HistoricActivityInstance> hisActInstList, List<String> highLightedFlows) { //check out startEvents in activityList List<ActivityImpl> startEventActList = new ArrayList<ActivityImpl>(); Map<String, ActivityImpl> activityMap = new HashMap<String, ActivityImpl>(activityList.size()); for (ActivityImpl activity : activityList) { activityMap.put(activity.getId(), activity); String actType = (String) activity.getProperty("type"); if (actType != null && actType.toLowerCase().indexOf("startevent") >= 0) { startEventActList.add(activity); } } //These codes is used to avoid a bug: //ACT-1728 If the process instance was started by a callActivity, it will be not have the startEvent activity in ACT_HI_ACTINST table //Code logic: //Check the first activity if it is a startEvent, if not check out the startEvent's highlight outgoing flow. HistoricActivityInstance firstHistActInst = hisActInstList.getFirst(); String firstActType = (String) firstHistActInst.getActivityType(); if (firstActType != null && firstActType.toLowerCase().indexOf("startevent") < 0) { PvmTransition startTrans = getStartTransaction(startEventActList, firstHistActInst); if (startTrans != null) { highLightedFlows.add(startTrans.getId()); } } while (!hisActInstList.isEmpty()) { HistoricActivityInstance histActInst = hisActInstList.removeFirst(); ActivityImpl activity = activityMap.get(histActInst.getActivityId()); if (activity != null) { boolean isParallel = false; String type = histActInst.getActivityType(); if ("parallelGateway".equals(type) || "inclusiveGateway".equals(type)) { isParallel = true; } else if ("subProcess".equals(histActInst.getActivityType())) { getHighlightedFlows(activity.getActivities(), hisActInstList, highLightedFlows); } List<PvmTransition> allOutgoingTrans = new ArrayList<PvmTransition>(); allOutgoingTrans.addAll(activity.getOutgoingTransitions()); allOutgoingTrans.addAll(getBoundaryEventOutgoingTransitions(activity)); List<String> activityHighLightedFlowIds = getHighlightedFlows(allOutgoingTrans, hisActInstList, isParallel); highLightedFlows.addAll(activityHighLightedFlowIds); } } } /** * Check out the outgoing transition connected to firstActInst from startEventActList * * @param startEventActList * @param firstActInst * @return */ private PvmTransition getStartTransaction(List<ActivityImpl> startEventActList, HistoricActivityInstance firstActInst) { for (ActivityImpl startEventAct : startEventActList) { for (PvmTransition trans : startEventAct.getOutgoingTransitions()) { if (trans.getDestination().getId().equals(firstActInst.getActivityId())) { return trans; } } } return null; } /** * getBoundaryEventOutgoingTransitions * * @param activity * @return */ private List<PvmTransition> getBoundaryEventOutgoingTransitions(ActivityImpl activity) { List<PvmTransition> boundaryTrans = new ArrayList<PvmTransition>(); for (ActivityImpl subActivity : activity.getActivities()) { String type = (String) subActivity.getProperty("type"); if (type != null && type.toLowerCase().indexOf("boundary") >= 0) { boundaryTrans.addAll(subActivity.getOutgoingTransitions()); } } return boundaryTrans; } /** * find out single activity's highlighted flowIds * * @param pvmTransitionList * @param hisActInstList * @param isParallel * @return */ private List<String> getHighlightedFlows(List<PvmTransition> pvmTransitionList, LinkedList<HistoricActivityInstance> hisActInstList, boolean isParallel) { List<String> highLightedFlowIds = new ArrayList<String>(); PvmTransition earliestTrans = null; HistoricActivityInstance earliestHisActInst = null; for (PvmTransition pvmTransition : pvmTransitionList) { String destActId = pvmTransition.getDestination().getId(); HistoricActivityInstance destHisActInst = findHisActInst(hisActInstList, destActId); if (destHisActInst != null) { if (isParallel) { highLightedFlowIds.add(pvmTransition.getId()); } else if (earliestHisActInst == null || (earliestHisActInst.getId().compareTo(destHisActInst.getId()) > 0)) { earliestTrans = pvmTransition; earliestHisActInst = destHisActInst; } } } if ((!isParallel) && earliestTrans != null) { highLightedFlowIds.add(earliestTrans.getId()); } return highLightedFlowIds; } private HistoricActivityInstance findHisActInst(LinkedList<HistoricActivityInstance> hisActInstList, String actId) { for (HistoricActivityInstance hisActInst : hisActInstList) { if (hisActInst.getActivityId().equals(actId)) { return hisActInst; } } return null; } } |
我遇到的坑:
因为测试使用的是JUNIT跑的测试用例,所以真个流程在同一秒就结束了,所以造成流程历史节点开始时间都一样,排序错乱,流程图绘制出现短线的情况。