mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-11-02 13:31:49 +08:00
update 优化 WorkflowService 增加一些常用的事件节点api方法 对外暴漏方便调用
This commit is contained in:
parent
42e7196b13
commit
b9c5975ae1
@ -3,6 +3,9 @@ package org.dromara.workflow.api;
|
||||
import org.dromara.workflow.api.domain.CompleteTaskDTO;
|
||||
import org.dromara.workflow.api.domain.StartProcessDTO;
|
||||
import org.dromara.workflow.api.domain.StartProcessReturnDTO;
|
||||
import org.dromara.workflow.api.domain.StartMessageEventDTO;
|
||||
import org.dromara.workflow.api.domain.StartTimerEventDTO;
|
||||
import org.dromara.workflow.api.domain.TriggerMessageEventDTO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -96,4 +99,48 @@ public interface WorkflowService {
|
||||
* @return 首节点办理成功返回 {@code true}
|
||||
*/
|
||||
boolean startCompleteTask(StartProcessDTO startProcess);
|
||||
|
||||
/**
|
||||
* 外部消息按流程编码和事件名称启动新实例。
|
||||
*
|
||||
* @param request 消息启动参数,业务 ID 由调用方提供
|
||||
* @return 新建流程实例 ID
|
||||
*/
|
||||
Long startByMessageEvent(StartMessageEventDTO request);
|
||||
|
||||
/**
|
||||
* 触发实例中等待的消息捕获或消息边界事件。重复投递时引擎保持实例不变。
|
||||
*
|
||||
* @param request 消息事件参数
|
||||
* @return 流程实例 ID
|
||||
*/
|
||||
Long triggerMessageEvent(TriggerMessageEventDTO request);
|
||||
|
||||
/**
|
||||
* 手动发起定时启动事件,不改变等待记录及下一次自动触发时间。
|
||||
*
|
||||
* @param request 定时启动参数,业务 ID 留空时生成数字雪花 ID
|
||||
* @return 新建流程实例 ID
|
||||
*/
|
||||
Long startByTimerEvent(StartTimerEventDTO request);
|
||||
|
||||
/**
|
||||
* 从实例级错误捕获节点处理外部错误。
|
||||
*
|
||||
* @param instanceId 流程实例 ID
|
||||
* @param errorCode 错误码,精确匹配优先于通配
|
||||
* @param errorMessage 错误描述
|
||||
* @return 错误事件已被处理时返回 true
|
||||
*/
|
||||
boolean throwInstanceErrorEvent(Long instanceId, String errorCode, String errorMessage);
|
||||
|
||||
/**
|
||||
* 从当前待办的错误边界节点处理外部错误。
|
||||
*
|
||||
* @param taskId 当前待办任务 ID
|
||||
* @param errorCode 错误码,精确匹配优先于通配
|
||||
* @param errorMessage 错误描述
|
||||
* @return 错误事件已被处理时返回 true
|
||||
*/
|
||||
boolean throwTaskErrorEvent(Long taskId, String errorCode, String errorMessage);
|
||||
}
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
package org.dromara.workflow.api.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 消息启动事件请求。
|
||||
*/
|
||||
@Data
|
||||
public class StartMessageEventDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 业务主键,调用方必须提供。 */
|
||||
private String businessId;
|
||||
/** 流程定义编码。 */
|
||||
private String flowCode;
|
||||
/** 消息启动事件名称。 */
|
||||
private String eventName;
|
||||
/** 合并到流程变量的消息数据,可为空。 */
|
||||
private Map<String, Object> payload;
|
||||
/** 操作人,留空时使用系统处理人。 */
|
||||
private String handler;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package org.dromara.workflow.api.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 手动发起定时启动事件请求。
|
||||
*/
|
||||
@Data
|
||||
public class StartTimerEventDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 业务主键,留空时生成数字雪花 ID。 */
|
||||
private String businessId;
|
||||
/** 已发布的流程定义 ID。 */
|
||||
private Long definitionId;
|
||||
/** 定时启动事件节点编码。 */
|
||||
private String nodeCode;
|
||||
/** 合并到流程变量的业务数据,可为空。 */
|
||||
private Map<String, Object> payload;
|
||||
/** 操作人,留空时使用系统处理人。 */
|
||||
private String handler;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package org.dromara.workflow.api.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 触发实例中等待的消息事件请求。
|
||||
*/
|
||||
@Data
|
||||
public class TriggerMessageEventDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 已存在的流程实例 ID。 */
|
||||
private Long instanceId;
|
||||
/** 等待中的消息捕获或消息边界事件名称。 */
|
||||
private String eventName;
|
||||
/** 合并到流程变量的消息数据,可为空。 */
|
||||
private Map<String, Object> payload;
|
||||
/** 操作人,留空时使用系统处理人。 */
|
||||
private String handler;
|
||||
}
|
||||
@ -2,13 +2,20 @@ package org.dromara.workflow.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.warm.flow.core.FlowEngine;
|
||||
import org.dromara.warm.flow.core.workflow.context.WorkflowContext;
|
||||
import org.dromara.warm.flow.orm.entity.FlowInstance;
|
||||
import org.dromara.workflow.api.WorkflowService;
|
||||
import org.dromara.workflow.api.domain.CompleteTaskDTO;
|
||||
import org.dromara.workflow.api.domain.StartMessageEventDTO;
|
||||
import org.dromara.workflow.api.domain.StartProcessDTO;
|
||||
import org.dromara.workflow.api.domain.StartProcessReturnDTO;
|
||||
import org.dromara.workflow.api.domain.StartTimerEventDTO;
|
||||
import org.dromara.workflow.api.domain.TriggerMessageEventDTO;
|
||||
import org.dromara.workflow.common.ConditionalOnEnable;
|
||||
import org.dromara.workflow.common.enums.MessageTypeEnum;
|
||||
import org.dromara.workflow.domain.FlowInstanceBizExt;
|
||||
@ -170,4 +177,54 @@ public class WorkflowServiceImpl implements WorkflowService {
|
||||
taskBo.setHandler(startProcess.getHandler());
|
||||
return flwTaskService.completeTask(taskBo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long startByMessageEvent(StartMessageEventDTO request) {
|
||||
if (request == null) {
|
||||
throw new ServiceException("消息启动参数不能为空");
|
||||
}
|
||||
return FlowEngine.insService().startByMessage(request.getBusinessId(), request.getFlowCode(),
|
||||
request.getEventName(), request.getPayload(), eventContext(request.getHandler())).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long triggerMessageEvent(TriggerMessageEventDTO request) {
|
||||
if (request == null) {
|
||||
throw new ServiceException("消息事件参数不能为空");
|
||||
}
|
||||
return FlowEngine.insService().triggerMessage(request.getInstanceId(), request.getEventName(),
|
||||
request.getPayload(), eventContext(request.getHandler())).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long startByTimerEvent(StartTimerEventDTO request) {
|
||||
if (request == null) {
|
||||
throw new ServiceException("定时启动参数不能为空");
|
||||
}
|
||||
String businessId = StringUtils.isNotBlank(request.getBusinessId())
|
||||
? request.getBusinessId().trim() : String.valueOf(IdWorker.getId());
|
||||
return FlowEngine.insService().startByTimer(businessId, request.getDefinitionId(),
|
||||
request.getNodeCode(), request.getPayload(), eventContext(request.getHandler())).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean throwInstanceErrorEvent(Long instanceId, String errorCode, String errorMessage) {
|
||||
return FlowEngine.insService().throwError(instanceId, errorCode, errorMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean throwTaskErrorEvent(Long taskId, String errorCode, String errorMessage) {
|
||||
return FlowEngine.taskService().throwError(taskId, errorCode, errorMessage);
|
||||
}
|
||||
|
||||
private WorkflowContext eventContext(String handler) {
|
||||
WorkflowContext context = new WorkflowContext();
|
||||
context.setHandler(StringUtils.isNotBlank(handler) ? handler : FlowEngine.getFlowConfig().getSystemHandler());
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,131 @@
|
||||
package org.dromara.workflow.service.impl;
|
||||
|
||||
import org.dromara.warm.flow.core.FlowEngine;
|
||||
import org.dromara.warm.flow.core.config.WarmFlow;
|
||||
import org.dromara.warm.flow.core.entity.Instance;
|
||||
import org.dromara.warm.flow.core.service.InsService;
|
||||
import org.dromara.warm.flow.core.service.TaskService;
|
||||
import org.dromara.warm.flow.core.workflow.context.WorkflowContext;
|
||||
import org.dromara.workflow.api.domain.StartMessageEventDTO;
|
||||
import org.dromara.workflow.api.domain.StartTimerEventDTO;
|
||||
import org.dromara.workflow.api.domain.TriggerMessageEventDTO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class WorkflowServiceImplEventTest {
|
||||
|
||||
private final WorkflowServiceImpl service = new WorkflowServiceImpl(null, null);
|
||||
|
||||
@Test
|
||||
void messageStartPassesPayloadAndHandler() {
|
||||
StartMessageEventDTO request = new StartMessageEventDTO();
|
||||
request.setBusinessId("order-1");
|
||||
request.setFlowCode("order_flow");
|
||||
request.setEventName("paid");
|
||||
request.setPayload(Map.of("amount", 20));
|
||||
request.setHandler("42");
|
||||
InsService insService = mock(InsService.class);
|
||||
Instance instance = mock(Instance.class);
|
||||
when(instance.getId()).thenReturn(100L);
|
||||
when(insService.startByMessage(eq("order-1"), eq("order_flow"), eq("paid"), eq(request.getPayload()),
|
||||
any(WorkflowContext.class))).thenReturn(instance);
|
||||
|
||||
try (MockedStatic<FlowEngine> engine = mockStatic(FlowEngine.class)) {
|
||||
engine.when(FlowEngine::insService).thenReturn(insService);
|
||||
assertEquals(100L, service.startByMessageEvent(request));
|
||||
ArgumentCaptor<WorkflowContext> context = ArgumentCaptor.forClass(WorkflowContext.class);
|
||||
verify(insService).startByMessage(eq("order-1"), eq("order_flow"), eq("paid"),
|
||||
eq(request.getPayload()), context.capture());
|
||||
assertEquals("42", context.getValue().getHandler());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void timerStartGeneratesNumericBusinessIdAndUsesSystemHandler() {
|
||||
StartTimerEventDTO request = new StartTimerEventDTO();
|
||||
request.setDefinitionId(10L);
|
||||
request.setNodeCode("timerStart");
|
||||
InsService insService = mock(InsService.class);
|
||||
Instance instance = mock(Instance.class);
|
||||
when(instance.getId()).thenReturn(200L);
|
||||
when(insService.startByTimer(any(String.class), eq(10L), eq("timerStart"),
|
||||
any(), any(WorkflowContext.class))).thenReturn(instance);
|
||||
WarmFlow config = mock(WarmFlow.class);
|
||||
when(config.getSystemHandler()).thenReturn("system");
|
||||
|
||||
try (MockedStatic<FlowEngine> engine = mockStatic(FlowEngine.class)) {
|
||||
engine.when(FlowEngine::insService).thenReturn(insService);
|
||||
engine.when(FlowEngine::getFlowConfig).thenReturn(config);
|
||||
assertEquals(200L, service.startByTimerEvent(request));
|
||||
ArgumentCaptor<String> businessId = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<WorkflowContext> context = ArgumentCaptor.forClass(WorkflowContext.class);
|
||||
verify(insService).startByTimer(businessId.capture(), eq(10L), eq("timerStart"),
|
||||
any(), context.capture());
|
||||
assertTrue(businessId.getValue().matches("[0-9]+"));
|
||||
assertEquals("system", context.getValue().getHandler());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void timerStartKeepsProvidedBusinessIdAndHandler() {
|
||||
StartTimerEventDTO request = new StartTimerEventDTO();
|
||||
request.setBusinessId("manual-42");
|
||||
request.setDefinitionId(10L);
|
||||
request.setNodeCode("timerStart");
|
||||
request.setHandler("operator");
|
||||
request.setPayload(Map.of("reason", "retry"));
|
||||
InsService insService = mock(InsService.class);
|
||||
Instance instance = mock(Instance.class);
|
||||
when(instance.getId()).thenReturn(201L);
|
||||
when(insService.startByTimer(eq("manual-42"), eq(10L), eq("timerStart"),
|
||||
eq(request.getPayload()), any(WorkflowContext.class))).thenReturn(instance);
|
||||
|
||||
try (MockedStatic<FlowEngine> engine = mockStatic(FlowEngine.class)) {
|
||||
engine.when(FlowEngine::insService).thenReturn(insService);
|
||||
assertEquals(201L, service.startByTimerEvent(request));
|
||||
ArgumentCaptor<WorkflowContext> context = ArgumentCaptor.forClass(WorkflowContext.class);
|
||||
verify(insService).startByTimer(eq("manual-42"), eq(10L), eq("timerStart"),
|
||||
eq(request.getPayload()), context.capture());
|
||||
assertEquals("operator", context.getValue().getHandler());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void messageAndErrorEventsDelegateToEngine() {
|
||||
TriggerMessageEventDTO request = new TriggerMessageEventDTO();
|
||||
request.setInstanceId(300L);
|
||||
request.setEventName("confirmed");
|
||||
request.setHandler("42");
|
||||
InsService insService = mock(InsService.class);
|
||||
TaskService taskService = mock(TaskService.class);
|
||||
Instance instance = mock(Instance.class);
|
||||
when(instance.getId()).thenReturn(300L);
|
||||
when(insService.triggerMessage(eq(300L), eq("confirmed"), any(), any(WorkflowContext.class)))
|
||||
.thenReturn(instance);
|
||||
when(insService.throwError(300L, "external_error", "failed")).thenReturn(true);
|
||||
when(taskService.throwError(400L, "external_error", "failed")).thenReturn(true);
|
||||
|
||||
try (MockedStatic<FlowEngine> engine = mockStatic(FlowEngine.class)) {
|
||||
engine.when(FlowEngine::insService).thenReturn(insService);
|
||||
engine.when(FlowEngine::taskService).thenReturn(taskService);
|
||||
assertEquals(300L, service.triggerMessageEvent(request));
|
||||
ArgumentCaptor<WorkflowContext> context = ArgumentCaptor.forClass(WorkflowContext.class);
|
||||
verify(insService).triggerMessage(eq(300L), eq("confirmed"), any(), context.capture());
|
||||
assertEquals("42", context.getValue().getHandler());
|
||||
assertTrue(service.throwInstanceErrorEvent(300L, "external_error", "failed"));
|
||||
assertTrue(service.throwTaskErrorEvent(400L, "external_error", "failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user