From 8ee1fef9e49130776bd733da1b0fa93606f51396 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 19:41:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix=EF=BC=9A=E3=80=90crm=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AE=A1=E7=90=86=E3=80=91=E8=81=94=E7=B3=BB=E4=BA=BA?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=B6=EF=BC=8C=E6=A0=A1=E9=AA=8C=E6=9D=83?= =?UTF-8?q?=E9=99=90=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/crm/service/contact/CrmContactServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 958fc7520d..c3855f70aa 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()); From 4fbdd3dc5cc87005bdfe7451b30c4c72c7567235 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 20:56:25 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91=E5=85=BC=E5=AE=B9=20bpmn=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E4=B8=8B=EF=BC=8C=E4=BD=BF=E7=94=A8=20START?= =?UTF-8?q?=5FUSER=5FNODE=5FID=20=E7=9A=84=E9=A2=84=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../task/BpmProcessInstanceServiceImpl.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) 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 30adda6341..2adc2a05dd 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; @@ -415,7 +414,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 +556,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 +565,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 +621,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 +638,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()); } From 2653b614d17e1b2e60b692738d20644a04979c87 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 2 Aug 2025 22:08:39 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91=E5=AE=A1=E6=89=B9=E4=BA=BA=E8=87=AA?= =?UTF-8?q?=E9=80=89=E6=97=B6=EF=BC=8C=E7=9B=B8=E5=90=8C=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bpm/service/task/BpmProcessInstanceServiceImpl.java | 5 ----- 1 file changed, 5 deletions(-) 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 2adc2a05dd..b4a871e145 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 @@ -220,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, From 192bbf6e929bf916e6d3a8058c8b318206541475 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 3 Aug 2025 19:16:37 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix=EF=BC=9A=E3=80=90bpm=20=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E3=80=91BpmSequentialMultiInstanceBehavior?= =?UTF-8?q?=20=E5=85=BC=E5=AE=B9=20CallActivity=E3=80=81SubProcess=20?= =?UTF-8?q?=E5=9C=BA=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BpmParallelMultiInstanceBehavior.java | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) 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); + } + } From 3d68072584e89246942ef1307c338d1a8e4044f5 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 3 Aug 2025 19:45:55 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix=EF=BC=9A=E3=80=90framework=20=E5=85=A8?= =?UTF-8?q?=E5=B1=80=E3=80=91GlobalExceptionHandler=20=E5=A4=84=E7=90=86?= =?UTF-8?q?=20guava=20UncheckedExecutionException=20=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=EF=BC=8Chttps://t.zsxq.com/UszdH?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yudao-spring-boot-starter-web/pom.xml | 8 ++++++- .../core/handler/GlobalExceptionHandler.java | 21 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/yudao-framework/yudao-spring-boot-starter-web/pom.xml b/yudao-framework/yudao-spring-boot-starter-web/pom.xml index d04db57c47..92ebc918f0 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 627a5ea784..9f2d6bd087 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 @@ -5,7 +5,6 @@ import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.servlet.JakartaServletUtil; import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi; import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO; import cn.iocoder.yudao.framework.common.exception.ServiceException; @@ -23,6 +22,7 @@ import jakarta.validation.ConstraintViolationException; import jakarta.validation.ValidationException; 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; @@ -111,6 +111,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); } @@ -266,6 +269,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 * @@ -344,12 +357,12 @@ public class GlobalExceptionHandler { errorLog.setApplicationName(applicationName); errorLog.setRequestUrl(request.getRequestURI()); Map requestParams = MapUtil.builder() - .put("query", JakartaServletUtil.getParamMap(request)) - .put("body", JakartaServletUtil.getBody(request)).build(); + .put("query", ServletUtils.getParamMap(request)) + .put("body", ServletUtils.getBody(request)).build(); errorLog.setRequestParams(JsonUtils.toJsonString(requestParams)); errorLog.setRequestMethod(request.getMethod()); errorLog.setUserAgent(ServletUtils.getUserAgent(request)); - errorLog.setUserIp(JakartaServletUtil.getClientIP(request)); + errorLog.setUserIp(ServletUtils.getClientIP(request)); errorLog.setExceptionTime(LocalDateTime.now()); }