diff --git a/yudao-framework/yudao-spring-boot-starter-web/pom.xml b/yudao-framework/yudao-spring-boot-starter-web/pom.xml
index 519d0e21bd..dadd9f6ab7 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-web/pom.xml
@@ -53,7 +53,13 @@
provided
-
+
+
+ com.google.guava
+ guava
+ provided
+
+
org.jsoup
jsoup
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
index 764dd59e79..f5651b6ae8 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -18,6 +18,7 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.UncheckedExecutionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert;
@@ -106,6 +107,9 @@ public class GlobalExceptionHandler {
if (ex instanceof AccessDeniedException) {
return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
}
+ if (ex instanceof UncheckedExecutionException && ex.getCause() != ex) {
+ return allExceptionHandler(request, ex.getCause());
+ }
return defaultExceptionHandler(request, ex);
}
@@ -252,6 +256,16 @@ public class GlobalExceptionHandler {
return CommonResult.error(FORBIDDEN);
}
+ /**
+ * 处理 Guava UncheckedExecutionException
+ *
+ * 例如说,缓存加载报错,可见 https://t.zsxq.com/UszdH
+ */
+ @ExceptionHandler(value = UncheckedExecutionException.class)
+ public CommonResult> uncheckedExecutionExceptionHandler(HttpServletRequest req, UncheckedExecutionException ex) {
+ return allExceptionHandler(req, ex.getCause());
+ }
+
/**
* 处理业务异常 ServiceException
*
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
index 57f4d393f3..87e6a605ff 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/behavior/BpmParallelMultiInstanceBehavior.java
@@ -7,44 +7,35 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter;
-import org.flowable.bpmn.model.Activity;
-import org.flowable.bpmn.model.CallActivity;
-import org.flowable.bpmn.model.FlowElement;
-import org.flowable.bpmn.model.UserTask;
+import org.flowable.bpmn.model.*;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
-import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
+import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
+import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
import java.util.List;
import java.util.Set;
/**
- * 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配
- * 第一步,基于分配规则,计算出分配任务的【多个】候选人们。
- * 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它
+ * 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配
*
- * @author kemengkai
- * @since 2022-04-21 16:57
+ * 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样
+ *
+ * @author 芋道源码
*/
@Setter
-public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
+public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior {
private BpmTaskCandidateInvoker taskCandidateInvoker;
- public BpmParallelMultiInstanceBehavior(Activity activity,
- AbstractBpmnActivityBehavior innerActivityBehavior) {
+ public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
super(activity, innerActivityBehavior);
}
/**
- * 重写该方法,主要实现两个功能:
- * 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
- * 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
+ * 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似
*
- * 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量
- *
- * @param execution 执行任务
- * @return 数量
+ * 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序!
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
@@ -58,8 +49,9 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
+ // 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
@SuppressWarnings("unchecked")
- Set assigneeUserIds = (Set) execution.getVariable(super.collectionVariable, Set.class);
+ Set assigneeUserIds = (Set) execution.getVariableLocal(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
if (CollUtil.isEmpty(assigneeUserIds)) {
@@ -88,4 +80,19 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
return super.resolveNrOfInstances(execution);
}
+ @Override
+ protected void executeOriginalBehavior(DelegateExecution execution, ExecutionEntity multiInstanceRootExecution, int loopCounter) {
+ // 参见 https://t.zsxq.com/53Meo 情况
+ if (execution.getCurrentFlowElement() instanceof CallActivity
+ || execution.getCurrentFlowElement() instanceof SubProcess) {
+ super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
+ return;
+ }
+ // 参见 https://gitee.com/zhijiantianya/yudao-cloud/issues/IC239F
+ super.collectionExpression = null;
+ super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
+ super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
+ super.executeOriginalBehavior(execution, multiInstanceRootExecution, loopCounter);
+ }
+
}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
index 2bd43fdc4d..eca8a9f1ec 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -67,7 +67,6 @@ import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
@@ -221,11 +220,6 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
List simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
processDefinitionInfo,
processVariables, activities);
- // 3.3 如果是发起动作,activityId 为开始节点,不校验审批人自选节点
- if (ObjUtil.equals(reqVO.getActivityId(), BpmnModelConstants.START_USER_NODE_ID)) {
- simulateActivityNodes.removeIf(node ->
- BpmTaskCandidateStrategyEnum.APPROVE_USER_SELECT.getStrategy().equals(node.getCandidateStrategy()));
- }
// 4. 拼接最终数据
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
@@ -415,7 +409,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
endActivities.forEach(activity -> {
// StartEvent:只处理 BPMN 的场景。因为,SIMPLE 情况下,已经有 START_USER_NODE 节点
if (ELEMENT_EVENT_START.equals(activity.getActivityType())
- && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())) {
+ && BpmModelTypeEnum.BPMN.getType().equals(processDefinitionInfo.getModelType())
+ && !CollUtil.contains(activities, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
+ historicActivity -> historicActivity.getActivityId().equals(START_USER_NODE_ID))) {
ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)
.setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());
ActivityNode startNode = new ActivityNode().setId(startTask.getId())
@@ -555,7 +551,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 情况一:BPMN 设计器
if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {
List flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
- return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel,
+ return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(
+ startUserId, bpmnModel, flowElements,
processDefinitionInfo, processVariables, flowElement, runActivityIds));
}
// 情况二:SIMPLE 设计器
@@ -563,7 +560,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(),
BpmSimpleModelNodeVO.class);
List simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
- return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel,
+ return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(
+ startUserId, bpmnModel,
processDefinitionInfo, processVariables, simpleNode, runActivityIds));
}
throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
@@ -618,8 +616,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
return null;
}
- private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,
- BpmProcessDefinitionInfoDO processDefinitionInfo, Map processVariables,
+ private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List flowElements,
+ BpmProcessDefinitionInfoDO processDefinitionInfo,
+ Map processVariables,
FlowElement node, Set runActivityIds) {
if (runActivityIds.contains(node.getId())) {
return null;
@@ -634,6 +633,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 1. 开始节点
if (node instanceof StartEvent) {
+ if (CollUtil.contains(flowElements, // 特殊:如果已经存在用户手动创建的 START_USER_NODE_ID 节点,则忽略 StartEvent
+ flowElement -> flowElement.getId().equals(START_USER_NODE_ID))) {
+ return null;
+ }
return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName())
.setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType());
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
index 9920359d51..dc9f32a0f1 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
@@ -159,10 +159,10 @@ public class CrmContactServiceImpl implements CrmContactService {
// 2. 删除联系人
contactMapper.deleteById(id);
- // 4.1 删除数据权限
- permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
- // 4.2 删除商机关联
+ // 4.1 删除商机关联
contactBusinessService.deleteContactBusinessByContactId(id);
+ // 4.2 删除数据权限
+ permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// 记录操作日志上下文
LogRecordContext.putVariable("contactName", contact.getName());