1
0

Compare commits

..

18 Commits

Author SHA1 Message Date
e1d50571d1 增加分页查询 2025-01-24 11:01:38 +08:00
6ef29b05c5 优化监听器事件,移除错误的流程始末事件 2025-01-08 10:39:42 +08:00
96dfcea7b8 修复node没有随instance进入下一轮 2025-01-07 18:25:38 +08:00
b48903c8af 增加Listener的辅助代码 2025-01-07 18:23:54 +08:00
dcf91a7e1e 修复TERMINAL操作没有出发流程结束事件 2025-01-07 17:25:51 +08:00
77567d997d 优化代码 2025-01-07 17:14:32 +08:00
1cc8bb5618 修复时间字段的传递 2025-01-07 17:09:51 +08:00
d8f58ea2b5 调整Listener参数 2025-01-07 14:50:02 +08:00
bf0e7e47be 增加一个额外的字段用于额外的查询条件 2025-01-07 14:31:24 +08:00
b56555923a 优化Spring环境下的类型 2025-01-07 14:22:01 +08:00
53c4be6e3e 增加String转换 2025-01-07 14:21:43 +08:00
935793d5b6 修复COMPLETE事件发生时机不对 2025-01-07 14:21:32 +08:00
2054f5802b 调整Accessor参数 2025-01-07 14:20:37 +08:00
28ef67d630 ERROR改为TERMINAL更符合语义 2025-01-07 14:19:29 +08:00
2c75cad680 优化存储读写,增加一些查询接口 2025-01-07 14:18:35 +08:00
dddc9d7171 优化监听器 2025-01-07 09:56:56 +08:00
465cc8cd3f 优化spring boot自动配置,增加manager初始化方法 2025-01-06 10:32:54 +08:00
82af04a4c6 增加spring boot自动配置 2025-01-03 17:47:39 +08:00
25 changed files with 463 additions and 66 deletions

View File

@@ -0,0 +1,56 @@
package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.repository.FlowableRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.FlowableHistoryRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.FlowableInstanceRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.FlowableNodeRepository;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
/**
* 自动配置
*
* @author lanyuanxiaoyao
* @version 20250103
*/
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Configuration
@EnableJpaRepositories("com.lanyuanxiaoyao.flowable.jpa.repository")
@EnableConfigurationProperties(SpringFlowableConfiguration.class)
public class SpringFlowableAutoConfiguration {
@Bean
@ConditionalOnMissingBean(FlowableRepository.class)
public SpringFlowableRepository flowableRepository(
FlowableNodeRepository flowableNodeRepository,
FlowableInstanceRepository flowableInstanceRepository,
FlowableHistoryRepository flowableHistoryRepository
) {
return new SpringFlowableRepository(flowableNodeRepository, flowableInstanceRepository, flowableHistoryRepository);
}
@Bean
@ConditionalOnBean(FlowableRepository.class)
@ConditionalOnMissingBean(FlowableManager.class)
public SpringFlowableManager flowableManager(
SpringFlowableConfiguration configuration,
SpringFlowableRepository repository,
ApplicationContext applicationContext
) {
SpringFlowableManager manager = new SpringFlowableManager(configuration, repository, applicationContext);
Map<String, SpringFlowableManagerInitializer> initializerMap = applicationContext.getBeansOfType(SpringFlowableManagerInitializer.class);
for (SpringFlowableManagerInitializer initializer : initializerMap.values()) {
manager = initializer.initial(manager);
}
return manager;
}
}

View File

@@ -3,7 +3,6 @@ package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 配置类
@@ -12,7 +11,6 @@ import org.springframework.context.annotation.Configuration;
* @version 20250103
*/
@ToString(callSuper = true)
@Configuration
@ConfigurationProperties("flowable")
public class SpringFlowableConfiguration extends FlowableConfiguration {
}

View File

