Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into develop

This commit is contained in:
YunaiV
2025-08-04 13:23:44 +08:00
215 changed files with 1391 additions and 767 deletions

View File

@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression;
import com.alibaba.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -27,4 +27,4 @@ public class BpmProcessExpressionRespVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
}

View File

@@ -40,7 +40,7 @@ public interface ErrorCodeConstants {
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
ErrorCode PROCESS_INSTANCE_HTTP_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 请求调用失败");
ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
@@ -58,7 +58,6 @@ public interface ErrorCodeConstants {
ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
ErrorCode TASK_WITHDRAW_FAIL_PROCESS_NOT_RUNNING = new ErrorCode(1_009_005_017, "撤回失败,流程实例未运行!");

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.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<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
Set<Long> assigneeUserIds = (Set<Long>) 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);
}
}

View File

@@ -6,15 +6,15 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
@@ -26,7 +26,7 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_CALL_ERROR;
/**
* 工作流发起 HTTP 请求工具类
@@ -42,7 +42,6 @@ public class BpmHttpRequestUtils {
List<BpmSimpleModelNodeVO.HttpRequestParam> bodyParams,
Boolean handleResponse,
List<KeyValue<String, String>> response) {
RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
// 1.1 设置请求头
@@ -51,6 +50,7 @@ public class BpmHttpRequestUtils {
MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams);
// 2. 发起请求
RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
ResponseEntity<String> responseEntity = sendHttpRequest(url, headers, body, restTemplate);
// 3. 处理返回
@@ -78,27 +78,55 @@ public class BpmHttpRequestUtils {
}
}
public static void executeBpmHttpRequest(BpmProcessInstanceStatusEvent event,
String url) {
// 1.1 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
if (TenantContextHolder.getTenantId() != null) {
headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
} else {
BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getId());
if (processInstance != null) {
headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
}
}
// 1.2 设置请求体
// MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
// body.add("id", event.getId());
// body.add("processDefinitionKey", event.getProcessDefinitionKey());
// body.add("status", event.getStatus().toString());
// if (StrUtil.isNotEmpty(event.getBusinessKey())) {
// body.add("businessKey", event.getBusinessKey());
// }
// 2. 发起请求
RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
sendHttpRequest(url, headers, event, restTemplate);
}
public static ResponseEntity<String> sendHttpRequest(String url,
MultiValueMap<String, String> headers,
MultiValueMap<String, String> body,
Object body,
RestTemplate restTemplate) {
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);
HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> responseEntity;
try {
responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
log.info("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity);
log.info("[sendHttpRequest][HTTP 请求,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity);
} catch (RestClientException e) {
log.error("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
throw exception(PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR);
log.error("[sendHttpRequest][HTTP 请求,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
throw exception(PROCESS_INSTANCE_HTTP_CALL_ERROR);
}
return responseEntity;
}
public static MultiValueMap<String, String> buildHttpHeaders(ProcessInstance processInstance,
List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) {
Map<String, Object> processVariables = processInstance.getProcessVariables();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HEADER_TENANT_ID, processInstance.getTenantId());
Map<String, Object> processVariables = processInstance.getProcessVariables();
addHttpRequestParam(headers, headerSettings, processVariables);
return headers;
}

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.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<ActivityNode> 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<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));
}
// 情况二SIMPLE 设计器
@@ -563,7 +560,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
BpmSimpleModelNodeVO simpleModel = JsonUtils.parseObject(processDefinitionInfo.getSimpleModel(),
BpmSimpleModelNodeVO.class);
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));
}
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<String, Object> processVariables,
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, List<FlowElement> flowElements,
BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables,
FlowElement node, Set<String> 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());
}

View File

@@ -8,9 +8,11 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCa
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryServiceImpl;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
@@ -32,6 +34,9 @@ public class BpmCategoryServiceImplTest extends BaseDbUnitTest {
@Resource
private BpmCategoryServiceImpl categoryService;
@MockitoBean
private BpmModelService modelService;
@Resource
private BpmCategoryMapper categoryMapper;