!1283 feat: 流程前后置通知

Merge pull request !1283 from Lesan/feature/bpm-流程前后置通知
This commit is contained in:
芋道源码 2025-03-15 00:04:55 +00:00 committed by Gitee
commit 6f2e538927
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
11 changed files with 348 additions and 224 deletions

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmAutoApproveTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
@ -80,6 +82,12 @@ public class BpmModelMetaInfoVO {
@Schema(description = "摘要设置", example = "{}") @Schema(description = "摘要设置", example = "{}")
private SummarySetting summarySetting; private SummarySetting summarySetting;
@Schema(description = "流程前置通知设置", example = "{}")
private HttpRequestSetting PreProcessNotifySetting;
@Schema(description = "流程后置通知设置", example = "{}")
private HttpRequestSetting PostProcessNotifySetting;
@Schema(description = "流程 ID 规则") @Schema(description = "流程 ID 规则")
@Data @Data
@Valid @Valid
@ -132,4 +140,32 @@ public class BpmModelMetaInfoVO {
} }
@Schema(description = "http 请求通知设置", example = "{}")
@Data
public static class HttpRequestSetting {
@Schema(description = "请求路径", example = "http://127.0.0.1")
@NotEmpty(message = "请求 URL 不能为空")
@URL(message = "请求 URL 格式不正确")
private String url;
@Schema(description = "请求头参数设置", example = "[]")
@Valid
private List<BpmSimpleModelNodeVO.HttpRequestParam> header;
@Schema(description = "请求头参数设置", example = "[]")
@Valid
private List<BpmSimpleModelNodeVO.HttpRequestParam> body;
/**
* 请求返回处理设置用于修改流程表单值
* <p>
* key表示要修改的流程表单字段名(name)
* value接口返回的字段名
*/
@Schema(description = "请求返回处理设置", example = "[]")
private List<KeyValue<String, String>> response;
}
} }

View File

