!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;
import cn.iocoder.yudao.framework.common.core.KeyValue;
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.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
@ -80,6 +82,12 @@ public class BpmModelMetaInfoVO {
@Schema(description = "摘要设置", example = "{}")
private SummarySetting summarySetting;
@Schema(description = "流程前置通知设置", example = "{}")
private HttpRequestSetting PreProcessNotifySetting;
@Schema(description = "流程后置通知设置", example = "{}")
private HttpRequestSetting PostProcessNotifySetting;
@Schema(description = "流程 ID 规则")
@Data
@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)
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 static final Set<FlowableEngineEventType> PROCESS_INSTANCE_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
.add(FlowableEngineEventType.PROCESS_CREATED)
.add(FlowableEngineEventType.PROCESS_COMPLETED)
.add(FlowableEngineEventType.PROCESS_CANCELLED)
.build();
@ -34,6 +35,11 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
super(PROCESS_INSTANCE_EVENTS);
}
@Override
protected void processCreated(FlowableEngineEntityEvent event) {
processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity());
}
@Override
protected void processCompleted(FlowableEngineEntityEvent event) {
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.engine.delegate.ExecutionListener;
import org.flowable.engine.delegate.TaskListener;
import org.springframework.util.MultiValueMap;
import java.util.*;
@ -1002,27 +1001,4 @@ public class SimpleModelUtils {
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);
/**
* 处理 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.BpmnVariableConstants;
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.FlowableUtils;
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.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.client.RestTemplate;
import java.util.*;
@ -120,6 +122,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
@Resource
private BpmProcessIdRedisDAO processIdRedisDAO;
@Resource
private RestTemplate restTemplate;
// ========== Query 查询相关方法 ==========
@Override
@ -148,7 +153,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
}
private Map<String, String> getFormFieldsPermission(BpmnModel bpmnModel,
String activityId, String taskId) {
String activityId, String taskId) {
// 1. 获取流程活动编号流程活动 Id 为空事从流程任务中获取流程活动 Id
if (StrUtil.isEmpty(activityId) && StrUtil.isNotEmpty(taskId)) {
activityId = Optional.ofNullable(taskService.getHistoricTask(taskId))
@ -351,15 +356,15 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
* 主要是拼接审批人的用户信息部门信息
*/
private BpmApprovalDetailRespVO buildApprovalDetail(BpmApprovalDetailReqVO reqVO,
BpmnModel bpmnModel,
ProcessDefinition processDefinition,
BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance processInstance,
Integer processInstanceStatus,
List<ActivityNode> endApprovalNodeInfos,
List<ActivityNode> runningApprovalNodeInfos,
List<ActivityNode> simulateApprovalNodeInfos,
BpmTaskRespVO todoTask) {
BpmnModel bpmnModel,
ProcessDefinition processDefinition,
BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance processInstance,
Integer processInstanceStatus,
List<ActivityNode> endApprovalNodeInfos,
List<ActivityNode> runningApprovalNodeInfos,
List<ActivityNode> simulateApprovalNodeInfos,
BpmTaskRespVO todoTask) {
// 1. 获取所有需要读取用户信息的 userIds
List<ActivityNode> approveNodes = newArrayList(
asList(endApprovalNodeInfos, runningApprovalNodeInfos, simulateApprovalNodeInfos));
@ -381,9 +386,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
* 获得已结束的活动节点们
*/
private List<ActivityNode> getEndActivityNodeList(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,
List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance historicProcessInstance, Integer processInstanceStatus,
List<HistoricActivityInstance> activities, List<HistoricTaskInstance> tasks) {
// 遍历 tasks 列表只处理已结束的 UserTask
// 为什么不通过 activities 因为加签场景下它只存在于 tasks没有 activities导致如果遍历 activities
// 的话它无法成为一个节点
@ -451,11 +456,11 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
* 获得进行中的活动节点们
*/
private List<ActivityNode> getRunApproveNodeList(Long startUserId,
BpmnModel bpmnModel,
ProcessDefinition processDefinition,
Map<String, Object> processVariables,
List<HistoricActivityInstance> activities,
List<HistoricTaskInstance> tasks) {
BpmnModel bpmnModel,
ProcessDefinition processDefinition,
Map<String, Object> processVariables,
List<HistoricActivityInstance> activities,
List<HistoricTaskInstance> tasks) {
// 构建运行中的任务子流程基于 activityId 分组
List<HistoricActivityInstance> runActivities = filterList(activities, activity -> activity.getEndTime() == null
&& (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,
BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables,
List<HistoricActivityInstance> activities) {
BpmProcessDefinitionInfoDO processDefinitionInfo,
Map<String, Object> processVariables,
List<HistoricActivityInstance> activities) {
// TODO @芋艿可优化在驳回场景下未来的预测准确性不高原因是驳回后HistoricActivityInstance
// 包括了历史的操作不是只有 startEvent 到当前节点的记录
Set<String> runActivityIds = convertSet(activities, HistoricActivityInstance::getActivityId);
@ -540,8 +545,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
}
private ActivityNode buildNotRunApproveNodeForSimple(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
BpmSimpleModelNodeVO node, Set<String> runActivityIds) {
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
BpmSimpleModelNodeVO node, Set<String> runActivityIds) {
// TODO @芋艿可优化在驳回场景下未来的预测准确性不高原因是驳回后HistoricActivityInstance
// 包括了历史的操作不是只有 startEvent 到当前节点的记录
if (runActivityIds.contains(node.getId())) {
@ -585,8 +590,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
}
private ActivityNode buildNotRunApproveNodeForBpmn(Long startUserId, BpmnModel bpmnModel,
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
FlowElement node, Set<String> runActivityIds) {
BpmProcessDefinitionInfoDO processDefinitionInfo, Map<String, Object> processVariables,
FlowElement node, Set<String> runActivityIds) {
if (runActivityIds.contains(node.getId())) {
return null;
}
@ -902,6 +907,46 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
// 3. 发送流程实例的状态事件
processInstanceEventPublisher.sendProcessInstanceResultEvent(
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;
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.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 jakarta.annotation.Resource;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.delegate.TaskListener;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.el.FixedValue;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.service.delegate.DelegateTask;
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.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
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;
/**
@ -50,46 +41,35 @@ public class BpmUserTaskListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
// 1. 获取所需基础信息
HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(delegateTask.getProcessInstanceId());
ProcessInstance processInstance = processInstanceService.getProcessInstance(delegateTask.getProcessInstanceId());
BpmSimpleModelNodeVO.ListenerHandler listenerHandler = parseListenerConfig(listenerConfig);
// 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 请求体默认参数
// 2. 发起请求
// TODO @芋艿哪些默认参数后续再调研下感觉可以搞个 task 字段把整个 delegateTask 放进去
body.add("processInstanceId", delegateTask.getProcessInstanceId());
body.add("assignee", delegateTask.getAssignee());
body.add("taskDefinitionKey", delegateTask.getTaskDefinitionKey());
body.add("taskId", delegateTask.getId());
listenerHandler.getBody()
.add(new BpmSimpleModelNodeVO.HttpRequestParam().setKey("processInstanceId")
.setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())
.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. 异步发起请求
// 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 芋艿待定
// 3. 是否需要后续操作TODO 芋艿待定
}
}

View File

@ -1,25 +1,7 @@
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 jakarta.annotation.Resource;
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 请求触发器抽象类
@ -29,42 +11,4 @@ import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTA
@Slf4j
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.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.framework.flowable.core.util.BpmHttpRequestUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
/**
* BPM HTTP 回调触发器
@ -19,6 +21,9 @@ import org.springframework.util.MultiValueMap;
@Slf4j
public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
@Resource
private RestTemplate restTemplate;
@Resource
private BpmProcessInstanceService processInstanceService;
@ -36,16 +41,19 @@ public class BpmHttpCallbackTrigger extends BpmAbstractHttpRequestTrigger {
log.error("[execute][流程({}) HTTP 回调触发器配置为空]", processInstanceId);
return;
}
// 2.1 设置请求头
// 2. 发起请求
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, setting.getHeader());
// 2.2 设置请求体
MultiValueMap<String, String> body = buildHttpBody(processInstance, setting.getBody());
// 重要回调请求 taskDefineKey 需要传给被调用方用于回调执行
body.add("taskDefineKey", setting.getCallbackTaskDefineKey());
// 3. 发起请求
sendHttpRequest(setting.getUrl(), headers, body);
setting.getBody().add(new BpmSimpleModelNodeVO.HttpRequestParam()
.setKey("taskDefineKey")
.setType(BpmHttpRequestParamTypeEnum.FIXED_VALUE.getType())
.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;
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.TriggerSetting.HttpRequestTriggerSetting;
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 com.fasterxml.jackson.core.type.TypeReference;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.client.RestTemplate;
/**
* BPM 发送同步 HTTP 请求触发器
@ -29,6 +20,9 @@ import java.util.Map;
@Slf4j
public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger {
@Resource
private RestTemplate restTemplate;
@Resource
private BpmProcessInstanceService processInstanceService;
@ -45,56 +39,15 @@ public class BpmSyncHttpRequestTrigger extends BpmAbstractHttpRequestTrigger {
log.error("[execute][流程({}) HTTP 触发器请求配置为空]", processInstanceId);
return;
}
// 2.1 设置请求头
// 2. 发起请求
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
MultiValueMap<String, String> headers = buildHttpHeaders(processInstance, setting.getHeader());
// 2.2 设置请求体
MultiValueMap<String, String> body = buildHttpBody(processInstance, setting.getBody());
// 3. 发起请求
ResponseEntity<String> responseEntity = sendHttpRequest(setting.getUrl(), headers, body);
// 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;
BpmHttpRequestUtils.executeBpmHttpRequest(processInstance,
setting.getUrl(),
setting.getHeader(),
setting.getBody(),
true, setting.getResponse(),
restTemplate,
processInstanceService);
}
}