mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-11-04 14:19:44 +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.CompleteTaskDTO;
|
||||||
import org.dromara.workflow.api.domain.StartProcessDTO;
|
import org.dromara.workflow.api.domain.StartProcessDTO;
|
||||||
import org.dromara.workflow.api.domain.StartProcessReturnDTO;
|
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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -96,4 +99,48 @@ public interface WorkflowService {
|
|||||||
* @return 首节点办理成功返回 {@code true}
|
* @return 首节点办理成功返回 {@code true}
|
||||||
*/
|
*/
|
||||||
boolean startCompleteTask(StartProcessDTO startProcess);
|
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.bean.BeanUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
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.warm.flow.orm.entity.FlowInstance;
|
||||||
import org.dromara.workflow.api.WorkflowService;
|
import org.dromara.workflow.api.WorkflowService;
|
||||||
import org.dromara.workflow.api.domain.CompleteTaskDTO;
|
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.StartProcessDTO;
|
||||||
import org.dromara.workflow.api.domain.StartProcessReturnDTO;
|
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.ConditionalOnEnable;
|
||||||
import org.dromara.workflow.common.enums.MessageTypeEnum;
|
import org.dromara.workflow.common.enums.MessageTypeEnum;
|
||||||
import org.dromara.workflow.domain.FlowInstanceBizExt;
|
import org.dromara.workflow.domain.FlowInstanceBizExt;
|
||||||
@ -170,4 +177,54 @@ public class WorkflowServiceImpl implements WorkflowService {
|
|||||||
taskBo.setHandler(startProcess.getHandler());
|
taskBo.setHandler(startProcess.getHandler());
|
||||||
return flwTaskService.completeTask(taskBo);
|
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