瀏覽代碼

!1283 feat: 流程前后置通知
Merge pull request !1283 from Lesan/feature/bpm-流程前后置通知

芋道源码 5 月之前
父節點
當前提交
6f2e538927
共有 11 個文件被更改,包括 348 次插入224 次删除
  1. 36 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java
  2. 12 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java
  3. 6 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
  4. 158 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java
  5. 0 24
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
  6. 6 0
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java
  7. 70 25
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
  8. 29 49
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java
  9. 0 56
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmAbstractHttpRequestTrigger.java
  10. 18 10
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java
  11. 13 60
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java

+ 36 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelMetaInfoVO.java

@@ -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;
+
+    }
+
 }

+ 12 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmProcessDefinitionInfoDO.java

@@ -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;
+
 }

+ 6 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java

@@ -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());

+ 158 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java

@@ -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());
+            }
+        });
+    }
+
+}

+ 0 - 24
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java

@@ -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());
-            }
-        });
-    }
 }

+ 6 - 0
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceService.java

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

+ 70 - 25
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java

@@ -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);
+            }
         });
     }
 }

+ 29 - 49
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java

@@ -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 芋艿:待定!
     }
 }

+ 0 - 56
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmAbstractHttpRequestTrigger.java

@@ -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;
-    }
-
 }

+ 18 - 10
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmHttpCallbackTrigger.java

@@ -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);
     }
 }

+ 13 - 60
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/trigger/http/BpmSyncHttpRequestTrigger.java

@@ -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);
     }
 
 }