@ -189,4 +189,16 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
@TableField(typeHandler = JacksonTypeHandler.class) @TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.SummarySetting summarySetting; private BpmModelMetaInfoVO.SummarySetting summarySetting;
/**
* 流程前置通知设置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.HttpRequestSetting PreProcessNotifySetting;
/**
* 流程后置通知设置
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private BpmModelMetaInfoVO.HttpRequestSetting PostProcessNotifySetting;
} }

View File

@ -22,6 +22,7 @@ import java.util.Set;
public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener { public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEventListener {
public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder() public static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
.add(FlowableEngineEventType.PROCESS_CREATED)
.add(FlowableEngineEventType.PROCESS_COMPLETED) .add(FlowableEngineEventType.PROCESS_COMPLETED)
.add(FlowableEngineEventType.PROCESS_CANCELLED) .add(FlowableEngineEventType.PROCESS_CANCELLED)
.build(); .build();
@ -34,6 +35,11 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
super(PROCESS_INSTANCE_EVENTS); super(PROCESS_INSTANCE_EVENTS);
} }
@Override
protected void processCreated(FlowableEngineEntityEvent event) {
processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity());
}
@Override @Override
protected void processCompleted(FlowableEngineEntityEvent event) { protected void processCompleted(FlowableEngineEntityEvent event) {
processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity());

View File

@ -0,0 +1,158 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
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.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.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.List;
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;
/**
* 工作流发起 HTTP 请求工具类
*
* @author 芋道源码
*/
@Slf4j
public class BpmHttpRequestUtils {
public static void executeBpmHttpRequest(ProcessInstance processInstance,
String url,
List<BpmSimpleModelNodeVO.HttpRequestParam> headerParam,
List<BpmSimpleModelNodeVO.HttpRequestParam> bodyParam,
Boolean handleResponse,
List<KeyValue<String, String>> response,
RestTemplate restTemplate,
BpmProcessInstanceService processInstanceService) {
// 1.1 设置请求头
MultiValueMap<String, String> headers = BpmHttpRequestUtils.buildHttpHeaders(processInstance, headerParam);
// 1.2 设置请求体
MultiValueMap<String, String> body = BpmHttpRequestUtils.buildHttpBody(processInstance, bodyParam);
// 2. 发起请求
ResponseEntity<String> responseEntity = BpmHttpRequestUtils.sendHttpRequest(url, headers, body, restTemplate);
// 3. 处理返回
if (Boolean.TRUE.equals(handleResponse)) {
// 3.1 判断是否需要解析返回值
if (responseEntity == null || StrUtil.isEmpty(responseEntity.getBody())
|| !responseEntity.getStatusCode().is2xxSuccessful()
|| CollUtil.isEmpty(response)) {
return;
}
// 3.2 解析返回值, 返回值必须符合 CommonResult 规范
CommonResult<Map<String, Object>> respResult = JsonUtils.parseObjectQuietly(
responseEntity.getBody(), new TypeReference<>() {
});
if (respResult == null || !respResult.isSuccess()) {
return;
}
// 3.3 获取需要更新的流程变量
Map<String, Object> updateVariables = BpmHttpRequestUtils.getNeedUpdatedVariablesFromResponse(respResult.getData(), response);
// 3.4 更新流程变量
if (CollUtil.isNotEmpty(updateVariables)) {
processInstanceService.updateProcessInstanceVariables(processInstance.getId(), updateVariables);
}
}
}
public static ResponseEntity<String> sendHttpRequest(String url,
MultiValueMap<String, String> headers,
MultiValueMap<String, String> body,
RestTemplate restTemplate) {
// 3. 发起请求
HttpEntity<MultiValueMap<String, String>> 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);
} catch (RestClientException e) {
log.error("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
throw exception(PROCESS_INSTANCE_HTTP_TRIGGER_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());
addHttpRequestParam(headers, headerSettings, processVariables);
return headers;
}
public static MultiValueMap<String, String> buildHttpBody(ProcessInstance processInstance,
List<BpmSimpleModelNodeVO.HttpRequestParam> bodySettings) {
Map<String, Object> processVariables = processInstance.getProcessVariables();
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
addHttpRequestParam(body, bodySettings, processVariables);
body.add("processInstanceId", processInstance.getId());
return body;
}
/**
* 从请求返回值获取需要更新的流程变量
*
* @param result 请求返回结果
* @param responseSettings 返回设置
* @return 需要更新的流程变量
*/
public static Map<String, Object> getNeedUpdatedVariablesFromResponse(Map<String, Object> result,
List<KeyValue<String, String>> responseSettings) {
Map<String, Object> updateVariables = new HashMap<>();
if (CollUtil.isEmpty(result)) {
return updateVariables;
}
responseSettings.forEach(responseSetting -> {
if (StrUtil.isNotEmpty(responseSetting.getKey()) && result.containsKey(responseSetting.getValue())) {
updateVariables.put(responseSetting.getKey(), result.get(responseSetting.getValue()));
}
});
return updateVariables;
}
/**
* 添加 HTTP 请求参数请求头或者请求体
*
* @param params HTTP 请求参数
* @param paramSettings HTTP 请求参数设置
* @param processVariables 流程变量
*/
public static void addHttpRequestParam(MultiValueMap<String, String> params,
List<BpmSimpleModelNodeVO.HttpRequestParam> paramSettings,
Map<String, Object> processVariables) {
if (CollUtil.isEmpty(paramSettings)) {
return;
}
paramSettings.forEach(item -> {
if (item.getType().equals(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())) {
params.add(item.getKey(), item.getValue());
} else if (item.getType().equals(BpmHttpRequestParamTypeEnum.FROM_FORM.getType())) {
params.add(item.getKey(), processVariables.get(item.getValue()).toString());
}
});
}
}

View File

@ -21,7 +21,6 @@ import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.engine.delegate.ExecutionListener; import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.engine.delegate.TaskListener; import org.flowable.engine.delegate.TaskListener;
import org.springframework.util.MultiValueMap;
import java.util.*; import java.util.*;
@ -1002,27 +1001,4 @@ public class SimpleModelUtils {
return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting)); return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting));
} }
// TODO @芋艿要不要优化下抽个 HttpUtils
/**
* 添加 HTTP 请求参数请求头或者请求体
*
* @param params HTTP 请求参数
* @param paramSettings HTTP 请求参数设置
* @param processVariables 流程变量
*/
public static void addHttpRequestParam(MultiValueMap<String, String> params,
List<BpmSimpleModelNodeVO.HttpRequestParam> paramSettings,
Map<String, Object> processVariables) {
if (CollUtil.isEmpty(paramSettings)) {
return;
}
paramSettings.forEach(item -> {
if (item.getType().equals(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())) {
params.add(item.getKey(), item.getValue());
} else if (item.getType().equals(BpmHttpRequestParamTypeEnum.FROM_FORM.getType())) {
params.add(item.getKey(), processVariables.get(item.getValue()).toString());
}
});
}
} }