@@ -1,29 +1,71 @@
package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.repository.FlowableRepository;
import com.lanyuanxiaoyao.flowable.jpa.entity.FlowableHistory;
import com.lanyuanxiaoyao.flowable.jpa.entity.FlowableInstance;
import com.lanyuanxiaoyao.flowable.jpa.entity.FlowableNode;
import java.util.List;
import java.util.UUID;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
/**
* @author lanyuanxiaoyao
* @version 20241231
*/
@Slf4j
@Service
public class SpringFlowableManager extends FlowableManager {
private final SpringFlowableRepository repository;
private final ApplicationContext applicationContext;
public SpringFlowableManager(FlowableConfiguration configuration, FlowableRepository repository, ApplicationContext applicationContext) {
public SpringFlowableManager(SpringFlowableConfiguration configuration, SpringFlowableRepository repository, ApplicationContext applicationContext) {
super(configuration, repository);
log.info("Configuration: {}", configuration);
this.repository = repository;
this.applicationContext = applicationContext;
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes(Specification<FlowableNode> specification) {
return repository.listNodes(specification);
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes(Specification<FlowableNode> specification, Sort sort) {
return repository.listNodes(specification, sort);
}
public Page<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes(Specification<FlowableNode> specification, Pageable pageable) {
return repository.listNodes(specification, pageable);
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances(Specification<FlowableInstance> specification) {
return repository.listInstances(specification);
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances(Specification<FlowableInstance> specification, Sort sort) {
return repository.listInstances(specification, sort);
}
public Page<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances(Specification<FlowableInstance> specification, Pageable pageable) {
return repository.listInstances(specification, pageable);
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId, Specification<FlowableHistory> specification) {
return repository.listHistories(instanceId, specification);
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId, Specification<FlowableHistory> specification, Sort sort) {
return repository.listHistories(instanceId, specification, sort);
}
public Page<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId, Specification<FlowableHistory> specification, Pageable pageable) {
return repository.listHistories(instanceId, specification, pageable);
}
@SneakyThrows
@Override
protected <T> T createBean(String classpath) {

View File

@@ -0,0 +1,9 @@
package com.lanyuanxiaoyao.flowable.jpa;
/**
* @author lanyuanxiaoyao
* @version 20250106
*/
public interface SpringFlowableManagerInitializer {
SpringFlowableManager initial(SpringFlowableManager manager);
}

View File

@@ -9,14 +9,18 @@ import com.lanyuanxiaoyao.flowable.jpa.repository.FlowableInstanceRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.FlowableNodeRepository;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.transaction.Transactional;
import org.springframework.stereotype.Service;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
/**
* @author lanyuanxiaoyao
* @version 20241231
*/
@Service
public class SpringFlowableRepository implements FlowableRepository {
private final FlowableNodeRepository flowableNodeRepository;
private final FlowableInstanceRepository flowableInstanceRepository;
@@ -28,6 +32,11 @@ public class SpringFlowableRepository implements FlowableRepository {
this.flowableHistoryRepository = flowableHistoryRepository;
}
@Override
public boolean existsNode(String nodeId) {
return flowableNodeRepository.existsById(nodeId);
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveNode(com.lanyuanxiaoyao.flowable.core.model.FlowableNode node) {
@@ -47,14 +56,35 @@ public class SpringFlowableRepository implements FlowableRepository {
.orElse(null);
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes() {
return flowableNodeRepository.findAll()
.stream()
private List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> toNodes(Iterable<FlowableNode> nodes) {
return StreamSupport.stream(nodes.spliterator(), false)
.map(FlowableNode::toFlowableNode)
.collect(Collectors.toList());
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes() {
return toNodes(flowableNodeRepository.findAll());
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes(Specification<FlowableNode> specification) {
return toNodes(flowableNodeRepository.findAll(specification));
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes(Specification<FlowableNode> specification, Sort sort) {
return toNodes(flowableNodeRepository.findAll(specification, sort));
}
public Page<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes(Specification<FlowableNode> specification, Pageable pageable) {
Page<FlowableNode> page = flowableNodeRepository.findAll(specification, pageable);
return new PageImpl<>(toNodes(page.getContent()), pageable, page.getTotalElements());
}
@Override
public boolean existsInstance(String instanceId) {
return flowableInstanceRepository.existsById(instanceId);
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveInstance(com.lanyuanxiaoyao.flowable.core.model.FlowableInstance instance) {
@@ -68,14 +98,35 @@ public class SpringFlowableRepository implements FlowableRepository {
.orElse(null);
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances() {
return flowableInstanceRepository.findAll()
.stream()
private List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> toInstances(Iterable<FlowableInstance> instances) {
return StreamSupport.stream(instances.spliterator(), false)
.map(FlowableInstance::toFlowableInstance)
.collect(Collectors.toList());
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances() {
return toInstances(flowableInstanceRepository.findAll());
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances(Specification<FlowableInstance> specification) {
return toInstances(flowableInstanceRepository.findAll(specification));
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances(Specification<FlowableInstance> specification, Sort sort) {
return toInstances(flowableInstanceRepository.findAll(specification, sort));
}
public Page<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances(Specification<FlowableInstance> specification, Pageable pageable) {
Page<FlowableInstance> page = flowableInstanceRepository.findAll(specification, pageable);
return new PageImpl<>(toInstances(page.getContent()), pageable, page.getTotalElements());
}
@Override
public boolean existsHistory(String historyId) {
return flowableHistoryRepository.existsById(historyId);
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveHistory(com.lanyuanxiaoyao.flowable.core.model.FlowableHistory history) {
@@ -89,14 +140,51 @@ public class SpringFlowableRepository implements FlowableRepository {
.orElse(null);
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId) {
return flowableHistoryRepository.findAllByInstanceId(instanceId)
.stream()
private List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> toHistories(Iterable<FlowableHistory> histories) {
return StreamSupport.stream(histories.spliterator(), false)
.map(FlowableHistory::toFlowableHistory)
.collect(Collectors.toList());
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId) {
return toHistories(flowableHistoryRepository.findAllByInstanceId(instanceId));
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId, Specification<FlowableHistory> specification) {
return toHistories(
flowableHistoryRepository.findAll(
(root, query, builder) -> builder.and(
builder.equal(root.get("instanceId"), instanceId),
specification.toPredicate(root, query, builder)
)
)
);
}
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId, Specification<FlowableHistory> specification, Sort sort) {
return toHistories(
flowableHistoryRepository.findAll(
(root, query, builder) -> builder.and(
builder.equal(root.get("instanceId"), instanceId),
specification.toPredicate(root, query, builder)
),
sort
)
);
}
public Page<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId, Specification<FlowableHistory> specification, Pageable pageable) {
Page<FlowableHistory> page = flowableHistoryRepository.findAll(
(root, query, builder) -> builder.and(
builder.equal(root.get("instanceId"), instanceId),
specification.toPredicate(root, query, builder)
),
pageable
);
return new PageImpl<>(toHistories(page.getContent()), pageable, page.getTotalElements());
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveInstanceAndHistory(com.lanyuanxiaoyao.flowable.core.model.FlowableInstance instance, com.lanyuanxiaoyao.flowable.core.model.FlowableHistory history) {

View File

@@ -43,6 +43,7 @@ public class FlowableHistory {
this.instanceId = history.getInstanceId();
this.action = history.getAction();
this.comment = history.getComment();
this.createdTime = history.getCreatedTime();
}
public com.lanyuanxiaoyao.flowable.core.model.FlowableHistory toFlowableHistory() {

View File

@@ -52,12 +52,17 @@ public class FlowableInstance {
@LastModifiedDate
private LocalDateTime updatedTime;
private String extra;
@SneakyThrows
public FlowableInstance(com.lanyuanxiaoyao.flowable.core.model.FlowableInstance instance) {
this.instanceId = instance.getInstanceId();
this.metadata = objectToBytes(instance.getMetadata());
this.currentNodeId = instance.getCurrentNodeId();
this.status = instance.getStatus();
this.extra = instance.getExtra();
this.createdTime = instance.getCreatedTime();
this.updatedTime = instance.getUpdatedTime();
}
private static byte[] objectToBytes(Object object) throws IOException {
@@ -86,6 +91,7 @@ public class FlowableInstance {
.status(status)
.createdTime(createdTime)
.updatedTime(updatedTime)
.extra(extra)
.build();
}
}

View File

@@ -59,7 +59,7 @@ public class FlowableNode {
@CreatedDate
private LocalDateTime createdTime;
@LastModifiedDate
private LocalDateTime updateTime;
private LocalDateTime updatedTime;
public FlowableNode(com.lanyuanxiaoyao.flowable.core.model.FlowableNode node) {
this.nodeId = node.getNodeId();
@@ -70,6 +70,8 @@ public class FlowableNode {
this.targets = node.getTargets();
this.accessor = node.getAccessor();
this.listeners = node.getListeners();
this.createdTime = node.getCreatedTime();
this.updatedTime = node.getUpdatedTime();
}
public com.lanyuanxiaoyao.flowable.core.model.FlowableNode toFlowableNode() {
@@ -83,7 +85,7 @@ public class FlowableNode {
.accessor(accessor)
.listeners(listeners)
.createdTime(createdTime)
.updatedTime(updateTime)
.updatedTime(updatedTime)
.build();
}
}

View File

@@ -3,6 +3,8 @@ package com.lanyuanxiaoyao.flowable.jpa.repository;
import com.lanyuanxiaoyao.flowable.jpa.entity.FlowableHistory;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.stereotype.Repository;
/**
@@ -10,6 +12,6 @@ import org.springframework.stereotype.Repository;
* @version 20250102
*/
@Repository
public interface FlowableHistoryRepository extends JpaRepository<FlowableHistory, String> {
public interface FlowableHistoryRepository extends JpaRepository<FlowableHistory, String>, JpaSpecificationExecutor<FlowableHistory>, QueryByExampleExecutor<FlowableHistory> {
List<FlowableHistory> findAllByInstanceId(String instanceId);
}

View File

@@ -2,6 +2,8 @@ package com.lanyuanxiaoyao.flowable.jpa.repository;
import com.lanyuanxiaoyao.flowable.jpa.entity.FlowableInstance;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.stereotype.Repository;
/**
@@ -9,5 +11,5 @@ import org.springframework.stereotype.Repository;
* @version 20250102
*/
@Repository
public interface FlowableInstanceRepository extends JpaRepository<FlowableInstance, String> {
public interface FlowableInstanceRepository extends JpaRepository<FlowableInstance, String>, JpaSpecificationExecutor<FlowableInstance>, QueryByExampleExecutor<FlowableInstance> {
}

View File

@@ -2,6 +2,8 @@ package com.lanyuanxiaoyao.flowable.jpa.repository;
import com.lanyuanxiaoyao.flowable.jpa.entity.FlowableNode;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.stereotype.Repository;
/**
@@ -9,5 +11,5 @@ import org.springframework.stereotype.Repository;
* @version 20250102
*/
@Repository
public interface FlowableNodeRepository extends JpaRepository<FlowableNode, String> {
public interface FlowableNodeRepository extends JpaRepository<FlowableNode, String>, JpaSpecificationExecutor<FlowableNode>, QueryByExampleExecutor<FlowableNode> {
}

View File

@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.lanyuanxiaoyao.flowable.jpa.SpringFlowableAutoConfiguration

View File

@@ -0,0 +1,36 @@
package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
import com.lanyuanxiaoyao.flowable.core.model.FlowableListener;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author lanyuanxiaoyao
* @version 20250106
*/
@Slf4j
@Service
public class SimpleListener implements FlowableListener {
@Resource
private FlowableManager flowableManager;
@Override
public void onActionStart(FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("onActionStart with spring: {}", flowableManager.listNodes());
}
@Override
public void onAction(FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("onAction with spring: {}", flowableManager.listNodes());
}
@Override
public void onActionComplete(FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("onActionComplete with spring: {}", flowableManager.listNodes());
}
}

View File

@@ -2,6 +2,7 @@ package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHandler;
import com.lanyuanxiaoyao.flowable.core.model.FlowableListener;
import com.lanyuanxiaoyao.flowable.test.TestFlowableManager;
import javax.annotation.Resource;
import org.springframework.boot.test.context.SpringBootTest;
@@ -21,7 +22,12 @@ public class TestSpringFlowableManager extends TestFlowableManager {
}
@Override
protected Class<? extends FlowableHandler> getAutomaticNodeClass() {
protected Class<? extends FlowableHandler> getHandler() {
return SimpleAutoAction.class;
}
@Override
protected Class<? extends FlowableListener> getListenerClass() {
return SimpleListener.class;
}
}

View File

@@ -36,6 +36,10 @@ public abstract class FlowableManager {
this.repository = repository;
}
public boolean existsNode(String nodeId) {
return repository.existsNode(nodeId);
}
public List<FlowableNode> listNodes() {
return repository.listNodes();
}
@@ -44,6 +48,10 @@ public abstract class FlowableManager {
return repository.getNode(nodeId);
}
public boolean existsInstance(String instanceId) {
return repository.existsInstance(instanceId);
}
public List<FlowableInstance> listInstances() {
return repository.listInstances();
}
@@ -52,6 +60,10 @@ public abstract class FlowableManager {
return repository.getInstance(instantId);
}
public boolean existsHistory(String historyId) {
return repository.existsHistory(historyId);
}
public List<FlowableHistory> listHistories(String instanceId) {
return repository.listHistories(instanceId);
}
@@ -69,16 +81,20 @@ public abstract class FlowableManager {
}
public String start(String nodeId, Map<String, Object> metadata) {
return start(nodeId, metadata, null);
}
public String start(String nodeId, Map<String, Object> metadata, String extra) {
FlowableNode node = repository.getNode(nodeId);
FlowableInstance instance = FlowableInstance.builder()
.instanceId(createId())
.currentNodeId(node.getNodeId())
.metadata(new FlowableMetadata(metadata))
.extra(extra)
.build();
repository.saveInstance(instance);
if (FlowableNode.Type.AUTOMATIC.equals(node.getType())) {
automaticAction(instance, node);
automaticAction(instance);
}
return instance.getInstanceId();
}
@@ -124,16 +140,28 @@ public abstract class FlowableManager {
if (MapHelper.isNotEmpty(metadata)) {
instance.getMetadata().putAll(metadata);
}
action(instance, action, comment);
}
private void automaticAction(FlowableInstance instance) {
action(instance, null, "系统自动执行");
}
private void action(FlowableInstance instance, final FlowableAction inputAction, String comment) {
FlowableNode node = repository.getNode(instance.getCurrentNodeId());
action(instance, node, action, comment);
}
String handlerClass = node.getHandler();
if (StringHelper.isBlank(handlerClass)) {
throw new IllegalArgumentException("节点执行器为空");
}
FlowableHandler handler = createBean(handlerClass);
final FlowableAction action = handler.handle(configuration, instance, node, inputAction);
if (Objects.isNull(action)) {
throw new IllegalArgumentException("节点执行结果不能为空");
}
private void automaticAction(FlowableInstance instance, FlowableNode node) {
action(instance, node, null, "系统自动执行");
}
callListeners(node.getListeners(), listener -> listener.onActionStart(instance, node, action));
private void action(FlowableInstance instance, FlowableNode node, FlowableAction action, String comment) {
if (FlowableInstance.Status.COMPLETED.equals(instance.getStatus()) || FlowableInstance.Status.ERROR.equals(instance.getStatus())) {
if (FlowableInstance.Status.COMPLETED.equals(instance.getStatus()) || FlowableInstance.Status.TERMINAL.equals(instance.getStatus())) {
throw new IllegalArgumentException("ID为" + instance.getInstanceId() + "的流程已结束,无法操作");
}
@@ -142,19 +170,11 @@ public abstract class FlowableManager {
throw new IllegalArgumentException("权限检查器为空");
}
FlowableAccessor accessor = createBean(accessorClass);
if (!accessor.access(configuration, instance, node, action)) {
if (!accessor.access(instance, node, action)) {
throw new IllegalArgumentException("权限校验不通过");
}
String handlerClass = node.getHandler();
if (StringHelper.isBlank(handlerClass)) {
throw new IllegalArgumentException("节点执行器为空");
}
FlowableHandler handler = createBean(handlerClass);
action = handler.handle(configuration, instance, node, action);
if (Objects.isNull(action)) {
throw new IllegalArgumentException("节点执行结果不能为空");
}
callListeners(node.getListeners(), listener -> listener.onAction(instance, node, action));
// 如果是挂起操作,就直接返回,不做操作
if (FlowableAction.SUSPEND.equals(action)) {
@@ -163,13 +183,15 @@ public abstract class FlowableManager {
}
if (FlowableAction.TERMINAL.equals(action)) {
saveInstance(instance, FlowableInstance.Status.ERROR, action, comment);
saveInstance(instance, FlowableInstance.Status.TERMINAL, action, comment);
callListeners(node.getListeners(), listener -> listener.onActionComplete(instance, node, action));
return;
}
if (Objects.isNull(node.getTargets())
|| !node.getTargets().containsKey(action)
|| StringHelper.isBlank(node.getTargets().get(action))) {
saveInstance(instance, FlowableInstance.Status.COMPLETED, action, comment);
callListeners(node.getListeners(), listener -> listener.onActionComplete(instance, node, action));
return;
}
String nextNodeId = node.getTargets().get(action);
@@ -177,8 +199,9 @@ public abstract class FlowableManager {
instance.setCurrentNodeId(nextNode.getNodeId());
saveInstance(instance, FlowableInstance.Status.RUNNING, action, comment);
callListeners(node.getListeners(), listener -> listener.onActionComplete(instance, node, action));
if (FlowableNode.Type.AUTOMATIC.equals(nextNode.getType())) {
automaticAction(instance, node);
automaticAction(instance);
}
}
@@ -190,8 +213,9 @@ public abstract class FlowableManager {
repository.saveInstanceAndHistory(instance, history);
}
private void callListeners(List<FlowableListener> listeners, Consumer<FlowableListener> handler) {
for (FlowableListener listener : listeners) {
private void callListeners(List<String> listeners, Consumer<FlowableListener> handler) {
for (String listenerClass : listeners) {
FlowableListener listener = createBean(listenerClass);
handler.accept(listener);
}
}

View File

@@ -1,7 +1,5 @@
package com.lanyuanxiaoyao.flowable.core.model;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
/**
* 权限校验
*
@@ -9,11 +7,11 @@ import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
* @version 20241231
*/
public interface FlowableAccessor {
boolean access(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action);
boolean access(FlowableInstance instance, FlowableNode node, FlowableAction action);
class DefaultFlowableAccessor implements FlowableAccessor {
@Override
public boolean access(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action) {
public boolean access(FlowableInstance instance, FlowableNode node, FlowableAction action) {
return true;
}
}

View File

@@ -26,6 +26,8 @@ public class FlowableInstance {
@Builder.Default
private LocalDateTime updatedTime = LocalDateTime.now();
private String extra;
public void addMetadata(Map<String, Object> metadata) {
this.metadata.putAll(metadata);
}
@@ -33,6 +35,6 @@ public class FlowableInstance {
public enum Status {
RUNNING,
COMPLETED,
ERROR,
TERMINAL,
}
}

View File

@@ -7,13 +7,23 @@ package com.lanyuanxiaoyao.flowable.core.model;
* @version 20241231
*/
public interface FlowableListener {
void onStart(FlowableInstance instance);
void onActionStart(FlowableInstance instance, FlowableNode node, FlowableAction action);
void onError(FlowableInstance instance, Throwable throwable);
void onAction(FlowableInstance instance, FlowableNode node, FlowableAction action);
void onComplete(FlowableInstance instance);
void onActionComplete(FlowableInstance instance, FlowableNode node, FlowableAction action);
void onApprove(FlowableInstance instance);
abstract class AbstractFlowableListener implements FlowableListener {
@Override
public void onActionStart(FlowableInstance instance, FlowableNode node, FlowableAction action) {
}
void onReject(FlowableInstance instance);
@Override
public void onAction(FlowableInstance instance, FlowableNode node, FlowableAction action) {
}
@Override
public void onActionComplete(FlowableInstance instance, FlowableNode node, FlowableAction action) {
}
}
}

View File

@@ -22,6 +22,9 @@ public class FlowableMetadata implements Serializable {
}
public FlowableMetadata(Map<String, Object> metadata) {
if (metadata == null) {
metadata = new HashMap<>();
}
this.metadata = metadata;
}
@@ -37,6 +40,10 @@ public class FlowableMetadata implements Serializable {
return clazz.cast(metadata.get(key));
}
public String getString(String key) {
return get(key, String.class);
}
public Integer getInt(String key) {
return get(key, Integer.class);
}
@@ -81,6 +88,10 @@ public class FlowableMetadata implements Serializable {
return clazz.cast(metadata.getOrDefault(key, defaultValue));
}
public String getStringOrDefault(String key, String defaultValue) {
return getOrDefault(key, String.class, defaultValue);
}
public Integer getIntOrDefault(String key, Integer defaultValue) {
return getOrDefault(key, Integer.class, defaultValue);
}

View File

@@ -12,6 +12,8 @@ import java.util.List;
* @version 20241231
*/
public interface FlowableRepository {
boolean existsNode(String nodeId);
void saveNode(FlowableNode node);
void saveNode(List<FlowableNode> nodes);
@@ -20,12 +22,16 @@ public interface FlowableRepository {
List<FlowableNode> listNodes();
boolean existsInstance(String instanceId);
void saveInstance(FlowableInstance instance);
FlowableInstance getInstance(String instantId);
List<FlowableInstance> listInstances();
boolean existsHistory(String historyId);
void saveHistory(FlowableHistory history);
FlowableHistory getHistory(String historyId);

View File

@@ -1,10 +1,12 @@
package com.lanyuanxiaoyao.flowable.test;
import com.lanyuanxiaoyao.flowable.core.helper.ListHelper;
import com.lanyuanxiaoyao.flowable.core.helper.MapHelper;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHandler;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
import com.lanyuanxiaoyao.flowable.core.model.FlowableListener;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import com.lanyuanxiaoyao.flowable.test.accessor.RoleCheckAccessor;
import com.lanyuanxiaoyao.flowable.test.handler.TwoApproveHandler;
@@ -12,7 +14,10 @@ import java.util.Map;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
/**
* 集成测试
@@ -21,6 +26,7 @@ import org.junit.jupiter.api.Test;
* @version 20241231
*/
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public abstract class TestFlowableManager {
protected abstract FlowableManager flowableManager();
@@ -41,10 +47,23 @@ public abstract class TestFlowableManager {
.build();
}
@Test
@Order(1)
public void testRepository() {
FlowableManager manager = flowableManager();
FlowableNode node1 = createManualNode();
Assertions.assertFalse(manager.existsNode(node1.getNodeId()));
manager.create(node1);
Assertions.assertTrue(manager.existsNode(node1.getNodeId()));
Assertions.assertNotNull(manager.getNode(node1.getNodeId()));
Assertions.assertEquals(1, manager.listNodes().size());
}
/**
* 单节点审批
*/
@Test
@Order(2)
public void testSingleNode() {
FlowableManager manager = flowableManager();
FlowableNode node1 = createManualNode();
@@ -69,6 +88,7 @@ public abstract class TestFlowableManager {
}
@Test
@Order(3)
public void testMultiNode() {
FlowableManager manager = flowableManager();
FlowableNode node1 = createManualNode(
@@ -105,6 +125,7 @@ public abstract class TestFlowableManager {
}
@Test
@Order(4)
public void testTerminal() {
FlowableManager manager = flowableManager();
FlowableNode node1 = createManualNode();
@@ -115,7 +136,7 @@ public abstract class TestFlowableManager {
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
manager.terminal(instanceId, "d896b642-a1d8-499c-92e7-bed63581f2f8");
Assertions.assertEquals(FlowableInstance.Status.ERROR, manager.getInstance(instanceId).getStatus());
Assertions.assertEquals(FlowableInstance.Status.TERMINAL, manager.getInstance(instanceId).getStatus());
Assertions.assertEquals(1, manager.listHistories(instanceId).size());
Assertions.assertEquals(FlowableAction.TERMINAL, manager.listHistories(instanceId).get(0).getAction());
Assertions.assertEquals("d896b642-a1d8-499c-92e7-bed63581f2f8", manager.listHistories(instanceId).get(0).getComment());
@@ -124,6 +145,7 @@ public abstract class TestFlowableManager {
}
@Test
@Order(5)
public void testSuspend() {
FlowableManager manager = flowableManager();
FlowableNode node = FlowableNode.builder()
@@ -142,9 +164,10 @@ public abstract class TestFlowableManager {
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
}
protected abstract Class<? extends FlowableHandler> getAutomaticNodeClass();
protected abstract Class<? extends FlowableHandler> getHandler();
@Test
@Order(6)
public void testAutomaticNode() {
FlowableManager manager = flowableManager();
FlowableNode node = FlowableNode.builder()
@@ -152,7 +175,7 @@ public abstract class TestFlowableManager {
.name("5d60dab0-7691-4543-b753-af7ac02cb7ec")
.description("5d60dab0-7691-4543-b753-af7ac02cb7ec")
.type(FlowableNode.Type.AUTOMATIC)
.handler(getAutomaticNodeClass().getName())
.handler(getHandler().getName())
.build();
manager.create(node);
String instanceId = manager.start(node.getNodeId());
@@ -160,6 +183,7 @@ public abstract class TestFlowableManager {
}
@Test
@Order(7)
public void testNodeContext() {
FlowableManager manager = flowableManager();
FlowableNode node1 = createManualNode(
@@ -182,6 +206,7 @@ public abstract class TestFlowableManager {
}
@Test
@Order(8)
public void testAccessor() {
FlowableManager manager = flowableManager();
FlowableNode node = FlowableNode.builder()
@@ -199,4 +224,25 @@ public abstract class TestFlowableManager {
manager.approve(instanceId, MapHelper.of("role", "admin"));
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
}
protected abstract Class<? extends FlowableListener> getListenerClass();
@Test
@Order(9)
public void testListener() {
FlowableManager manager = flowableManager();
FlowableNode node = FlowableNode.builder()
.nodeId("1c81366f-4102-4a9e-abb2-1dbcb09062e9")
.name("1c81366f-4102-4a9e-abb2-1dbcb09062e9")
.description("1c81366f-4102-4a9e-abb2-1dbcb09062e9")
.type(FlowableNode.Type.MANUAL)
.listeners(ListHelper.of(
getListenerClass().getName()
))
.build();
manager.create(node);
String instanceId = manager.start(node.getNodeId());
manager.approve(instanceId);
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
}
}

View File

@@ -1,6 +1,5 @@
package com.lanyuanxiaoyao.flowable.test.accessor;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAccessor;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
@@ -15,7 +14,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class RoleCheckAccessor implements FlowableAccessor {
@Override
public boolean access(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action) {
public boolean access(FlowableInstance instance, FlowableNode node, FlowableAction action) {
FlowableMetadata metadata = instance.getMetadata();
return metadata.containsKey("role");
}

View File

@@ -22,6 +22,11 @@ public class InMemoryFlowableRepository implements FlowableRepository {
private static final Map<String, FlowableInstance> instances = new HashMap<>();
private static final Map<String, List<FlowableHistory>> histories = new HashMap<>();
@Override
public boolean existsNode(String nodeId) {
return nodes.containsKey(nodeId);
}
@Override
public void saveNode(FlowableNode node) {
nodes.put(node.getNodeId(), node);
@@ -44,6 +49,11 @@ public class InMemoryFlowableRepository implements FlowableRepository {
return new ArrayList<>(nodes.values());
}
@Override
public boolean existsInstance(String instanceId) {
return instances.containsKey(instanceId);
}
@Override
public void saveInstance(FlowableInstance instance) {
instances.put(instance.getInstanceId(), instance);
@@ -59,6 +69,11 @@ public class InMemoryFlowableRepository implements FlowableRepository {
return new ArrayList<>(instances.values());
}
@Override
public boolean existsHistory(String historyId) {
return histories.containsKey(historyId);
}
@Override
public void saveHistory(FlowableHistory history) {
String instanceId = history.getInstanceId();

View File

@@ -0,0 +1,29 @@
package com.lanyuanxiaoyao.flowable.test;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
import com.lanyuanxiaoyao.flowable.core.model.FlowableListener;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import lombok.extern.slf4j.Slf4j;
/**
* @author lanyuanxiaoyao
* @version 20250106
*/
@Slf4j
public class SimpleListener implements FlowableListener {
@Override
public void onActionStart(FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("onActionStart");
}
@Override
public void onAction(FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("onAction");
}
@Override
public void onActionComplete(FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("onActionComplete");
}
}

View File

@@ -2,6 +2,7 @@ package com.lanyuanxiaoyao.flowable.test;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHandler;
import com.lanyuanxiaoyao.flowable.core.model.FlowableListener;
/**
* @author lanyuanxiaoyao
@@ -14,7 +15,12 @@ public class TestSimpleFlowableManager extends TestFlowableManager {
}
@Override
protected Class<? extends FlowableHandler> getAutomaticNodeClass() {
protected Class<? extends FlowableHandler> getHandler() {
return SimpleAutoHandler.class;
}
@Override
protected Class<? extends FlowableListener> getListenerClass() {
return SimpleListener.class;
}
}