设计思路:
(1)一个页面,两个tab标签:A和B。
(2)A标签加载流程图,B标签加载流程数据。
流程图的作用:显示全局流程布局,高亮显示当前进行的环节。
流程数据的作用:显示当前人,处理人,处理时间等需要的信息。
(3)加载流程图:
页面传回businessKey,后台实现查询。查询需要用到的对象:HistoricProcessInstance 或者 ProcessInstance,两者有什么区别?
HistoricProcessInstance:既可以查询历史流程实例(结束的流程),也可以查询运行中的流程实例。(调用getHistoricService()方法实现业务处理)
ProcessInstance :只查询运行中的流程实例。(调用getRuntimeService()方法实现业务处理)
结论:需要效率并且不关注结束流程的情况选择 ProcessInstance,而针对流程监控如果结束的流程也需要监控,应该选择 HistoricProcessInstance 。
重要代码:
/*取历史流程实例,既能取到历史实例又能取到运行中的流程实例*/
HistoricProcessInstance hpi = workFlowEngineServiceImpl.findHistoryProcessInstanceByBusKey(businessKey);
try {
if (hpi == null) {
throw new RuntimeException("获取流程图异常!");
} else {
InputStream imageStream = workFlowEngineServiceImpl.getFlowMap(hpi, hpi.getId(), flowType);
ServletOutputStream os = response.getOutputStream();
int bytesRead = 0;
byte[] buffer = new byte[1024];
while ((bytesRead = imageStream.read(buffer, 0, 1024)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
os.close();
imageStream.close();
}
} catch (Exception e) {
logger.error(e, e);
throw new RuntimeException("获取流程图异常!");
}
/*findHistoryProcessInstanceByBusKey方法*/
/**
* 根据流程businessKey查询历史流程实例
* @param processId
* @return
*/
public HistoricProcessInstance findHistoryProcessInstanceByBusKey(String businessKey){
HistoryService historyService = this.getHistoryService();
return historyService.createHistoricProcessInstanceQuery()
.processInstanceBusinessKey(businessKey).singleResult();
}
/*getFlowMap方法*/
public InputStream getFlowMap(HistoricProcessInstance processInstance, String instanceId, String flowType) {
processEngine = getInstance();
// RuntimeService runtimeService = processEngine.getRuntimeService();
// DynamicBpmnService flowMoniService = processEngine.getDynamicBpmnService();
/*资源服务*/
RepositoryService repositoryService = processEngine.getRepositoryService();
/*历史数据服务*/
HistoryService historyService = processEngine.getHistoryService();
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity)repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
/*为了流程监控图显示效果,替换多有未取到的变量,只显示节点的中文描述*/
List<ActivityImpl> activityList = processDefinition.getActivities();
for(ActivityImpl activity : activityList){
String name = bpmnModel.getFlowElement(activity.getId()).getName();
bpmnModel.getFlowElement(activity.getId())
.setName(name.replaceAll("[\\w{}$\\-+]", ""));
}
/*历史节点,取出变量,设置为节点的名称*/
List<ArkHistoricActivity> hisList = findProcessHistoryByPiid(instanceId);
for(ArkHistoricActivity hisActivity : hisList){
bpmnModel.getFlowElement(hisActivity.getActivityId())
.setName(hisActivity.getActivityName());
}
List<HistoricActivityInstance> activityInstances = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(instanceId).orderByHistoricActivityInstanceStartTime().asc().list();
List<String> activitiIds = new ArrayList<String>();
List<String> flowIds = new ArrayList<String>();
/*获取流程走过的线*/
flowIds = flowServiceImpl.getHighLightedFlows(processDefinition, activityInstances);
/*获取流程走过的节点*/
for (HistoricActivityInstance hai : activityInstances) {
activitiIds.add(hai.getActivityId());
}
Context.setProcessEngineConfiguration(
(ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration());
/**
* 从配置文件中获取中文配置信息,避免中文乱码
* processEngine.getProcessEngineConfiguration().getActivityFontName(),
* processEngine.getProcessEngineConfiguration().getLabelFontName(),
*/
InputStream imageStream = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel, "png", activitiIds,
flowIds, processEngine.getProcessEngineConfiguration().getActivityFontName(),
processEngine.getProcessEngineConfiguration().getLabelFontName(),
"", null, 1.0);
return imageStream;
}
/*getHighLightedFlows方法*/
public List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinitionEntity,
List<HistoricActivityInstance> historicActivityInstances) {
/*用以保存高亮的线flowId*/
List<String> highFlows = new ArrayList<String>();
/*对历史流程节点进行遍历*/
for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
/*得到节点定义的详细信息*/
ActivityImpl activityImpl = processDefinitionEntity.findActivity(historicActivityInstances.get(i).getActivityId());
/*用以保存后需开始时间相同的节点*/
List<ActivityImpl> sameStartTimeNodes = new ArrayList<ActivityImpl>();
/*将后面第一个节点放在时间相同节点的集合里*/
ActivityImpl sameActivityImpl1 = processDefinitionEntity.findActivity(historicActivityInstances.get(i + 1).getActivityId());
sameStartTimeNodes.add(sameActivityImpl1);
for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
/*后续第一个节点*/
HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);
/*后续第二个节点*/
HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);
/*如果第一个节点和第二个节点开始时间相同保存*/
if (activityImpl1.getStartTime().equals(activityImpl2.getStartTime())) {
ActivityImpl sameActivityImpl2 = processDefinitionEntity.findActivity(activityImpl2.getActivityId());
sameStartTimeNodes.add(sameActivityImpl2);
}
/*有不相同跳出循环*/
else {
break;
}
}
/*取出节点的所有出去的线*/
List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions();
/*对所有的线进行遍历*/
for (PvmTransition pvmTransition : pvmTransitions) {
/*如果取出的线的目标节点存在时间相同的节点里,保存该线的id,进行高亮显示*/
ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition.getDestination();
if (sameStartTimeNodes.contains(pvmActivityImpl)) {
highFlows.add(pvmTransition.getId());
}
}
}
return highFlows;
}
注意:
1)流程图中可能需要处理${currName}类似的变量,代码中已有写出,但这些变量替换为真实数据的前提是环节已办理,因为已办理才会记录Variables,未办理的环节需要将这些变量替换为空字符串,这是一些细节处理。
2) HistoricProcessInstance 获取历史数据的前提是:需要配置历史数据的记录级别,与此同时,在配置中也可以处理流程图中文字的乱码。
<!-- ProcessEngineConfiguration
ProcessEngineConfiguration:用于创建ProcessEngine
-->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
<!-- activiti数据库表处理策略 -->
<property name="databaseSchemaUpdate" value="true"/>
<!--
Activiti默认提供4种历史级别:
1、none: 不保存任何历史记录,可以提高系统的性能;
2、activity:保存所有的流程实例、任务、活动信息;
3、audit:也是Activiti的默认级别,保存所有的流程实例、任务、活动、表单属性;
4、full:最完整的历史记录,除了包含Audit级别的信息之外还能保存详细信息,例如:流程变量。
-->
<!-- 历史数据记录级别 -->
<property name="history" value="full"/>
<!-- 中文乱码问题 -->
<property name="activityFontName" value="宋体"/>
<property name="labelFontName" value="宋体"/>
</bean>
流程图效果:
附:完善后的流程图贴出来啦:
注意:需要流程图展示得美观,画流程图的时候就得注意美观。
说得这里就完了吗?没有,流程数据的展示还没说呢。
(4)加载流程数据:
流程数据为什么能和流程图保持一致呢?因为页面传回的businessKey是同一个嘛。这种低级的问题我都不好意回答。
切入正题:从流程图中我们可以看出,红色标注可以追踪到一系列环节,也就意味着这一系列环节的数据有章可循。刚好,有个叫HistoricActivityInstance的对象。
/*查询流程历史记录*/
List<HistoricActivityInstance> history = historyService.createHistoricActivityInstanceQuery()
/*过滤条件*/
.processInstanceId(processId)
/*执行查询*/
.list();
然后呢?把这个history扔个前端去循环遍历,有什么属性自己翻翻,至于如何发请求拿到后台的数据,呵呵,差一点又回答低级问题。
注意:
(1)需要区别businessKey和processId。HistoricProcessInstance.getId()就是processId了,但他没有getProcessId()方法哦。
(2)怎么样知道这些“未知”的对象中都有哪些变量或者方法呢?对象之间又怎么联系呢?答案是反复试验几遍,试验的方法就是传说的中的“断点”。
总结:
为什么要总结?经历的时候只是开阔眼界,总结和反思才能开始成长。
有人会问,为什么不在流程图上显示更多信息呢?比如鼠标经过时,显示时间,处理人等等。我想说,试了你就知道了。还有人会问,流程图好丑,为什么不用vml或者svg依赖数据画图呢?我想说,你不光有钱,还有势,还有时间,还有精力,说不定还脸大。
设计和代码同样重要,缺一不可。
如有问题,随时联系我,网名即QQ。联系不到我,就说明文章没看完。