View File

@ -182,4 +182,10 @@ public interface BpmProcessInstanceService {
*/ */
void processProcessInstanceCompleted(ProcessInstance instance); void processProcessInstanceCompleted(ProcessInstance instance);
/**
* 处理 ProcessInstance 开始事件例如说流程前置通知
*
* @param instance 流程任务
*/
void processProcessInstanceCreated(ProcessInstance instance);
} }

View File

@ -34,6 +34,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
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 cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
@ -62,6 +63,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.client.RestTemplate;
import java.util.*; import java.util.*;
@ -120,6 +122,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
@Resource @Resource
private BpmProcessIdRedisDAO processIdRedisDAO; private BpmProcessIdRedisDAO processIdRedisDAO;
@Resource
private RestTemplate restTemplate;
// ========== Query 查询相关方法 ========== // ========== Query 查询相关方法 ==========
@Override @Override
@ -148,7 +153,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
} }
private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel, private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,
String activityId, String taskId) { String activityId, String taskId) {
// 1. 获取流程活动编号流程活动 Id 为空事从流程任务中获取流程活动 Id // 1. 获取流程活动编号流程活动 Id 为空事从流程任务中获取流程活动 Id
if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) { if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {
activityId = Optional.ofNullable(taskService.getHistoricTask(taskId)) activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))
@ -351,15 +356,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
* 主要是拼接审批人的用户信息部门信息 * 主要是拼接审批人的用户信息部门信息
*/ */
private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO, private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO,
BpmnModel bpmnModel, BpmnModel bpmnModel,
ProcessDefinition processDefinition, ProcessDefinition processDefinition,
BpmProcessDefinitionInfoDO processDefinitionInfo, BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance processInstance, HistoricProcessInstance processInstance,
Integer processInstanceStatus, Integer processInstanceStatus,
List<ActivityNode> endApprovalNodeInfos, List<ActivityNode> endApprovalNodeInfos,
List<ActivityNode> runningApprovalNodeInfos, List<ActivityNode> runningApprovalNodeInfos,
List<ActivityNode> simulateApprovalNodeInfos, List<ActivityNode> simulateApprovalNodeInfos,
BpmTaskRespVO todoTask) { BpmTaskRespVO todoTask) {
// 1. 获取所有需要读取用户信息的 userIds // 1. 获取所有需要读取用户信息的 userIds
List<ActivityNode> approveNodes = newArrayList( List<ActivityNode> approveNodes = newArrayList(
asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos)); asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));
@ -381,9 +386,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
* 获得已结束的活动节点们 * 获得已结束的活动节点们
*/ */
private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel, private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus, HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,
List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) { List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
// 遍历 tasks 列表只处理已结束的 UserTask // 遍历 tasks 列表只处理已结束的 UserTask
// 为什么不通过 activities 因为加签场景下它只存在于 tasks没有 activities导致如果遍历 activities // 为什么不通过 activities 因为加签场景下它只存在于 tasks没有 activities导致如果遍历 activities
// 的话它无法成为一个节点 // 的话它无法成为一个节点
@ -451,11 +456,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
* 获得进行中的活动节点们 * 获得进行中的活动节点们
*/ */
private List<ActivityNode> getRunApproveNodeList(Long startUserId, private List<ActivityNode> getRunApproveNodeList(Long startUserId,
BpmnModel bpmnModel, BpmnModel bpmnModel,
ProcessDefinition processDefinition, ProcessDefinition processDefinition,
Map<String, Object> processVariables, Map<String, Object> processVariables,
List<HistoricActivityInstance> activities, List<HistoricActivityInstance> activities,
List<HistoricTaskInstance> tasks) { List<HistoricTaskInstance> tasks) {
// 构建运行中的任务子流程基于 activityId 分组 // 构建运行中的任务子流程基于 activityId 分组
List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null
&& (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER, ELEMENT_CALL_ACTIVITY))); && (StrUtil.equalsAny(activity.getActivityType(), ELEMENT_TASK_USER, ELEMENT_CALL_ACTIVITY)));
@ -516,9 +521,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
* 获得预测未来的活动节点们 * 获得预测未来的活动节点们
*/ */
private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel, private List<ActivityNode> getSimulateApproveNodeList(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables, Map<String, Object> processVariables,
List<HistoricActivityInstance> activities) { List<HistoricActivityInstance> activities) {
// TODO @芋艿可优化在驳回场景下未来的预测准确性不高原因是驳回后HistoricActivityInstance // TODO @芋艿可优化在驳回场景下未来的预测准确性不高原因是驳回后HistoricActivityInstance
// 包括了历史的操作不是只有 startEvent 到当前节点的记录 // 包括了历史的操作不是只有 startEvent 到当前节点的记录
Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId); Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);
@ -540,8 +545,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
} }
private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel, private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables, BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
BpmSimpleModelNodeVO node, Set<String> runActivityIds) { BpmSimpleModelNodeVO node, Set<String> runActivityIds) {
// TODO @芋艿可优化在驳回场景下未来的预测准确性不高原因是驳回后HistoricActivityInstance // TODO @芋艿可优化在驳回场景下未来的预测准确性不高原因是驳回后HistoricActivityInstance
// 包括了历史的操作不是只有 startEvent 到当前节点的记录 // 包括了历史的操作不是只有 startEvent 到当前节点的记录
if (runActivityIds.contains(node.getId())) { if (runActivityIds.contains(node.getId())) {
@ -585,8 +590,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
} }
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel, private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,
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;
} }
@ -902,6 +907,46 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 3. 发送流程实例的状态事件 // 3. 发送流程实例的状态事件
processInstanceEventPublisher.sendProcessInstanceResultEvent( processInstanceEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status));
// 4. 流程后置通知
if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.
getProcessDefinitionInfo(instance.getProcessDefinitionId());
if (ObjUtil.isNotNull(processDefinitionInfo) &&
ObjUtil.isNotNull(processDefinitionInfo.getPostProcessNotifySetting())) {
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getPostProcessNotifySetting();
BpmHttpRequestUtils.executeBpmHttpRequest(instance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
true, setting.getResponse(),
restTemplate,
this);
}
}
});
}
@Override
public void processProcessInstanceCreated(ProcessInstance instance) {
// 注意需要基于 instance 设置租户编号避免 Flowable 内部异步时丢失租户编号
FlowableUtils.execute(instance.getTenantId(), () -> {
// 流程前置通知
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.
getProcessDefinitionInfo(instance.getProcessDefinitionId());
if (ObjUtil.isNotNull(processDefinitionInfo) &&
ObjUtil.isNotNull(processDefinitionInfo.getPreProcessNotifySetting())) {
BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getPreProcessNotifySetting();
BpmHttpRequestUtils.executeBpmHttpRequest(instance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
true, setting.getResponse(),
restTemplate,
this);
}
}); });
} }
} }

