This commit is contained in:
YunaiV
2025-08-03 19:46:21 +08:00
5 changed files with 66 additions and 36 deletions

View File

@ -53,7 +53,13 @@
<scope>provided</scope> <!-- 设置为 provided主要是 GlobalExceptionHandler 使用 --> <scope>provided</scope> <!-- 设置为 provided主要是 GlobalExceptionHandler 使用 -->
</dependency> </dependency>
<!-- xss --> <!-- 工具类相关 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>provided</scope> <!-- 设置为 provided只有工具类需要使用到 -->
</dependency>
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId> <artifactId>jsoup</artifactId>

View File

@ -18,6 +18,7 @@ import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.concurrent.UncheckedExecutionException;
import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -106,6 +107,9 @@ public class GlobalExceptionHandler {
if (ex instanceof AccessDeniedException) { if (ex instanceof AccessDeniedException) {
return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
} }
if (ex instanceof UncheckedExecutionException && ex.getCause() != ex) {
return allExceptionHandler(request, ex.getCause());
}
return defaultExceptionHandler(request, ex); return defaultExceptionHandler(request, ex);
} }
@ -252,6 +256,16 @@ public class GlobalExceptionHandler {
return CommonResult.error(FORBIDDEN); return CommonResult.error(FORBIDDEN);
} }
/**
* 处理 Guava UncheckedExecutionException
*
* 例如说,缓存加载报错,可见 <a href="https://t.zsxq.com/UszdH">https://t.zsxq.com/UszdH</a>
*/
@ExceptionHandler(value = UncheckedExecutionException.class)
public CommonResult<?> uncheckedExecutionExceptionHandler(HttpServletRequest req, UncheckedExecutionException ex) {
return allExceptionHandler(req, ex.getCause());
}
/** /**
* 处理业务异常 ServiceException * 处理业务异常 ServiceException
* *

View File

@ -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.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import lombok.Setter; import lombok.Setter;
import org.flowable.bpmn.model.Activity; import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.CallActivity;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior; 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.List;
import java.util.Set; import java.util.Set;
/** /**
* 自定义的【行】的【多个】流程任务的 assignee 负责人的分配 * 自定义的【行】的【多个】流程任务的 assignee 负责人的分配
* 第一步,基于分配规则,计算出分配任务的【多个】候选人们。
* 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它
* *
* @author kemengkai * 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样
* @since 2022-04-21 16:57 *
* @author 芋道源码
*/ */
@Setter @Setter
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior { public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior {
private BpmTaskCandidateInvoker taskCandidateInvoker; private BpmTaskCandidateInvoker taskCandidateInvoker;
public BpmParallelMultiInstanceBehavior(Activity activity, public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
AbstractBpmnActivityBehavior innerActivityBehavior) {
super(activity, innerActivityBehavior); super(activity, innerActivityBehavior);
} }
/** /**
* 重写该方法,主要实现两个功能: * 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似
* 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
* 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
* *
* 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量 * 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序!
*
* @param execution 执行任务
* @return 数量
*/ */
@Override @Override
protected int resolveNrOfInstances(DelegateExecution execution) { protected int resolveNrOfInstances(DelegateExecution execution) {
@ -58,8 +49,9 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId()); super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人 // 第二步,获取任务的所有处理人
// 不使用 execution.getVariable 原因:目前依次审批任务回退后 collectionVariable 变量没有清理, 如果重新进入该任务不会重新分配审批人
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class); Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
if (assigneeUserIds == null) { if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution); assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
if (CollUtil.isEmpty(assigneeUserIds)) { if (CollUtil.isEmpty(assigneeUserIds)) {
@ -88,4 +80,19 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
return super.resolveNrOfInstances(execution); 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);
}
} }

View File

@ -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.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 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.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 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; 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<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel, List<ActivityNode> simulateActivityNodes = getSimulateApproveNodeList(startUserId, bpmnModel,
processDefinitionInfo, processDefinitionInfo,
processVariables, activities); 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. 拼接最终数据 // 4. 拼接最终数据
return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance, return buildApprovalDetail(reqVO, bpmnModel, processDefinition, processDefinitionInfo, historicProcessInstance,
@ -415,7 +409,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
endActivities.forEach(activity -> { endActivities.forEach(activity -> {
// StartEvent只处理 BPMN 的场景。因为SIMPLE 情况下,已经有 START_USER_NODE 节点 // StartEvent只处理 BPMN 的场景。因为SIMPLE 情况下,已经有 START_USER_NODE 节点
if (ELEMENT_EVENT_START.equals(activity.getActivityType()) 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) ActivityNodeTask startTask = new ActivityNodeTask().setId(BpmnModelConstants.START_USER_NODE_ID)
.setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus()); .setAssignee(startUserId).setStatus(BpmTaskStatusEnum.APPROVE.getStatus());
ActivityNode startNode = new ActivityNode().setId(startTask.getId()) ActivityNode startNode = new ActivityNode().setId(startTask.getId())
@ -555,7 +551,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 情况一BPMN 设计器 // 情况一BPMN 设计器
if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) { if (Objects.equals(BpmModelTypeEnum.BPMN.getType(), processDefinitionInfo.getModelType())) {
List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables); List<FlowElement> flowElements = BpmnModelUtils.simulateProcess(bpmnModel, processVariables);
return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(startUserId, bpmnModel, return convertList(flowElements, flowElement -> buildNotRunApproveNodeForBpmn(
startUserId, bpmnModel, flowElements,
processDefinitionInfo, processVariables, flowElement, runActivityIds)); processDefinitionInfo, processVariables, flowElement, runActivityIds));
} }
// 情况二SIMPLE 设计器 // 情况二SIMPLE 设计器
@ -563,7 +560,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(), BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(),
BpmSimpleModelNodeVO.class); BpmSimpleModelNodeVO.class);
List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables); List<BpmSimpleModelNodeVO> simpleNodes = SimpleModelUtils.simulateProcess(simpleModel, processVariables);
return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(startUserId, bpmnModel, return convertList(simpleNodes, simpleNode -> buildNotRunApproveNodeForSimple(
startUserId, bpmnModel,
processDefinitionInfo, processVariables, simpleNode, runActivityIds)); processDefinitionInfo, processVariables, simpleNode, runActivityIds));
} }
throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType()); throw new IllegalArgumentException("未知设计器类型:" + processDefinitionInfo.getModelType());
@ -618,8 +616,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
return null; return null;
} }
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables,
FlowElement node, Set<String> runActivityIds) { FlowElement node, Set<String> runActivityIds) {
if (runActivityIds.contains(node.getId())) { if (runActivityIds.contains(node.getId())) {
return null; return null;
@ -634,6 +633,10 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 1. 开始节点 // 1. 开始节点
if (node instanceof StartEvent) { 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()) return activityNode.setName(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getName())
.setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()); .setNodeType(BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType());
} }

View File

@ -159,10 +159,10 @@ public class CrmContactServiceImpl implements CrmContactService {
// 2. 删除联系人 // 2. 删除联系人
contactMapper.deleteById(id); contactMapper.deleteById(id);
// 4.1 删除数据权限 // 4.1 删除商机关联
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// 4.2 删除商机关联
contactBusinessService.deleteContactBusinessByContactId(id); contactBusinessService.deleteContactBusinessByContactId(id);
// 4.2 删除数据权限
permissionService.deletePermission(CrmBizTypeEnum.CRM_CONTACT.getType(), id);
// 记录操作日志上下文 // 记录操作日志上下文
LogRecordContext.putVariable("contactName", contact.getName()); LogRecordContext.putVariable("contactName", contact.getName());