发布 v1.1.0 #2
@@ -1732,7 +1732,47 @@ public class Chain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅在工作流处于暂停状态时恢复执行。
|
||||||
|
*
|
||||||
|
* <p>状态判断与恢复动作在同一个实例锁内完成,避免并发恢复请求重复注入变量
|
||||||
|
* 或把终态实例重新改为运行中。</p>
|
||||||
|
*
|
||||||
|
* @param variables 恢复时注入的变量
|
||||||
|
* @return 本次是否完成了暂停态到运行态的转换
|
||||||
|
*/
|
||||||
|
public boolean resumeIfSuspended(Map<String, Object> variables) {
|
||||||
|
return executeWithLock(
|
||||||
|
stateInstanceId,
|
||||||
|
10L,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
() -> {
|
||||||
|
ChainState current =
|
||||||
|
chainStateRepository.load(stateInstanceId);
|
||||||
|
if (current == null
|
||||||
|
|| current.getStatus() != ChainStatus.SUSPEND) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
resumeSuspended(variables);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 恢复暂停中的工作流。
|
||||||
|
*
|
||||||
|
* @param variables 恢复时注入的变量
|
||||||
|
*/
|
||||||
public void resume(Map<String, Object> variables) {
|
public void resume(Map<String, Object> variables) {
|
||||||
|
resumeIfSuspended(variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在调用方持有实例锁且已确认暂停状态后执行恢复动作。
|
||||||
|
*
|
||||||
|
* @param variables 恢复时注入的变量
|
||||||
|
*/
|
||||||
|
private void resumeSuspended(Map<String, Object> variables) {
|
||||||
ChainState newState = updateStateSafely(state -> {
|
ChainState newState = updateStateSafely(state -> {
|
||||||
if (variables != null) {
|
if (variables != null) {
|
||||||
state.getMemory().putAll(variables);
|
state.getMemory().putAll(variables);
|
||||||
|
|||||||
@@ -896,6 +896,82 @@ public class ChainExecutor {
|
|||||||
chain.resume(variables);
|
chain.resume(variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅在工作流实例处于暂停状态时恢复执行。
|
||||||
|
*
|
||||||
|
* <p>状态判断和恢复由 {@link Chain} 在同一个实例锁内完成,可安全处理并发恢复请求。</p>
|
||||||
|
*
|
||||||
|
* @param stateInstanceId 工作流实例 ID
|
||||||
|
* @param variables 恢复时注入的变量
|
||||||
|
* @return 本次是否完成了暂停态到运行态的转换
|
||||||
|
*/
|
||||||
|
public boolean resumeAsyncIfSuspended(
|
||||||
|
String stateInstanceId,
|
||||||
|
Map<String, Object> variables) {
|
||||||
|
ChainState state = chainStateRepository.load(stateInstanceId);
|
||||||
|
if (state == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ChainDefinition definition = getDefinitionForInstance(state);
|
||||||
|
if (definition == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Chain chain = configureChain(
|
||||||
|
definition,
|
||||||
|
state.getInstanceId(),
|
||||||
|
state);
|
||||||
|
return chain.resumeIfSuspended(variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工作流实例启动时定义快照中的节点名称。
|
||||||
|
*
|
||||||
|
* @param stateInstanceId 工作流实例 ID
|
||||||
|
* @return 按定义顺序排列的节点 ID 与名称;实例或定义不存在时返回空映射
|
||||||
|
*/
|
||||||
|
public Map<String, String> getInstanceNodeNames(
|
||||||
|
String stateInstanceId) {
|
||||||
|
ChainState state = chainStateRepository.load(stateInstanceId);
|
||||||
|
if (state == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return getInstanceNodeNames(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用调用方已经加载的状态获取实例定义快照中的节点名称。
|
||||||
|
*
|
||||||
|
* @param state 已加载的工作流状态
|
||||||
|
* @return 按定义顺序排列的节点 ID 与名称;状态或定义不存在时返回空映射
|
||||||
|
*/
|
||||||
|
public Map<String, String> getInstanceNodeNames(
|
||||||
|
ChainState state) {
|
||||||
|
if (state == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
ChainDefinition definition = getDefinitionForInstance(state);
|
||||||
|
if (definition == null
|
||||||
|
|| definition.getNodes() == null
|
||||||
|
|| definition.getNodes().isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
Map<String, String> nodeNames = new LinkedHashMap<>();
|
||||||
|
for (Node node : definition.getNodes()) {
|
||||||
|
if (node == null
|
||||||
|
|| node.getId() == null
|
||||||
|
|| node.getId().isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String nodeName = node.getName();
|
||||||
|
nodeNames.put(
|
||||||
|
node.getId(),
|
||||||
|
nodeName == null || nodeName.isBlank()
|
||||||
|
? node.getId()
|
||||||
|
: nodeName);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(nodeNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Chain createChain(String definitionId) {
|
private Chain createChain(String definitionId) {
|
||||||
ChainDefinition definition = definitionRepository.getChainDefinitionById(definitionId);
|
ChainDefinition definition = definitionRepository.getChainDefinitionById(definitionId);
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.easyagents.flow.core.test;
|
||||||
|
|
||||||
|
import com.easyagents.flow.core.chain.ChainDefinition;
|
||||||
|
import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository;
|
||||||
|
import com.easyagents.flow.core.chain.repository.InMemoryNodeStateRepository;
|
||||||
|
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
|
||||||
|
import com.easyagents.flow.core.chain.runtime.InMemoryTriggerStore;
|
||||||
|
import com.easyagents.flow.core.chain.runtime.TriggerScheduler;
|
||||||
|
import com.easyagents.flow.core.node.StartNode;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ChainExecutor} 实例定义元数据查询测试。
|
||||||
|
*/
|
||||||
|
public class ChainExecutorInstanceMetadataTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证节点名称来自实例启动时可恢复的定义快照。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldResolveNodeNamesFromInstanceDefinition() {
|
||||||
|
ChainDefinition definition = new ChainDefinition();
|
||||||
|
definition.setId("metadata-definition");
|
||||||
|
StartNode start = new StartNode();
|
||||||
|
start.setId("start");
|
||||||
|
start.setName("开始节点");
|
||||||
|
definition.setNodes(Collections.singletonList(start));
|
||||||
|
definition.setEdges(Collections.emptyList());
|
||||||
|
|
||||||
|
InMemoryChainStateRepository stateRepository =
|
||||||
|
new InMemoryChainStateRepository();
|
||||||
|
stateRepository.create("metadata-instance")
|
||||||
|
.setChainDefinitionId(definition.getId());
|
||||||
|
ScheduledExecutorService schedulerPool =
|
||||||
|
Executors.newSingleThreadScheduledExecutor();
|
||||||
|
ExecutorService workerPool =
|
||||||
|
Executors.newSingleThreadExecutor();
|
||||||
|
TriggerScheduler scheduler = new TriggerScheduler(
|
||||||
|
new InMemoryTriggerStore(),
|
||||||
|
schedulerPool,
|
||||||
|
workerPool,
|
||||||
|
1_000L);
|
||||||
|
ChainExecutor executor = new ChainExecutor(
|
||||||
|
ignored -> definition,
|
||||||
|
stateRepository,
|
||||||
|
new InMemoryNodeStateRepository(),
|
||||||
|
scheduler);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, String> nodeNames =
|
||||||
|
executor.getInstanceNodeNames(
|
||||||
|
"metadata-instance");
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
Map.of("start", "开始节点"),
|
||||||
|
nodeNames);
|
||||||
|
} finally {
|
||||||
|
scheduler.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.easyagents.flow.core.test;
|
||||||
|
|
||||||
|
import com.easyagents.flow.core.chain.Chain;
|
||||||
|
import com.easyagents.flow.core.chain.ChainDefinition;
|
||||||
|
import com.easyagents.flow.core.chain.ChainStatus;
|
||||||
|
import com.easyagents.flow.core.chain.EventManager;
|
||||||
|
import com.easyagents.flow.core.chain.repository.InMemoryChainStateRepository;
|
||||||
|
import com.easyagents.flow.core.chain.repository.InMemoryNodeStateRepository;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Chain} 暂停恢复状态守卫测试。
|
||||||
|
*/
|
||||||
|
public class ChainResumeGuardTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证只有暂停中的实例可以恢复,重复恢复不会再次注入变量。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldResumeOnlyOnceFromSuspendedState() {
|
||||||
|
InMemoryChainStateRepository stateRepository =
|
||||||
|
new InMemoryChainStateRepository();
|
||||||
|
Chain chain = createChain(stateRepository, "resume-once");
|
||||||
|
chain.suspend();
|
||||||
|
|
||||||
|
boolean resumed = chain.resumeIfSuspended(
|
||||||
|
Map.of("approved", true));
|
||||||
|
boolean resumedAgain = chain.resumeIfSuspended(
|
||||||
|
Map.of("unexpected", true));
|
||||||
|
|
||||||
|
Assert.assertTrue(resumed);
|
||||||
|
Assert.assertFalse(resumedAgain);
|
||||||
|
Assert.assertEquals(
|
||||||
|
ChainStatus.RUNNING,
|
||||||
|
stateRepository.load("resume-once").getStatus());
|
||||||
|
Assert.assertEquals(
|
||||||
|
Boolean.TRUE,
|
||||||
|
stateRepository.load("resume-once")
|
||||||
|
.getMemory()
|
||||||
|
.get("approved"));
|
||||||
|
Assert.assertFalse(
|
||||||
|
stateRepository.load("resume-once")
|
||||||
|
.getMemory()
|
||||||
|
.containsKey("unexpected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证成功终态不会被恢复操作改回运行中。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void shouldKeepTerminalStateUnchanged() {
|
||||||
|
InMemoryChainStateRepository stateRepository =
|
||||||
|
new InMemoryChainStateRepository();
|
||||||
|
Chain chain = createChain(stateRepository, "resume-terminal");
|
||||||
|
stateRepository.load("resume-terminal")
|
||||||
|
.setStatus(ChainStatus.SUCCEEDED);
|
||||||
|
|
||||||
|
boolean resumed = chain.resumeIfSuspended(
|
||||||
|
Map.of("unexpected", true));
|
||||||
|
|
||||||
|
Assert.assertFalse(resumed);
|
||||||
|
Assert.assertEquals(
|
||||||
|
ChainStatus.SUCCEEDED,
|
||||||
|
stateRepository.load("resume-terminal").getStatus());
|
||||||
|
Assert.assertFalse(
|
||||||
|
stateRepository.load("resume-terminal")
|
||||||
|
.getMemory()
|
||||||
|
.containsKey("unexpected"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建使用进程内状态仓储的最小工作流。
|
||||||
|
*
|
||||||
|
* @param stateRepository 状态仓储
|
||||||
|
* @param instanceId 工作流实例 ID
|
||||||
|
* @return 已配置工作流
|
||||||
|
*/
|
||||||
|
private Chain createChain(
|
||||||
|
InMemoryChainStateRepository stateRepository,
|
||||||
|
String instanceId) {
|
||||||
|
ChainDefinition definition = new ChainDefinition();
|
||||||
|
definition.setId("resume-definition");
|
||||||
|
definition.setNodes(Collections.emptyList());
|
||||||
|
definition.setEdges(Collections.emptyList());
|
||||||
|
Chain chain = new Chain(definition, instanceId);
|
||||||
|
chain.setChainStateRepository(stateRepository);
|
||||||
|
chain.setNodeStateRepository(
|
||||||
|
new InMemoryNodeStateRepository());
|
||||||
|
chain.setEventManager(new EventManager());
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user