View File

@ -1,29 +1,20 @@
package cn.iocoder.yudao.module.bpm.service.task.listener; package cn.iocoder.yudao.module.bpm.service.task.listener;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils; import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.TaskListener; import org.flowable.engine.delegate.TaskListener;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.el.FixedValue; import org.flowable.engine.impl.el.FixedValue;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.service.delegate.DelegateTask; import org.flowable.task.service.delegate.DelegateTask;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import java.util.Map;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseListenerConfig; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseListenerConfig;
/** /**
@ -50,46 +41,35 @@ public class BpmUserTaskListener implements TaskListener {
@Override @Override
public void notify(DelegateTask delegateTask) { public void notify(DelegateTask delegateTask) {
// 1. 获取所需基础信息 // 1. 获取所需基础信息
HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(delegateTask.getProcessInstanceId()); ProcessInstance processInstance = processInstanceService.getProcessInstance(delegateTask.getProcessInstanceId());
BpmSimpleModelNodeVO.ListenerHandler listenerHandler = parseListenerConfig(listenerConfig); BpmSimpleModelNodeVO.ListenerHandler listenerHandler = parseListenerConfig(listenerConfig);
// 2. 获取请求头和请求体 // 2. 发起请求
Map<String, Object> processVariables = processInstance.getProcessVariables();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
SimpleModelUtils.addHttpRequestParam(headers, listenerHandler.getHeader(), processVariables);
SimpleModelUtils.addHttpRequestParam(body, listenerHandler.getBody(), processVariables);
// 2.1 请求头默认参数
if (StrUtil.isNotEmpty(delegateTask.getTenantId())) {
headers.add(HEADER_TENANT_ID, delegateTask.getTenantId());
}
// 2.2 请求体默认参数
// TODO @芋艿哪些默认参数后续再调研下感觉可以搞个 task 字段把整个 delegateTask 放进去 // TODO @芋艿哪些默认参数后续再调研下感觉可以搞个 task 字段把整个 delegateTask 放进去
body.add("processInstanceId", delegateTask.getProcessInstanceId()); listenerHandler.getBody()
body.add("assignee", delegateTask.getAssignee()); .add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("processInstanceId")
body.add("taskDefinitionKey", delegateTask.getTaskDefinitionKey()); .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())
body.add("taskId", delegateTask.getId()); .setValue(delegateTask.getProcessInstanceId()));
listenerHandler.getBody()
.add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("assignee")
.setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())
.setValue(delegateTask.getAssignee()));
listenerHandler.getBody()
.add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskDefinitionKey")
.setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())
.setValue(delegateTask.getTaskDefinitionKey()));
listenerHandler.getBody()
.add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("taskId")
.setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())
.setValue(delegateTask.getId()));
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
listenerHandler.getPath(),
listenerHandler.getHeader(),
listenerHandler.getBody(),
false, null,
restTemplate,
processInstanceService);
// 3. 异步发起请求 // 3. 是否需要后续操作TODO 芋艿待定
// TODO @芋艿确认要同步还是异步
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);
try {
ResponseEntity<String> responseEntity = restTemplate.exchange(listenerHandler.getPath(), HttpMethod.POST,
requestEntity, String.class);
log.info("[notify][监听器:{},事件类型:{},请求头:{},请求体:{},响应结果:{}]",
DELEGATE_EXPRESSION,
delegateTask.getEventName(),
headers,
body,
responseEntity);
} catch (RestClientException e) {
log.error("[error][监听器:{},事件类型:{},请求头:{},请求体:{},请求出错:{}]",
DELEGATE_EXPRESSION,
delegateTask.getEventName(),
headers,
body,
e.getMessage());
}
// 4. 是否需要后续操作TODO 芋艿待定
} }
} }

View File

@ -1,25 +1,7 @@
package cn.iocoder.yudao.module.bpm.service.task.trigger.http; package cn.iocoder.yudao.module.bpm.service.task.trigger.http;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger; import cn.iocoder.yudao.module.bpm.service.task.trigger.BpmTrigger;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; 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.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.util.List;
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;
/** /**
* BPM 发送 HTTP 请求触发器抽象类 * BPM 发送 HTTP 请求触发器抽象类
@ -29,42 +11,4 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTA
@Slf4j @Slf4j
public abstract class BpmAbstractHttpRequestTrigger implements BpmTrigger { public abstract class BpmAbstractHttpRequestTrigger implements BpmTrigger {
@Resource
private RestTemplate restTemplate;
protected ResponseEntity<String> sendHttpRequest(String url,
MultiValueMap<String, String> headers,
MultiValueMap<String, String> body) {
// TODO @芋艿要不要抽象一个 Http 请求的工具类方便复用呢
// 3. 发起请求
HttpEntity<MultiValueMap<String, String>> 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);
} catch (RestClientException e) {
log.error("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
throw exception(PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR);
}
return responseEntity;
}
protected 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());
SimpleModelUtils.addHttpRequestParam(headers, headerSettings, processVariables);
return headers;
}
protected MultiValueMap<String, String> buildHttpBody(ProcessInstance processInstance,
List<BpmSimpleModelNodeVO.HttpRequestParam> bodySettings) {
Map<String, Object> processVariables = processInstance.getProcessVariables();
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
SimpleModelUtils.addHttpRequestParam(body, bodySettings, processVariables);
body.add("processInstanceId", processInstance.getId());
return body;
}
} }

View File

@ -2,13 +2,15 @@ package cn.iocoder.yudao.module.bpm.service.task.trigger.http;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 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.enums.definition.BpmTriggerTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate;
/** /**
* BPM HTTP 回调触发器 * BPM HTTP 回调触发器
@ -19,6 +21,9 @@ import org.springframework.util.MultiValueMap;
@Slf4j @Slf4j
public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger { public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
@Resource
private RestTemplate restTemplate;
@Resource @Resource
private BpmProcessInstanceService processInstanceService; private BpmProcessInstanceService processInstanceService;
@ -36,16 +41,19 @@ public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
log.error("[execute][流程({}) HTTP 回调触发器配置为空]", processInstanceId); log.error("[execute][流程({}) HTTP 回调触发器配置为空]", processInstanceId);
return; return;
} }
// 2. 发起请求
// 2.1 设置请求头
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, setting.getHeader());
// 2.2 设置请求体
MultiValueMap<String, String> body = buildHttpBody(processInstance, setting.getBody());
// 重要回调请求 taskDefineKey 需要传给被调用方用于回调执行 // 重要回调请求 taskDefineKey 需要传给被调用方用于回调执行
body.add("taskDefineKey", setting.getCallbackTaskDefineKey()); setting.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam()
.setKey("taskDefineKey")
// 3. 发起请求 .setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())
sendHttpRequest(setting.getUrl(), headers, body); .setValue(setting.getCallbackTaskDefineKey()));
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
false, null,
restTemplate,
processInstanceService);
} }
} }

View File

@ -1,24 +1,15 @@
package cn.iocoder.yudao.module.bpm.service.task.trigger.http; package cn.iocoder.yudao.module.bpm.service.task.trigger.http;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
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.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.TriggerSetting.HttpRequestTriggerSetting;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmTriggerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* BPM 发送同步 HTTP 请求触发器 * BPM 发送同步 HTTP 请求触发器
@ -29,6 +20,9 @@ import java.util.Map;
@Slf4j @Slf4j
public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger { public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger {
@Resource
private RestTemplate restTemplate;
@Resource @Resource
private BpmProcessInstanceService processInstanceService; private BpmProcessInstanceService processInstanceService;
@ -45,56 +39,15 @@ public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger {
log.error("[execute][流程({}) HTTP 触发器请求配置为空]", processInstanceId); log.error("[execute][流程({}) HTTP 触发器请求配置为空]", processInstanceId);
return; return;
} }
// 2. 发起请求
// 2.1 设置请求头
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, setting.getHeader()); BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
// 2.2 设置请求体 setting.getUrl(),
MultiValueMap<String, String> body = buildHttpBody(processInstance, setting.getBody()); setting.getHeader(),
setting.getBody(),
// 3. 发起请求 true, setting.getResponse(),
ResponseEntity<String> responseEntity = sendHttpRequest(setting.getUrl(), headers, body); restTemplate,
processInstanceService);
// 4.1 判断是否需要解析返回值
if (responseEntity == null || StrUtil.isEmpty(responseEntity.getBody())
|| !responseEntity.getStatusCode().is2xxSuccessful()
|| CollUtil.isEmpty(setting.getResponse())) {
return;
}
// 4.2 解析返回值, 返回值必须符合 CommonResult 规范
CommonResult<Map<String, Object>> respResult = JsonUtils.parseObjectQuietly(
responseEntity.getBody(), new TypeReference<>() {
});
if (respResult == null || !respResult.isSuccess()) {
return;
}
// 4.3 获取需要更新的流程变量
Map<String, Object> updateVariables = getNeedUpdatedVariablesFromResponse(respResult.getData(), setting.getResponse());
// 4.4 更新流程变量
if (CollUtil.isNotEmpty(updateVariables)) {
processInstanceService.updateProcessInstanceVariables(processInstanceId, updateVariables);
}
}
/**
* 从请求返回值获取需要更新的流程变量
*
* @param result 请求返回结果
* @param responseSettings 返回设置
* @return 需要更新的流程变量
*/
private Map<String, Object> getNeedUpdatedVariablesFromResponse(Map<String, Object> result,
List<KeyValue<String, String>> responseSettings) {
Map<String, Object> updateVariables = new HashMap<>();
if (CollUtil.isEmpty(result)) {
return updateVariables;
}
responseSettings.forEach(responseSetting -> {
if (StrUtil.isNotEmpty(responseSetting.getKey()) && result.containsKey(responseSetting.getValue())) {
updateVariables.put(responseSetting.getKey(), result.get(responseSetting.getValue()));
}
});
return updateVariables;
} }
} }