前言
Activiti Modeler是一款基于angular的web流程在线设计器,主要用于保存BPMN规范相关的对象,例如将模型转换为相应的流程图对象。该组件可以方便业务人员进行在线工作流程图设计。 Activiti Modeler它本身是不提供流程节点合法性校验,而为了保证流程能够顺利走通,因此我们需要进行流程节点校验。
流程节点校验的方式
1、前端保存前校验,通过扩展流程设计器的校验功能 2、后端保存校验,主要通过异常导致事务回滚机制进行校验
由于项目其前端框架主要用iview,项目组缺乏有angular.js开发经验的伙伴,又因为工期原因,没法空出多余时间进行angular研究,因此后面采用的后端保存校验的方式,本文主要介绍以后端保存校验
需要校验流程节点的哪些环节
其实就是校验流程节点的完整性
1、流程节点是否存在开始节点、步骤节点、结束节点 2、流程节点是否有设置节点名称 3、流程节点与节点之间是否有设置流程连接线 4、当流程节点出口存在多个分支时,是否有出口规则条件判断 5、。。。
后端校验的方式
1、通过activiti自带的API进行校验
核心代码片段
代码语言:javascript复制List<ValidationError> validationErrorList = repositoryService.validateProcess(bpmnModel);
上面的校验方式,其自带的文案可能过于技术性,可能会给业务人员带来一定的阅读困难,因此项目组采用下面的校验方式
2、通过校验FlowElement合法性
核心代码
代码语言:javascript复制/**
* 流程校验,因流程判断比较复杂,取巧借用事务回滚机制,如果校验失败,则回滚
* @param bpmnModel
* @param handlerPersonMap 流程办理人map
*/
private void validateFlowModel(BpmnModel bpmnModel,Map<String, List<String>> handlerPersonMap) {
if(bpmnModel != null){
Collection<FlowElement> flowElements = bpmnModel.getMainProcess().getFlowElements();
List<StartEvent> startEvents = new ArrayList<>();
List<UserTask> userTasks = new ArrayList<>();
List<EndEvent> endEvents = new ArrayList<>();
List<SequenceFlow> sequenceFlows = new ArrayList<>();
StringBuilder nodeError = new StringBuilder();
boolean isFoundNodeNameNotSet = false;
for (FlowElement e : flowElements) {
if (e instanceof StartEvent) {
StartEvent startEvent = (StartEvent)e;
startEvents.add(startEvent);
} else if (e instanceof SequenceFlow) {
SequenceFlow sequenceFlow = (SequenceFlow)e;
sequenceFlows.add(sequenceFlow);
} else if (e instanceof UserTask) {
UserTask userTask = (UserTask) e;
userTasks.add(userTask);
if(StringUtil.isBlank(userTask.getName())){
isFoundNodeNameNotSet = true;
}
} else if (e instanceof EndEvent) {
EndEvent endEvent = (EndEvent) e;
endEvents.add(endEvent);
}
}
if(CollectionUtils.isEmpty(startEvents)){
nodeError.append(Constants.PRCOESS_STARTEVENT_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
if(CollectionUtils.isEmpty(userTasks)){
nodeError.append(Constants.PRCOESS_USERTASK_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
if (CollectionUtils.isEmpty(endEvents)){
nodeError.append(Constants.PRCOESS_ENDEVENT_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
if(StringUtil.isNotBlank(nodeError.toString())){
throw new MuseException(ErrorCode.PROCESS_SAVE_FAIL,nodeError.toString());
}
if(isFoundNodeNameNotSet){
throw new MuseException(ErrorCode.PROCESS_SAVE_FAIL,Constants.PRCOESS_NODE_NAME_NOT_SET);
}
if(!CollectionUtils.isEmpty(sequenceFlows)){
for(SequenceFlow sequenceFlow : sequenceFlows){
if(StringUtil.isBlank(sequenceFlow.getSourceRef()) && StringUtil.isBlank(sequenceFlow.getTargetRef())){
nodeError.append(Constants.PRCOESS_SEQUENCEFLOW_RELATE_NODE_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
break;
}
}
}
StringBuilder handlerPersonNotFound = new StringBuilder();
for(StartEvent startEvent : startEvents){
List<SequenceFlow> outGoingFlows = startEvent.getOutgoingFlows();
if(CollectionUtils.isEmpty(outGoingFlows)){
nodeError.append("[").append(startEvent.getName()).append("]").append(Constants.PRCOESS_OUTGOING_FLOWS_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
//节点之间连线校验
validateSequenceFlows(nodeError, startEvent.getName(), outGoingFlows,SequenceFlowType.TARGET);
List<String> handlerPersonList = handlerPersonMap.get(startEvent.getId());
if(CollectionUtils.isEmpty(handlerPersonList)){
handlerPersonNotFound.append("[").append(startEvent.getName()).append("]").append(Constants.PRCOESS_HANDLER_PERSON_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
}
for(UserTask userTask : userTasks){
List<SequenceFlow> incomingFlows = userTask.getIncomingFlows();
if(CollectionUtils.isEmpty(incomingFlows)){
nodeError.append("[").append(StringUtil.isBlank(userTask.getName())? Constants.PRCOESS_WIHTOUT_NAME : userTask.getName()).append("]").append(Constants.PRCOESS_INCOMING_FLOWS_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
//节点之间连线校验
validateSequenceFlows(nodeError,StringUtil.isBlank(userTask.getName())? Constants.PRCOESS_WIHTOUT_NAME : userTask.getName(),incomingFlows,SequenceFlowType.SOURCE);
List<SequenceFlow> outGoingFlows = userTask.getOutgoingFlows();
if(CollectionUtils.isEmpty(outGoingFlows)){
nodeError.append("[").append(StringUtil.isBlank(userTask.getName())? Constants.PRCOESS_WIHTOUT_NAME : userTask.getName()).append("]").append(Constants.PRCOESS_OUTGOING_FLOWS_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
//节点之间连线校验
validateSequenceFlows(nodeError, StringUtil.isBlank(userTask.getName())? Constants.PRCOESS_WIHTOUT_NAME : userTask.getName(), outGoingFlows,SequenceFlowType.TARGET);
List<String> handlerPersonList = handlerPersonMap.get(userTask.getId());
if(CollectionUtils.isEmpty(handlerPersonList)){
handlerPersonNotFound.append("[").append(userTask.getName()).append("]").append(Constants.PRCOESS_HANDLER_PERSON_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
}
for(EndEvent endEvent : endEvents){
List<SequenceFlow> incomingFlows = endEvent.getIncomingFlows();
if(CollectionUtils.isEmpty(incomingFlows)){
nodeError.append("[").append(StringUtil.isBlank(endEvent.getName()) ? Constants.ACT_TYPE_END_CN : endEvent.getName()).append("]").append(Constants.PRCOESS_INCOMING_FLOWS_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
//节点之间连线校验
validateSequenceFlows(nodeError,StringUtil.isBlank(endEvent.getName()) ? Constants.ACT_TYPE_END_CN : endEvent.getName(),incomingFlows,SequenceFlowType.SOURCE);
}
if(StringUtil.isNotBlank(handlerPersonNotFound.toString())){
nodeError.append(handlerPersonNotFound.toString());
}
if(StringUtil.isNotBlank(nodeError.toString())){
throw new MuseException(ErrorCode.PROCESS_SAVE_FAIL,nodeError.toString());
}
}
}
/**
* 节点之间连接线校验
* @param nodeError
* @param nodeName
* @param sequenceFlows
*/
private void validateSequenceFlows(StringBuilder nodeError, String nodeName, List<SequenceFlow> sequenceFlows,SequenceFlowType sequenceFlowType) {
if(CollectionUtils.isEmpty(sequenceFlows)){
return;
}
String tip = null;
String seqRef = null;
int conditionCount = sequenceFlows.size();
for(SequenceFlow sequenceFlow : sequenceFlows){
switch (sequenceFlowType){
case SOURCE:
seqRef = sequenceFlow.getSourceRef();
tip = Constants.PRCOESS_INCOMING_FLOWS_NOT_FOUND;
break;
case TARGET:
seqRef = sequenceFlow.getTargetRef();
tip = Constants.PRCOESS_OUTGOING_FLOWS_NOT_FOUND;
break;
default:
break;
}
if(StringUtil.isBlank(seqRef)){
nodeError.append("[").append(nodeName).append("]").append(tip).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
break;
}
//存在多个分支时,则进行出口条件校验
if(sequenceFlows.size() > 1){
String conditionExpression = sequenceFlow.getConditionExpression();
if(StringUtil.isBlank(conditionExpression)){
conditionCount = conditionCount - 1;
}
}
}
if(conditionCount == 0){
nodeError.append("[").append(nodeName).append("]").append(Constants.PRCOESS_EXPORT_RULE_NOT_FOUND).append(Constants.SEMICOLON).append(Constants.HTML_NEWLINE);
}
}
效果图展示
其他
项目组有用iview的伙伴们,且要实现modal弹窗的拖拽、遮罩层的禁用的需求的话,如果你们项目是采用iview3.x版本以上,则modal加上draggable=true,和mask=false这两个属性就可以实现效果。如果目前采用的是iview2.x版本,这个版本没有这两个属性,可以通过引入jquery-ui.min.js,调用该js提供的 draggable()实现拖拽
附录
view2.x 版本实现modal弹窗拖拽和遮罩层禁用的方法如下
代码语言:javascript复制draggableWithDisAbledMask:function(){
$("div.ivu-modal").draggable();
$("div.ivu-modal-mask").remove();
$("div.ivu-modal-content").css("background","#F7F7F7");
setTimeout(function(){
$(".ivu-modal-wrap").removeClass("ivu-modal-wrap");
}, 1500);
}