1
0

Compare commits

...

14 Commits

38 changed files with 767 additions and 343 deletions

2
.idea/encodings.xml generated
View File

@@ -5,6 +5,8 @@
<file url="file://$PROJECT_DIR$/adapter/flowable-spring-boot-jpa-starter/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/flowable-core/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/flowable-core/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/flowable-example/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/flowable-example/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>

7
.idea/jpa.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JpaBuddyIdeaProjectConfig">
<option name="defaultUnitInitialized" value="true" />
<option name="renamerInitialized" value="true" />
</component>
</project>

3
.idea/misc.xml generated
View File

@@ -11,4 +11,7 @@
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="temurin-1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="ProjectType">
<option name="id" value="jpab" />
</component>
</project>

View File

@@ -23,11 +23,21 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>flowable-example</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -0,0 +1,18 @@
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;
/**
* 配置类
*
* @author lanyuanxiaoyao
* @version 20250103
*/
@ToString(callSuper = true)
@Configuration
@ConfigurationProperties("flowable")
public class SpringFlowableConfiguration extends FlowableConfiguration {
}

View File

@@ -1,5 +1,6 @@
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 java.util.UUID;
@@ -17,8 +18,9 @@ import org.springframework.stereotype.Service;
public class SpringFlowableManager extends FlowableManager {
private final ApplicationContext applicationContext;
public SpringFlowableManager(FlowableRepository flowableRepository, ApplicationContext applicationContext) {
super(flowableRepository, () -> UUID.randomUUID().toString());
public SpringFlowableManager(FlowableConfiguration configuration, FlowableRepository repository, ApplicationContext applicationContext) {
super(configuration, repository);
log.info("Configuration: {}", configuration);
this.applicationContext = applicationContext;
}
@@ -26,17 +28,21 @@ public class SpringFlowableManager extends FlowableManager {
@Override
protected <T> T createBean(String classpath) {
Class<?> clazz = Class.forName(classpath);
T targetObject = null;
T targetObject;
try {
targetObject = (T) applicationContext.getBean(clazz);
} catch (Exception springException) {
log.warn("{} not found in spring context", springException);
try {
targetObject = (T) clazz.newInstance();
} catch (Exception javaException) {
throw new IllegalArgumentException(javaException);
throw new IllegalArgumentException(javaException.initCause(springException));
}
}
return targetObject;
}
@Override
protected String createId() {
return UUID.randomUUID().toString();
}
}

View File

@@ -0,0 +1,106 @@
package com.lanyuanxiaoyao.flowable.jpa;
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 com.lanyuanxiaoyao.flowable.jpa.repository.FlowableHistoryRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.FlowableInstanceRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.FlowableNodeRepository;
import java.util.List;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.springframework.stereotype.Service;
/**
* @author lanyuanxiaoyao
* @version 20241231
*/
@Service
public class SpringFlowableRepository implements FlowableRepository {
private final FlowableNodeRepository flowableNodeRepository;
private final FlowableInstanceRepository flowableInstanceRepository;
private final FlowableHistoryRepository flowableHistoryRepository;
public SpringFlowableRepository(FlowableNodeRepository flowableNodeRepository, FlowableInstanceRepository flowableInstanceRepository, FlowableHistoryRepository flowableHistoryRepository) {
this.flowableNodeRepository = flowableNodeRepository;
this.flowableInstanceRepository = flowableInstanceRepository;
this.flowableHistoryRepository = flowableHistoryRepository;
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveNode(com.lanyuanxiaoyao.flowable.core.model.FlowableNode node) {
flowableNodeRepository.save(new FlowableNode(node));
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveNode(List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> nodes) {
flowableNodeRepository.saveAll(nodes.stream().map(FlowableNode::new).collect(Collectors.toList()));
}
@Override
public com.lanyuanxiaoyao.flowable.core.model.FlowableNode getNode(String nodeId) {
return flowableNodeRepository.findById(nodeId)
.map(FlowableNode::toFlowableNode)
.orElse(null);
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableNode> listNodes() {
return flowableNodeRepository.findAll()
.stream()
.map(FlowableNode::toFlowableNode)
.collect(Collectors.toList());
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveInstance(com.lanyuanxiaoyao.flowable.core.model.FlowableInstance instance) {
flowableInstanceRepository.save(new FlowableInstance(instance));
}
@Override
public com.lanyuanxiaoyao.flowable.core.model.FlowableInstance getInstance(String instantId) {
return flowableInstanceRepository.findById(instantId)
.map(FlowableInstance::toFlowableInstance)
.orElse(null);
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableInstance> listInstances() {
return flowableInstanceRepository.findAll()
.stream()
.map(FlowableInstance::toFlowableInstance)
.collect(Collectors.toList());
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveHistory(com.lanyuanxiaoyao.flowable.core.model.FlowableHistory history) {
flowableHistoryRepository.save(new FlowableHistory(history));
}
@Override
public com.lanyuanxiaoyao.flowable.core.model.FlowableHistory getHistory(String historyId) {
return flowableHistoryRepository.findById(historyId)
.map(FlowableHistory::toFlowableHistory)
.orElse(null);
}
@Override
public List<com.lanyuanxiaoyao.flowable.core.model.FlowableHistory> listHistories(String instanceId) {
return flowableHistoryRepository.findAllByInstanceId(instanceId)
.stream()
.map(FlowableHistory::toFlowableHistory)
.collect(Collectors.toList());
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveInstanceAndHistory(com.lanyuanxiaoyao.flowable.core.model.FlowableInstance instance, com.lanyuanxiaoyao.flowable.core.model.FlowableHistory history) {
saveInstance(instance);
saveHistory(history);
}
}

View File

@@ -1,109 +0,0 @@
package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHistory;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import com.lanyuanxiaoyao.flowable.core.repository.FlowableRepository;
import com.lanyuanxiaoyao.flowable.jpa.entity.JpaFlowableHistory;
import com.lanyuanxiaoyao.flowable.jpa.entity.JpaFlowableInstance;
import com.lanyuanxiaoyao.flowable.jpa.entity.JpaFlowableNode;
import com.lanyuanxiaoyao.flowable.jpa.repository.JpaFlowableHistoryRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.JpaFlowableInstanceRepository;
import com.lanyuanxiaoyao.flowable.jpa.repository.JpaFlowableNodeRepository;
import java.util.List;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import org.springframework.stereotype.Service;
/**
* @author lanyuanxiaoyao
* @version 20241231
*/
@Service
public class SpringJpaFlowableRepository implements FlowableRepository {
private final JpaFlowableNodeRepository jpaFlowableNodeRepository;
private final JpaFlowableInstanceRepository jpaFlowableInstanceRepository;
private final JpaFlowableHistoryRepository jpaFlowableHistoryRepository;
public SpringJpaFlowableRepository(JpaFlowableNodeRepository jpaFlowableNodeRepository, JpaFlowableInstanceRepository jpaFlowableInstanceRepository, JpaFlowableHistoryRepository jpaFlowableHistoryRepository) {
this.jpaFlowableNodeRepository = jpaFlowableNodeRepository;
this.jpaFlowableInstanceRepository = jpaFlowableInstanceRepository;
this.jpaFlowableHistoryRepository = jpaFlowableHistoryRepository;
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveNode(FlowableNode node) {
jpaFlowableNodeRepository.save(new JpaFlowableNode(node));
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveNode(List<FlowableNode> nodes) {
jpaFlowableNodeRepository.saveAll(nodes.stream().map(JpaFlowableNode::new).collect(Collectors.toList()));
}
@Override
public FlowableNode getNode(String nodeId) {
return jpaFlowableNodeRepository.findById(nodeId)
.map(JpaFlowableNode::toFlowableNode)
.orElse(null);
}
@Override
public List<FlowableNode> listNodes() {
return jpaFlowableNodeRepository.findAll()
.stream()
.map(JpaFlowableNode::toFlowableNode)
.collect(Collectors.toList());
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveInstance(FlowableInstance instance) {
jpaFlowableInstanceRepository.save(new JpaFlowableInstance(instance));
}
@Override
public FlowableInstance getInstance(String instantId) {
return jpaFlowableInstanceRepository.findById(instantId)
.map(JpaFlowableInstance::toFlowableInstance)
.orElse(null);
}
@Override
public List<FlowableInstance> listInstances() {
return jpaFlowableInstanceRepository.findAll()
.stream()
.map(JpaFlowableInstance::toFlowableInstance)
.collect(Collectors.toList());
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveHistory(FlowableHistory history) {
jpaFlowableHistoryRepository.save(new JpaFlowableHistory(history));
}
@Override
public FlowableHistory getHistory(String historyId) {
return jpaFlowableHistoryRepository.findById(historyId)
.map(JpaFlowableHistory::toFlowableHistory)
.orElse(null);
}
@Override
public List<FlowableHistory> listHistories(String instanceId) {
return jpaFlowableHistoryRepository.findAllByInstanceId(instanceId)
.stream()
.map(JpaFlowableHistory::toFlowableHistory)
.collect(Collectors.toList());
}
@Transactional(rollbackOn = Exception.class)
@Override
public void saveInstanceAndHistory(FlowableInstance instance, FlowableHistory history) {
saveInstance(instance);
saveHistory(history);
}
}

View File

@@ -1,7 +1,6 @@
package com.lanyuanxiaoyao.flowable.jpa.entity;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHistory;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
@@ -9,7 +8,6 @@ import javax.persistence.EntityListeners;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -26,11 +24,10 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@ToString
@Getter
@Setter
@Entity
@Table(name = "flowable_history")
@Entity(name = "flowable_history")
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
public class JpaFlowableHistory {
public class FlowableHistory {
@Id
private String historyId;
@Column(nullable = false)
@@ -41,14 +38,14 @@ public class JpaFlowableHistory {
@CreatedDate
private LocalDateTime createdTime;
public JpaFlowableHistory(FlowableHistory history) {
public FlowableHistory(com.lanyuanxiaoyao.flowable.core.model.FlowableHistory history) {
this.historyId = history.getHistoryId();
this.instanceId = history.getInstanceId();
this.action = history.getAction();
this.comment = history.getComment();
}
public FlowableHistory toFlowableHistory() {
return new FlowableHistory(historyId, instanceId, action, comment, createdTime);
public com.lanyuanxiaoyao.flowable.core.model.FlowableHistory toFlowableHistory() {
return new com.lanyuanxiaoyao.flowable.core.model.FlowableHistory(historyId, instanceId, action, comment, createdTime);
}
}

View File

@@ -1,13 +1,12 @@
package com.lanyuanxiaoyao.flowable.jpa.entity;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
import com.lanyuanxiaoyao.flowable.core.model.FlowableMetadata;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.LocalDateTime;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
@@ -15,7 +14,6 @@ import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import lombok.Cleanup;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -35,11 +33,10 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@ToString
@Getter
@Setter
@Entity
@Table(name = "flowable_instance")
@Entity(name = "flowable_instance")
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
public class JpaFlowableInstance {
public class FlowableInstance {
@Id
private String instanceId;
@Lob
@@ -48,7 +45,7 @@ public class JpaFlowableInstance {
private String currentNodeId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private FlowableInstance.Status status = FlowableInstance.Status.RUNNING;
private com.lanyuanxiaoyao.flowable.core.model.FlowableInstance.Status status = com.lanyuanxiaoyao.flowable.core.model.FlowableInstance.Status.RUNNING;
@CreatedDate
private LocalDateTime createdTime;
@@ -56,7 +53,7 @@ public class JpaFlowableInstance {
private LocalDateTime updatedTime;
@SneakyThrows
public JpaFlowableInstance(FlowableInstance instance) {
public FlowableInstance(com.lanyuanxiaoyao.flowable.core.model.FlowableInstance instance) {
this.instanceId = instance.getInstanceId();
this.metadata = objectToBytes(instance.getMetadata());
this.currentNodeId = instance.getCurrentNodeId();
@@ -81,14 +78,14 @@ public class JpaFlowableInstance {
}
@SneakyThrows
public FlowableInstance toFlowableInstance() {
return new FlowableInstance(
instanceId,
currentNodeId,
(Map<String, Object>) bytesToObject(this.metadata),
status,
createdTime,
updatedTime
);
public com.lanyuanxiaoyao.flowable.core.model.FlowableInstance toFlowableInstance() {
return com.lanyuanxiaoyao.flowable.core.model.FlowableInstance.builder()
.instanceId(instanceId)
.currentNodeId(currentNodeId)
.metadata((FlowableMetadata) bytesToObject(this.metadata))
.status(status)
.createdTime(createdTime)
.updatedTime(updatedTime)
.build();
}
}

View File

@@ -1,8 +1,8 @@
package com.lanyuanxiaoyao.flowable.jpa.entity;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
@@ -14,8 +14,6 @@ import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@@ -33,11 +31,10 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@ToString
@Getter
@Setter
@Entity
@Table(name = "flowable_node")
@Entity(name = "flowable_node")
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
public class JpaFlowableNode {
public class FlowableNode {
@Id
private String nodeId;
@Column(nullable = false)
@@ -46,31 +43,47 @@ public class JpaFlowableNode {
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private FlowableNode.Type type;
private String automaticAction;
private com.lanyuanxiaoyao.flowable.core.model.FlowableNode.Type type;
@Column(nullable = false)
private String handler;
@ElementCollection(fetch = javax.persistence.FetchType.EAGER)
@MapKeyColumn(name = "action")
@Column(name = "nodeId")
@CollectionTable(name = "flowable_node_manual_actions", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Map<FlowableAction, String> manualActions;
@CollectionTable(name = "flowable_node_targets", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Map<FlowableAction, String> targets;
@Column(nullable = false)
private String accessor;
@ElementCollection(fetch = javax.persistence.FetchType.EAGER)
@CollectionTable(name = "flowable_node_listeners", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private List<String> listeners;
@CreatedDate
private LocalDateTime createdTime;
@LastModifiedDate
private LocalDateTime updateTime;
public JpaFlowableNode(FlowableNode node) {
public FlowableNode(com.lanyuanxiaoyao.flowable.core.model.FlowableNode node) {
this.nodeId = node.getNodeId();
this.name = node.getName();
this.description = node.getDescription();
this.type = node.getType();
this.automaticAction = node.getAutomaticAction();
this.manualActions = node.getManualActions();
this.handler = node.getHandler();
this.targets = node.getTargets();
this.accessor = node.getAccessor();
this.listeners = node.getListeners();
}
public FlowableNode toFlowableNode() {
FlowableNode node = new FlowableNode(nodeId, name, description, type, automaticAction, manualActions, null, createdTime);
node.setUpdatedTime(updateTime);
return node;
public com.lanyuanxiaoyao.flowable.core.model.FlowableNode toFlowableNode() {
return com.lanyuanxiaoyao.flowable.core.model.FlowableNode.builder()
.nodeId(nodeId)
.name(name)
.description(description)
.type(type)
.handler(handler)
.targets(targets)
.accessor(accessor)
.listeners(listeners)
.createdTime(createdTime)
.updatedTime(updateTime)
.build();
}
}

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
package com.lanyuanxiaoyao.flowable.core;
package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
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.FlowableNode;
import java.util.Map;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -13,12 +14,12 @@ import lombok.extern.slf4j.Slf4j;
* @version 20250102
*/
@Slf4j
public class SimpleAutoAction implements FlowableNode.AutoAction {
public class SimpleAutoAction implements FlowableHandler {
@Resource
private FlowableManager flowableManager;
@Override
public FlowableAction action(FlowableInstance instance, FlowableNode node, Map<String, Object> metadata) {
public FlowableAction handle(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("Initial with spring: {}", flowableManager.listNodes());
return FlowableAction.APPROVE;
}

View File

@@ -1,6 +1,5 @@
package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.SimpleAutoAction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

View File

@@ -1,7 +1,8 @@
package com.lanyuanxiaoyao.flowable.jpa;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.test.TestFlowableManager;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHandler;
import com.lanyuanxiaoyao.flowable.test.TestFlowableManager;
import javax.annotation.Resource;
import org.springframework.boot.test.context.SpringBootTest;
@@ -18,4 +19,9 @@ public class TestSpringFlowableManager extends TestFlowableManager {
protected FlowableManager flowableManager() {
return flowableManager;
}
@Override
protected Class<? extends FlowableHandler> getAutomaticNodeClass() {
return SimpleAutoAction.class;
}
}

View File

@@ -12,34 +12,9 @@
<artifactId>flowable-core</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.16</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.4</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -2,6 +2,7 @@ package com.lanyuanxiaoyao.flowable.core.helper;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import lombok.NoArgsConstructor;
/**
@@ -24,7 +25,26 @@ public class MapHelper {
return map;
}
public static Map<String, Object> empty() {
public static <K, V> Map<K, V> empty() {
return new HashMap<>(0);
}
public static boolean isEmpty(Map<?, ?> map) {
return Objects.isNull(map) || map.isEmpty();
}
public static boolean isNotEmpty(Map<?, ?> map) {
return !isEmpty(map);
}
@SafeVarargs
public static <K, V> Map<K, V> concat(Map<K, V>... maps) {
Map<K, V> result = new HashMap<>();
for (Map<K, V> map : maps) {
if (Objects.nonNull(map)) {
result.putAll(map);
}
}
return result;
}
}

View File

@@ -0,0 +1,28 @@
package com.lanyuanxiaoyao.flowable.core.manager;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 配置项
*
* @author lanyuanxiaoyao
* @version 20250103
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FlowableConfiguration {
/**
* 在上下文中查询权限的key
*/
@Builder.Default
private String accessorKey = Constants.DEFAULT_ACCESSOR_KEY;
public interface Constants {
String DEFAULT_ACCESSOR_KEY = "accessor";
}
}

View File

@@ -3,10 +3,13 @@ package com.lanyuanxiaoyao.flowable.core.manager;
import com.lanyuanxiaoyao.flowable.core.helper.ListHelper;
import com.lanyuanxiaoyao.flowable.core.helper.MapHelper;
import com.lanyuanxiaoyao.flowable.core.helper.StringHelper;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAccessor;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHandler;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHistory;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
import com.lanyuanxiaoyao.flowable.core.model.FlowableListener;
import com.lanyuanxiaoyao.flowable.core.model.FlowableMetadata;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import com.lanyuanxiaoyao.flowable.core.repository.FlowableRepository;
import java.time.LocalDateTime;
@@ -24,40 +27,41 @@ import lombok.extern.slf4j.Slf4j;
*/
@Slf4j
public abstract class FlowableManager {
private final FlowableRepository flowableRepository;
private final IdGenerator idGenerator;
private static final String DEFAULT_APPROVE_COMMENT = "审批通过";
private final FlowableConfiguration configuration;
private final FlowableRepository repository;
public FlowableManager(FlowableRepository flowableRepository, IdGenerator idGenerator) {
this.flowableRepository = flowableRepository;
this.idGenerator = idGenerator;
public FlowableManager(FlowableConfiguration configuration, FlowableRepository repository) {
this.configuration = configuration;
this.repository = repository;
}
public List<FlowableNode> listNodes() {
return flowableRepository.listNodes();
return repository.listNodes();
}
public FlowableNode getNode(String nodeId) {
return flowableRepository.getNode(nodeId);
return repository.getNode(nodeId);
}
public List<FlowableInstance> listInstances() {
return flowableRepository.listInstances();
return repository.listInstances();
}
public FlowableInstance getInstance(String instantId) {
return flowableRepository.getInstance(instantId);
return repository.getInstance(instantId);
}
public List<FlowableHistory> listHistories(String instanceId) {
return flowableRepository.listHistories(instanceId);
return repository.listHistories(instanceId);
}
public FlowableHistory getHistory(String historyId) {
return flowableRepository.getHistory(historyId);
return repository.getHistory(historyId);
}
public void create(FlowableNode... node) {
flowableRepository.saveNode(ListHelper.of(node));
repository.saveNode(ListHelper.of(node));
}
public String start(String nodeId) {
@@ -65,22 +69,34 @@ public abstract class FlowableManager {
}
public String start(String nodeId, Map<String, Object> metadata) {
FlowableNode node = flowableRepository.getNode(nodeId);
FlowableInstance instance = new FlowableInstance(idGenerator.createId(), node.getNodeId(), metadata);
flowableRepository.saveInstance(instance);
FlowableNode node = repository.getNode(nodeId);
FlowableInstance instance = FlowableInstance.builder()
.instanceId(createId())
.currentNodeId(node.getNodeId())
.metadata(new FlowableMetadata(metadata))
.build();
repository.saveInstance(instance);
if (FlowableNode.Type.AUTOMATIC.equals(node.getType())) {
autoAction(instance, node, MapHelper.empty());
automaticAction(instance, node);
}
return instance.getInstanceId();
}
public void approve(String instanceId) {
approve(instanceId, "审批通过");
approve(instanceId, DEFAULT_APPROVE_COMMENT);
}
public void approve(String instanceId, String comment) {
action(instanceId, FlowableAction.APPROVE, comment);
manualAction(instanceId, FlowableAction.APPROVE, comment);
}
public void approve(String instanceId, Map<String, Object> metadata) {
manualAction(instanceId, FlowableAction.APPROVE, DEFAULT_APPROVE_COMMENT, metadata);
}
public void approve(String instanceId, String comment, Map<String, Object> metadata) {
manualAction(instanceId, FlowableAction.APPROVE, comment, metadata);
}
public void reject(String instanceId) {
@@ -88,7 +104,7 @@ public abstract class FlowableManager {
}
public void reject(String instanceId, String comment) {
action(instanceId, FlowableAction.REJECT, comment);
manualAction(instanceId, FlowableAction.REJECT, comment);
}
public void terminal(String instanceId) {
@@ -96,61 +112,82 @@ public abstract class FlowableManager {
}
public void terminal(String instanceId, String comment) {
action(instanceId, FlowableAction.TERMINAL, comment);
manualAction(instanceId, FlowableAction.TERMINAL, comment);
}
public void action(String instanceId, FlowableAction action, String comment) {
action(instanceId, action, comment, MapHelper.empty());
public void manualAction(String instanceId, FlowableAction action, String comment) {
manualAction(instanceId, action, comment, MapHelper.empty());
}
private void autoAction(FlowableInstance instance, FlowableNode node, Map<String, Object> metadata) {
String actionClass = node.getAutomaticAction();
if (StringHelper.isBlank(actionClass)) {
throw new IllegalArgumentException("自动节点执行器为空");
private void manualAction(String instanceId, FlowableAction action, String comment, Map<String, Object> metadata) {
FlowableInstance instance = repository.getInstance(instanceId);
if (MapHelper.isNotEmpty(metadata)) {
instance.getMetadata().putAll(metadata);
}
FlowableNode.AutoAction autoAction = createBean(actionClass);
action(instance, node, autoAction.action(instance, node, metadata), "系统自动执行", metadata);
FlowableNode node = repository.getNode(instance.getCurrentNodeId());
action(instance, node, action, comment);
}
private void action(String instanceId, FlowableAction action, String comment, Map<String, Object> metadata) {
FlowableInstance instance = flowableRepository.getInstance(instanceId);
FlowableNode node = flowableRepository.getNode(instance.getCurrentNodeId());
action(instance, node, action, comment, metadata);
private void automaticAction(FlowableInstance instance, FlowableNode node) {
action(instance, node, null, "系统自动执行");
}
private void action(FlowableInstance instance, FlowableNode node, FlowableAction action, String comment, Map<String, Object> metadata) {
private void action(FlowableInstance instance, FlowableNode node, FlowableAction action, String comment) {
if (FlowableInstance.Status.COMPLETED.equals(instance.getStatus()) || FlowableInstance.Status.ERROR.equals(instance.getStatus())) {
throw new IllegalArgumentException("ID为" + instance.getInstanceId() + "的流程已结束,无法操作");
}
String accessorClass = node.getAccessor();
if (StringHelper.isBlank(accessorClass)) {
throw new IllegalArgumentException("权限检查器为空");
}
FlowableAccessor accessor = createBean(accessorClass);
if (!accessor.access(configuration, 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("节点执行结果不能为空");
}
// 如果是挂起操作,就直接返回,不做操作
if (FlowableAction.SUSPEND.equals(action)) {
saveInstance(instance, instance.getStatus(), action, comment);
return;
}
if (FlowableAction.TERMINAL.equals(action)) {
saveInstance(instance, FlowableInstance.Status.ERROR, metadata, action, comment);
saveInstance(instance, FlowableInstance.Status.ERROR, action, comment);
return;
}
if (Objects.isNull(node.getManualActions())
|| !node.getManualActions().containsKey(action)
|| StringHelper.isBlank(node.getManualActions().get(action))) {
saveInstance(instance, FlowableInstance.Status.COMPLETED, metadata, action, comment);
if (Objects.isNull(node.getTargets())
|| !node.getTargets().containsKey(action)
|| StringHelper.isBlank(node.getTargets().get(action))) {
saveInstance(instance, FlowableInstance.Status.COMPLETED, action, comment);
return;
}
String nextNodeId = node.getManualActions().get(action);
FlowableNode nextNode = flowableRepository.getNode(nextNodeId);
String nextNodeId = node.getTargets().get(action);
FlowableNode nextNode = repository.getNode(nextNodeId);
instance.setCurrentNodeId(nextNode.getNodeId());
saveInstance(instance, FlowableInstance.Status.RUNNING, metadata, action, comment);
saveInstance(instance, FlowableInstance.Status.RUNNING, action, comment);
if (FlowableNode.Type.AUTOMATIC.equals(nextNode.getType())) {
autoAction(instance, node, metadata);
automaticAction(instance, node);
}
}
private void saveInstance(FlowableInstance instance, FlowableInstance.Status status, Map<String, Object> metadata, FlowableAction action, String comment) {
private void saveInstance(FlowableInstance instance, FlowableInstance.Status status, FlowableAction action, String comment) {
instance.setStatus(status);
if (Objects.nonNull(metadata)) {
instance.addMetadata(metadata);
}
instance.setUpdatedTime(LocalDateTime.now());
FlowableHistory history = new FlowableHistory(idGenerator.createId(), instance.getInstanceId(), action, comment);
flowableRepository.saveInstanceAndHistory(instance, history);
FlowableHistory history = new FlowableHistory(createId(), instance.getInstanceId(), action, comment);
repository.saveInstanceAndHistory(instance, history);
}
private void callListeners(List<FlowableListener> listeners, Consumer<FlowableListener> handler) {
@@ -161,7 +198,5 @@ public abstract class FlowableManager {
protected abstract <T> T createBean(String classpath);
public interface IdGenerator {
String createId();
}
protected abstract String createId();
}

View File

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

View File

@@ -10,4 +10,5 @@ public enum FlowableAction {
APPROVE,
REJECT,
TERMINAL,
SUSPEND,
}

View File

@@ -0,0 +1,20 @@
package com.lanyuanxiaoyao.flowable.core.model;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
/**
* 处理器
*
* @author lanyuanxiaoyao
* @version 20250103
*/
public interface FlowableHandler {
FlowableAction handle(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action);
class DefaultFlowableHandler implements FlowableHandler {
@Override
public FlowableAction handle(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action) {
return action;
}
}
}

View File

@@ -1,6 +1,8 @@
package com.lanyuanxiaoyao.flowable.core.model;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
@@ -8,6 +10,8 @@ import lombok.Data;
* @version 20241231
*/
@Data
@AllArgsConstructor
@Builder
public class FlowableHistory {
private final String historyId;
private final String instanceId;
@@ -18,12 +22,4 @@ public class FlowableHistory {
public FlowableHistory(String historyId, String instanceId, FlowableAction action, String comment) {
this(historyId, instanceId, action, comment, LocalDateTime.now());
}
public FlowableHistory(String historyId, String instanceId, FlowableAction action, String comment, LocalDateTime createdTime) {
this.historyId = historyId;
this.instanceId = instanceId;
this.action = action;
this.comment = comment;
this.createdTime = createdTime;
}
}

View File

@@ -1,8 +1,9 @@
package com.lanyuanxiaoyao.flowable.core.model;
import com.lanyuanxiaoyao.flowable.core.helper.MapHelper;
import java.time.LocalDateTime;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
@@ -10,32 +11,21 @@ import lombok.Data;
* @version 20241231
*/
@Data
@AllArgsConstructor
@Builder
public class FlowableInstance {
private final String instanceId;
private final Map<String, Object> metadata;
private final LocalDateTime createdTime;
@Builder.Default
private final FlowableMetadata metadata = new FlowableMetadata();
@Builder.Default
private final LocalDateTime createdTime = LocalDateTime.now();
private String currentNodeId;
private Status status;
@Builder.Default
private Status status = Status.RUNNING;
@Builder.Default
private LocalDateTime updatedTime = LocalDateTime.now();
public FlowableInstance(String instanceId, String currentNodeId) {
this(instanceId, currentNodeId, MapHelper.empty(), Status.RUNNING, LocalDateTime.now(), LocalDateTime.now());
}
public FlowableInstance(String instanceId, String currentNodeId, Map<String, Object> metadata) {
this(instanceId, currentNodeId, metadata, Status.RUNNING, LocalDateTime.now(), LocalDateTime.now());
}
public FlowableInstance(String instanceId, String currentNodeId, Map<String, Object> metadata, Status status, LocalDateTime createdTime, LocalDateTime updatedTime) {
this.instanceId = instanceId;
this.metadata = metadata;
this.createdTime = createdTime;
this.currentNodeId = currentNodeId;
this.status = status;
this.updatedTime = updatedTime;
}
public void addMetadata(Map<String, Object> metadata) {
this.metadata.putAll(metadata);
}

View File

@@ -0,0 +1,95 @@
package com.lanyuanxiaoyao.flowable.core.model;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import lombok.Getter;
import lombok.ToString;
/**
* 节点流转上下文
*
* @author lanyuanxiaoyao
* @version 20250103
*/
@ToString
@Getter
public class FlowableMetadata implements Serializable {
private final Map<String, Object> metadata;
public FlowableMetadata() {
this(new HashMap<>());
}
public FlowableMetadata(Map<String, Object> metadata) {
this.metadata = metadata;
}
public Object put(String key, Object value) {
return metadata.put(key, value);
}
public Object get(String key) {
return metadata.get(key);
}
public <T> T get(String key, Class<T> clazz) {
return clazz.cast(metadata.get(key));
}
public Integer getInt(String key) {
return get(key, Integer.class);
}
public Long getLong(String key) {
return get(key, Long.class);
}
public Boolean getBoolean(String key) {
return get(key, Boolean.class);
}
public boolean isEmpty() {
return metadata.isEmpty();
}
public int size() {
return metadata.size();
}
public boolean containsKey(String key) {
return metadata.containsKey(key);
}
public boolean containsValue(String value) {
return metadata.containsValue(value);
}
public Object remove(String key) {
return metadata.remove(key);
}
public void putAll(Map<String, Object> m) {
metadata.putAll(m);
}
public Object getOrDefault(String key, Object defaultValue) {
return metadata.getOrDefault(key, defaultValue);
}
public <T> T getOrDefault(String key, Class<T> clazz, T defaultValue) {
return clazz.cast(metadata.getOrDefault(key, defaultValue));
}
public Integer getIntOrDefault(String key, Integer defaultValue) {
return getOrDefault(key, Integer.class, defaultValue);
}
public Long getLongOrDefault(String key, Long defaultValue) {
return getOrDefault(key, Long.class, defaultValue);
}
public Boolean getBooleanOrDefault(String key, Boolean defaultValue) {
return getOrDefault(key, Boolean.class, defaultValue);
}
}

View File

@@ -1,9 +1,12 @@
package com.lanyuanxiaoyao.flowable.core.model;
import com.lanyuanxiaoyao.flowable.core.helper.ListHelper;
import com.lanyuanxiaoyao.flowable.core.helper.MapHelper;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
@@ -13,41 +16,32 @@ import lombok.Data;
* @version 20241231
*/
@Data
@AllArgsConstructor
@Builder
public class FlowableNode {
private final String nodeId;
private final String name;
private final String description;
private final Type type;
private final String automaticAction;
private final Map<FlowableAction, String> manualActions;
@Builder.Default
private final Type type = Type.MANUAL;
@Builder.Default
private final String handler = FlowableHandler.DefaultFlowableHandler.class.getName();
@Builder.Default
private final Map<FlowableAction, String> targets = MapHelper.empty();
private final List<String> listeners;
private final LocalDateTime createdTime;
@Builder.Default
private final String accessor = FlowableAccessor.DefaultFlowableAccessor.class.getName();
@Builder.Default
private final List<String> listeners = ListHelper.empty();
@Builder.Default
private final LocalDateTime createdTime = LocalDateTime.now();
@Builder.Default
private LocalDateTime updatedTime = LocalDateTime.now();
public FlowableNode(String nodeId, String name, String description, Type type, String automaticAction, Map<FlowableAction, String> manualActions) {
this(nodeId, name, description, type, automaticAction, manualActions, ListHelper.empty(), LocalDateTime.now());
}
public FlowableNode(String nodeId, String name, String description, Type type, String automaticAction, Map<FlowableAction, String> manualActions, List<String> listeners, LocalDateTime createdTime) {
this.nodeId = nodeId;
this.name = name;
this.description = description;
this.type = type;
this.automaticAction = automaticAction;
this.manualActions = manualActions;
this.listeners = listeners;
this.createdTime = createdTime;
}
public enum Type {
AUTOMATIC,
MANUAL,
}
public interface AutoAction {
FlowableAction action(FlowableInstance instance, FlowableNode node, Map<String, Object> metadata);
}
}

View File

@@ -1,17 +0,0 @@
package com.lanyuanxiaoyao.flowable.core;
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import java.util.Map;
/**
* @author lanyuanxiaoyao
* @version 20250102
*/
public class SimpleAutoAction implements FlowableNode.AutoAction {
@Override
public FlowableAction action(FlowableInstance instance, FlowableNode node, Map<String, Object> metadata) {
return FlowableAction.APPROVE;
}
}

36
flowable-example/pom.xml Normal file
View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>flowable</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>flowable-example</artifactId>
<dependencies>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>flowable-core</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,10 +1,13 @@
package com.lanyuanxiaoyao.flowable.core.test;
package com.lanyuanxiaoyao.flowable.test;
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.FlowableNode;
import com.lanyuanxiaoyao.flowable.test.accessor.RoleCheckAccessor;
import com.lanyuanxiaoyao.flowable.test.handler.TwoApproveHandler;
import java.util.Map;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
@@ -30,14 +33,12 @@ public abstract class TestFlowableManager {
}
private FlowableNode createManualNode(String nodeId, Map<FlowableAction, String> nextNodes) {
return new FlowableNode(
nodeId,
UUID.randomUUID().toString(),
UUID.randomUUID().toString(),
FlowableNode.Type.MANUAL,
null,
nextNodes
);
return FlowableNode.builder()
.nodeId(nodeId)
.name(UUID.randomUUID().toString())
.description(UUID.randomUUID().toString())
.targets(nextNodes)
.build();
}
/**
@@ -122,19 +123,80 @@ public abstract class TestFlowableManager {
Assertions.assertThrows(IllegalArgumentException.class, () -> manager.approve(instanceId));
}
@Test
public void testSuspend() {
FlowableManager manager = flowableManager();
FlowableNode node = FlowableNode.builder()
.nodeId("50342cb7-2029-4159-bd4c-cbfa4aebe474")
.name("50342cb7-2029-4159-bd4c-cbfa4aebe474")
.description("50342cb7-2029-4159-bd4c-cbfa4aebe474")
.handler(TwoApproveHandler.class.getName())
.build();
manager.create(node);
String instanceId = manager.start(node.getNodeId());
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
manager.approve(instanceId);
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
manager.approve(instanceId);
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
}
protected abstract Class<? extends FlowableHandler> getAutomaticNodeClass();
@Test
public void testAutomaticNode() {
FlowableManager manager = flowableManager();
FlowableNode node = new FlowableNode(
"2733d930-7a4b-491e-b1ca-4d5811435e9f",
"自动节点",
"自动节点",
FlowableNode.Type.AUTOMATIC,
"com.lanyuanxiaoyao.flowable.core.SimpleAutoAction",
null
);
FlowableNode node = FlowableNode.builder()
.nodeId("5d60dab0-7691-4543-b753-af7ac02cb7ec")
.name("5d60dab0-7691-4543-b753-af7ac02cb7ec")
.description("5d60dab0-7691-4543-b753-af7ac02cb7ec")
.type(FlowableNode.Type.AUTOMATIC)
.handler(getAutomaticNodeClass().getName())
.build();
manager.create(node);
String instanceId = manager.start(node.getNodeId());
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
}
@Test
public void testNodeContext() {
FlowableManager manager = flowableManager();
FlowableNode node1 = createManualNode(
"a3f3f055-aa0e-49ed-9bc5-d0c04f11017e",
MapHelper.of(
FlowableAction.APPROVE, "88a4ef5b-9cca-4e89-8232-24b6e9e94f4a"
)
);
FlowableNode node2 = createManualNode("88a4ef5b-9cca-4e89-8232-24b6e9e94f4a");
manager.create(node1, node2);
String instanceId = manager.start(node1.getNodeId(), MapHelper.of("name", "lanyuanxiaoyao"));
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
Assertions.assertEquals("lanyuanxiaoyao", manager.getInstance(instanceId).getMetadata().get("name"));
manager.approve(instanceId);
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
Assertions.assertEquals("88a4ef5b-9cca-4e89-8232-24b6e9e94f4a", manager.getInstance(instanceId).getCurrentNodeId());
Assertions.assertEquals("lanyuanxiaoyao", manager.getInstance(instanceId).getMetadata().get("name"));
}
@Test
public void testAccessor() {
FlowableManager manager = flowableManager();
FlowableNode node = FlowableNode.builder()
.nodeId("a3f3f055-aa0e-49ed-9bc5-d0c04f11017e")
.name("a3f3f055-aa0e-49ed-9bc5-d0c04f11017e")
.description("a3f3f055-aa0e-49ed-9bc5-d0c04f11017e")
.type(FlowableNode.Type.MANUAL)
.accessor(RoleCheckAccessor.class.getName())
.build();
manager.create(node);
String instanceId = manager.start(node.getNodeId());
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
Assertions.assertThrows(IllegalArgumentException.class, () -> manager.approve(instanceId));
manager.approve(instanceId, MapHelper.of("role", "admin"));
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
}
}

View File

@@ -0,0 +1,22 @@
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;
import com.lanyuanxiaoyao.flowable.core.model.FlowableMetadata;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import lombok.extern.slf4j.Slf4j;
/**
* @author lanyuanxiaoyao
* @version 20250103
*/
@Slf4j
public class RoleCheckAccessor implements FlowableAccessor {
@Override
public boolean access(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action) {
FlowableMetadata metadata = instance.getMetadata();
return metadata.containsKey("role");
}
}

View File

@@ -0,0 +1,32 @@
package com.lanyuanxiaoyao.flowable.test.handler;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
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.FlowableMetadata;
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
import lombok.extern.slf4j.Slf4j;
/**
* 同意两次才能通过的自动节点
*
* @author lanyuanxiaoyao
* @version 20250103
*/
@Slf4j
public class TwoApproveHandler implements FlowableHandler {
private static final String KEY = "approve-times";
@Override
public FlowableAction handle(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("{}", instance.getMetadata());
FlowableMetadata metadata = instance.getMetadata();
int approveCount = metadata.getIntOrDefault(KEY, 0);
if (approveCount + 1 > 1) {
return FlowableAction.APPROVE;
}
metadata.put(KEY, approveCount + 1);
return FlowableAction.SUSPEND;
}
}

View File

@@ -1,4 +1,4 @@
package com.lanyuanxiaoyao.flowable.core;
package com.lanyuanxiaoyao.flowable.test;
import com.lanyuanxiaoyao.flowable.core.helper.ListHelper;
import com.lanyuanxiaoyao.flowable.core.helper.StringHelper;

View File

@@ -0,0 +1,21 @@
package com.lanyuanxiaoyao.flowable.test;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
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.FlowableNode;
import lombok.extern.slf4j.Slf4j;
/**
* @author lanyuanxiaoyao
* @version 20250102
*/
@Slf4j
public class SimpleAutoHandler implements FlowableHandler {
@Override
public FlowableAction handle(FlowableConfiguration configuration, FlowableInstance instance, FlowableNode node, FlowableAction action) {
log.info("Simple handler initial");
return FlowableAction.APPROVE;
}
}

View File

@@ -1,5 +1,6 @@
package com.lanyuanxiaoyao.flowable.core;
package com.lanyuanxiaoyao.flowable.test;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableConfiguration;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import java.util.UUID;
import lombok.SneakyThrows;
@@ -10,7 +11,7 @@ import lombok.SneakyThrows;
*/
public class SimpleFlowableManager extends FlowableManager {
public SimpleFlowableManager() {
super(new InMemoryFlowableRepository(), () -> UUID.randomUUID().toString());
super(FlowableConfiguration.builder().build(), new InMemoryFlowableRepository());
}
@SneakyThrows
@@ -18,4 +19,9 @@ public class SimpleFlowableManager extends FlowableManager {
protected <T> T createBean(String classpath) {
return (T) Class.forName(classpath).newInstance();
}
@Override
protected String createId() {
return UUID.randomUUID().toString();
}
}

View File

@@ -1,7 +1,7 @@
package com.lanyuanxiaoyao.flowable.core;
package com.lanyuanxiaoyao.flowable.test;
import com.lanyuanxiaoyao.flowable.core.test.TestFlowableManager;
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
import com.lanyuanxiaoyao.flowable.core.model.FlowableHandler;
/**
* @author lanyuanxiaoyao
@@ -12,4 +12,9 @@ public class TestSimpleFlowableManager extends TestFlowableManager {
protected FlowableManager flowableManager() {
return new SimpleFlowableManager();
}
@Override
protected Class<? extends FlowableHandler> getAutomaticNodeClass() {
return SimpleAutoHandler.class;
}
}

42
pom.xml
View File

@@ -11,6 +11,7 @@
<modules>
<module>flowable-core</module>
<module>flowable-example</module>
<module>adapter/flowable-spring-boot-jpa-starter</module>
</modules>
@@ -20,14 +21,31 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>2.6.15</spring-boot.version>
<hutool.version>5.8.32</hutool.version>
<junit.version>5.8.2</junit.version>
<slf4j.version>1.7.36</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>compile</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>flowable-core</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.lanyuanxiaoyao</groupId>
<artifactId>flowable-example</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -39,7 +57,27 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.32</version>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>