完成spring jpa的存储适配
This commit is contained in:
4
.idea/encodings.xml
generated
4
.idea/encodings.xml
generated
@@ -1,6 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Encoding">
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/adapter/flowable-spring-boot-jpa-starter/src/main/java" charset="UTF-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$/src/main/java" 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" />
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
60
adapter/flowable-spring-boot-jpa-starter/pom.xml
Normal file
60
adapter/flowable-spring-boot-jpa-starter/pom.xml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?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-spring-boot-jpa-starter</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lanyuanxiaoyao</groupId>
|
||||||
|
<artifactId>flowable-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.jpa;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.repository.FlowableRepository;
|
||||||
|
import java.util.UUID;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class SpringFlowableManager extends FlowableManager {
|
||||||
|
public SpringFlowableManager(FlowableRepository flowableRepository) {
|
||||||
|
super(flowableRepository, () -> UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
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;
|
||||||
|
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;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.annotations.DynamicUpdate;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@ToString
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "flowable_history")
|
||||||
|
@DynamicUpdate
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class JpaFlowableHistory {
|
||||||
|
@Id
|
||||||
|
private String historyId;
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String instanceId;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
private FlowableAction action;
|
||||||
|
private String comment;
|
||||||
|
@CreatedDate
|
||||||
|
private LocalDateTime createdTime;
|
||||||
|
|
||||||
|
public JpaFlowableHistory(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.jpa.entity;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.annotations.DynamicUpdate;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@ToString
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "flowable_instance")
|
||||||
|
@DynamicUpdate
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class JpaFlowableInstance {
|
||||||
|
@Id
|
||||||
|
private String instanceId;
|
||||||
|
@Lob
|
||||||
|
private byte[] metadata;
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String currentNodeId;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private FlowableInstance.Status status = FlowableInstance.Status.RUNNING;
|
||||||
|
|
||||||
|
@CreatedDate
|
||||||
|
private LocalDateTime createdTime;
|
||||||
|
@LastModifiedDate
|
||||||
|
private LocalDateTime updatedTime;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public JpaFlowableInstance(FlowableInstance instance) {
|
||||||
|
this.instanceId = instance.getInstanceId();
|
||||||
|
this.metadata = objectToBytes(instance.getMetadata());
|
||||||
|
this.currentNodeId = instance.getCurrentNodeId();
|
||||||
|
this.status = instance.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] objectToBytes(Object object) throws IOException {
|
||||||
|
@Cleanup
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
@Cleanup
|
||||||
|
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
|
||||||
|
objectOutputStream.writeObject(object);
|
||||||
|
return byteArrayOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object bytesToObject(byte[] bytes) throws IOException, ClassNotFoundException {
|
||||||
|
@Cleanup
|
||||||
|
java.io.ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
|
||||||
|
@Cleanup
|
||||||
|
java.io.ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
|
||||||
|
return objectInputStream.readObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public FlowableInstance toFlowableInstance() {
|
||||||
|
return new FlowableInstance(
|
||||||
|
instanceId,
|
||||||
|
currentNodeId,
|
||||||
|
(Map<String, Object>) bytesToObject(this.metadata),
|
||||||
|
status,
|
||||||
|
createdTime,
|
||||||
|
updatedTime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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.Map;
|
||||||
|
import javax.persistence.CollectionTable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.ConstraintMode;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EntityListeners;
|
||||||
|
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;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.annotations.DynamicUpdate;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@ToString
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "flowable_node")
|
||||||
|
@DynamicUpdate
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class JpaFlowableNode {
|
||||||
|
@Id
|
||||||
|
private String nodeId;
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String name;
|
||||||
|
private String description;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private FlowableNode.RunType runType;
|
||||||
|
@ElementCollection(fetch = javax.persistence.FetchType.EAGER)
|
||||||
|
@MapKeyColumn(name = "action")
|
||||||
|
@Column(name = "nodeId")
|
||||||
|
@CollectionTable(foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
|
||||||
|
private Map<FlowableAction, String> nextNodes;
|
||||||
|
@CreatedDate
|
||||||
|
private LocalDateTime createdTime;
|
||||||
|
@LastModifiedDate
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
public JpaFlowableNode(FlowableNode node) {
|
||||||
|
this.nodeId = node.getNodeId();
|
||||||
|
this.name = node.getName();
|
||||||
|
this.description = node.getDescription();
|
||||||
|
this.runType = node.getRunType();
|
||||||
|
this.nextNodes = node.getNextNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlowableNode toFlowableNode() {
|
||||||
|
FlowableNode node = new FlowableNode(nodeId, name, description, runType, nextNodes, null, createdTime);
|
||||||
|
node.setUpdatedTime(updateTime);
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.jpa.repository;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.jpa.entity.JpaFlowableHistory;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface JpaFlowableHistoryRepository extends JpaRepository<JpaFlowableHistory, String> {
|
||||||
|
List<JpaFlowableHistory> findAllByInstanceId(String instanceId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.jpa.repository;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.jpa.entity.JpaFlowableInstance;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface JpaFlowableInstanceRepository extends JpaRepository<JpaFlowableInstance, String> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.jpa.repository;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.jpa.entity.JpaFlowableNode;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface JpaFlowableNodeRepository extends JpaRepository<JpaFlowableNode, String> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.jpa;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableJpaAuditing
|
||||||
|
public class SpringFlowableApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(SpringFlowableApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.jpa;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.test.TestFlowableManager;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
@SpringBootTest
|
||||||
|
public class TestSpringFlowableManager extends TestFlowableManager {
|
||||||
|
@Resource
|
||||||
|
private SpringFlowableManager flowableManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FlowableManager flowableManager() {
|
||||||
|
return flowableManager;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:h2:mem:testdb
|
||||||
|
username: flowable
|
||||||
|
password: flowable
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
jpa:
|
||||||
|
show-sql: true
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: create
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: info
|
||||||
|
org:
|
||||||
|
hibernate:
|
||||||
|
orm:
|
||||||
|
jdbc:
|
||||||
|
bind: trace
|
||||||
46
flowable-core/pom.xml
Normal file
46
flowable-core/pom.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?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-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>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.helper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List工具
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||||
|
public class ListHelper {
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> List<T> of(T... entries) {
|
||||||
|
return new ArrayList<>(Arrays.asList(entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> List<T> empty() {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.helper;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map工具
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||||
|
public class MapHelper {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <K, V> Map<K, V> of(Object... entries) {
|
||||||
|
if (entries.length % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("参数必须为偶数个");
|
||||||
|
}
|
||||||
|
Map<K, V> map = new HashMap<>(entries.length / 2 + 1);
|
||||||
|
for (int index = 0; index < entries.length; index += 2) {
|
||||||
|
map.put((K) entries[index], (V) entries[index + 1]);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Object> empty() {
|
||||||
|
return new HashMap<>(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.helper;
|
||||||
|
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String工具
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||||
|
public class StringHelper {
|
||||||
|
public static Boolean isBlank(String text) {
|
||||||
|
return text == null || text.trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean equals(String text1, String text2) {
|
||||||
|
return text1 != null && text1.equals(text2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
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.FlowableHistory;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableAction;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableListener;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.repository.FlowableRepository;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程服务
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class FlowableManager {
|
||||||
|
private final FlowableRepository flowableRepository;
|
||||||
|
private final IdGenerator idGenerator;
|
||||||
|
|
||||||
|
public FlowableManager(FlowableRepository flowableRepository, IdGenerator idGenerator) {
|
||||||
|
this.flowableRepository = flowableRepository;
|
||||||
|
this.idGenerator = idGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FlowableNode> listNodes() {
|
||||||
|
return flowableRepository.listNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlowableNode getNode(String nodeId) {
|
||||||
|
return flowableRepository.getNode(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FlowableInstance> listInstances() {
|
||||||
|
return flowableRepository.listInstances();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlowableInstance getInstance(String instantId) {
|
||||||
|
return flowableRepository.getInstance(instantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FlowableHistory> listHistories(String instanceId) {
|
||||||
|
return flowableRepository.listHistories(instanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlowableHistory getHistory(String historyId) {
|
||||||
|
return flowableRepository.getHistory(historyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void create(FlowableNode... node) {
|
||||||
|
flowableRepository.saveNode(ListHelper.of(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String start(String nodeId) {
|
||||||
|
return start(nodeId, MapHelper.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (FlowableNode.RunType.AUTOMATIC.equals(node.getRunType())) {
|
||||||
|
action(instance, node, FlowableAction.APPROVE, "系统审批通过", MapHelper.empty());
|
||||||
|
}
|
||||||
|
return instance.getInstanceId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void approve(String instanceId) {
|
||||||
|
approve(instanceId, "审批通过");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void approve(String instanceId, String comment) {
|
||||||
|
action(instanceId, FlowableAction.APPROVE, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reject(String instanceId) {
|
||||||
|
reject(instanceId, "审批不通过");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reject(String instanceId, String comment) {
|
||||||
|
action(instanceId, FlowableAction.REJECT, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminal(String instanceId) {
|
||||||
|
terminal(instanceId, "流程被终止");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void terminal(String instanceId, String comment) {
|
||||||
|
action(instanceId, FlowableAction.TERMINAL, comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void action(String instanceId, FlowableAction action, String comment) {
|
||||||
|
action(instanceId, action, comment, MapHelper.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 action(FlowableInstance instance, FlowableNode node, FlowableAction action, String comment, Map<String, Object> metadata) {
|
||||||
|
if (FlowableInstance.Status.COMPLETED.equals(instance.getStatus()) || FlowableInstance.Status.ERROR.equals(instance.getStatus())) {
|
||||||
|
throw new IllegalArgumentException("ID为" + instance.getInstanceId() + "的流程已结束,无法操作");
|
||||||
|
}
|
||||||
|
if (FlowableAction.TERMINAL.equals(action)) {
|
||||||
|
saveInstance(instance, FlowableInstance.Status.ERROR, metadata, action, comment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Objects.isNull(node.getNextNodes())
|
||||||
|
|| !node.getNextNodes().containsKey(action)
|
||||||
|
|| StringHelper.isBlank(node.getNextNodes().get(action))) {
|
||||||
|
saveInstance(instance, FlowableInstance.Status.COMPLETED, metadata, action, comment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String nextNodeId = node.getNextNodes().get(action);
|
||||||
|
FlowableNode nextNode = flowableRepository.getNode(nextNodeId);
|
||||||
|
instance.setCurrentNodeId(nextNode.getNodeId());
|
||||||
|
saveInstance(instance, FlowableInstance.Status.RUNNING, metadata, action, comment);
|
||||||
|
|
||||||
|
if (FlowableNode.RunType.AUTOMATIC.equals(nextNode.getRunType())) {
|
||||||
|
action(instance, node, FlowableAction.APPROVE, "系统审批通过", metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveInstance(FlowableInstance instance, FlowableInstance.Status status, Map<String, Object> metadata, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void callListeners(List<FlowableListener> listeners, Consumer<FlowableListener> handler) {
|
||||||
|
for (FlowableListener listener : listeners) {
|
||||||
|
handler.accept(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IdGenerator {
|
||||||
|
String createId();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 权限校验
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
public interface FlowableAccessor {
|
||||||
|
void access(String accessor);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点操作
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
public enum FlowableAction {
|
||||||
|
APPROVE,
|
||||||
|
REJECT,
|
||||||
|
TERMINAL,
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.model;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class FlowableHistory {
|
||||||
|
private final String historyId;
|
||||||
|
private final String instanceId;
|
||||||
|
private final FlowableAction action;
|
||||||
|
private final String comment;
|
||||||
|
private final LocalDateTime createdTime;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.model;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.helper.MapHelper;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class FlowableInstance {
|
||||||
|
private final String instanceId;
|
||||||
|
private final Map<String, Object> metadata;
|
||||||
|
private final LocalDateTime createdTime;
|
||||||
|
|
||||||
|
private String currentNodeId;
|
||||||
|
private Status status;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
RUNNING,
|
||||||
|
COMPLETED,
|
||||||
|
ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点监听
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
public interface FlowableListener {
|
||||||
|
void onStart(FlowableInstance instance);
|
||||||
|
|
||||||
|
void onError(FlowableInstance instance, Throwable throwable);
|
||||||
|
|
||||||
|
void onComplete(FlowableInstance instance);
|
||||||
|
|
||||||
|
void onApprove(FlowableInstance instance);
|
||||||
|
|
||||||
|
void onReject(FlowableInstance instance);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.model;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.helper.ListHelper;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程节点
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class FlowableNode {
|
||||||
|
private final String nodeId;
|
||||||
|
private final String name;
|
||||||
|
private final String description;
|
||||||
|
private final RunType runType;
|
||||||
|
private final Map<FlowableAction, String> nextNodes;
|
||||||
|
private final List<Class<? extends FlowableListener>> listeners;
|
||||||
|
private final LocalDateTime createdTime;
|
||||||
|
|
||||||
|
private LocalDateTime updatedTime = LocalDateTime.now();
|
||||||
|
|
||||||
|
public FlowableNode(String nodeId, String name, String description, RunType runType, Map<FlowableAction, String> nextNodes) {
|
||||||
|
this(nodeId, name, description, runType, nextNodes, ListHelper.empty(), LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FlowableNode(String nodeId, String name, String description, RunType runType, Map<FlowableAction, String> nextNodes, List<Class<? extends FlowableListener>> listeners, LocalDateTime createdTime) {
|
||||||
|
this.nodeId = nodeId;
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.runType = runType;
|
||||||
|
this.nextNodes = nextNodes;
|
||||||
|
this.listeners = listeners;
|
||||||
|
this.createdTime = createdTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RunType {
|
||||||
|
AUTOMATIC,
|
||||||
|
MANUAL,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.repository;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableHistory;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableInstance;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 存储统一管理
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
public interface FlowableRepository {
|
||||||
|
void saveNode(FlowableNode node);
|
||||||
|
|
||||||
|
void saveNode(List<FlowableNode> nodes);
|
||||||
|
|
||||||
|
FlowableNode getNode(String nodeId);
|
||||||
|
|
||||||
|
List<FlowableNode> listNodes();
|
||||||
|
|
||||||
|
void saveInstance(FlowableInstance instance);
|
||||||
|
|
||||||
|
FlowableInstance getInstance(String instantId);
|
||||||
|
|
||||||
|
List<FlowableInstance> listInstances();
|
||||||
|
|
||||||
|
void saveHistory(FlowableHistory history);
|
||||||
|
|
||||||
|
FlowableHistory getHistory(String historyId);
|
||||||
|
|
||||||
|
List<FlowableHistory> listHistories(String instanceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同时保存实例和历史记录,单独设立这里一个方法是方便数据库使用事务重写
|
||||||
|
*/
|
||||||
|
void saveInstanceAndHistory(FlowableInstance instance, FlowableHistory history);
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core.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.FlowableInstance;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.model.FlowableNode;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 集成测试
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class TestFlowableManager {
|
||||||
|
protected abstract FlowableManager flowableManager();
|
||||||
|
|
||||||
|
private FlowableNode createManualNode() {
|
||||||
|
return createManualNode(UUID.randomUUID().toString(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlowableNode createManualNode(String nodeId) {
|
||||||
|
return createManualNode(nodeId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlowableNode createManualNode(String nodeId, Map<FlowableAction, String> nextNodes) {
|
||||||
|
return new FlowableNode(
|
||||||
|
nodeId,
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
FlowableNode.RunType.MANUAL,
|
||||||
|
nextNodes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单节点审批
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testSingleNode() {
|
||||||
|
FlowableManager manager = flowableManager();
|
||||||
|
FlowableNode node1 = createManualNode();
|
||||||
|
manager.create(node1);
|
||||||
|
Assertions.assertNotNull(manager.getNode(node1.getNodeId()));
|
||||||
|
|
||||||
|
String instanceId = manager.start(node1.getNodeId());
|
||||||
|
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
|
||||||
|
Assertions.assertTrue(manager.listHistories(instanceId).isEmpty());
|
||||||
|
|
||||||
|
manager.approve(instanceId, "3ca20a11-dfb6-435b-8dcc-d00c6c0abd7f");
|
||||||
|
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
|
||||||
|
Assertions.assertEquals(1, manager.listHistories(instanceId).size());
|
||||||
|
Assertions.assertEquals(FlowableAction.APPROVE, manager.listHistories(instanceId).get(0).getAction());
|
||||||
|
Assertions.assertEquals("3ca20a11-dfb6-435b-8dcc-d00c6c0abd7f", manager.listHistories(instanceId).get(0).getComment());
|
||||||
|
|
||||||
|
instanceId = manager.start(node1.getNodeId());
|
||||||
|
manager.reject(instanceId, "3ca20a11-dfb6-435b-8dcc-d00c6c0abd7f");
|
||||||
|
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
|
||||||
|
Assertions.assertEquals(FlowableAction.REJECT, manager.listHistories(instanceId).get(0).getAction());
|
||||||
|
Assertions.assertEquals("3ca20a11-dfb6-435b-8dcc-d00c6c0abd7f", manager.listHistories(instanceId).get(0).getComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMultiNode() {
|
||||||
|
FlowableManager manager = flowableManager();
|
||||||
|
FlowableNode node1 = createManualNode(
|
||||||
|
"02779cbe-0d82-4e09-9bf8-60885400d100",
|
||||||
|
MapHelper.of(
|
||||||
|
FlowableAction.APPROVE, "1e126640-34ae-40f9-b55f-9cb8099d638f",
|
||||||
|
FlowableAction.REJECT, "02779cbe-0d82-4e09-9bf8-60885400d100"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
FlowableNode node2 = createManualNode(
|
||||||
|
"1e126640-34ae-40f9-b55f-9cb8099d638f",
|
||||||
|
MapHelper.of(FlowableAction.REJECT, "02779cbe-0d82-4e09-9bf8-60885400d100")
|
||||||
|
);
|
||||||
|
manager.create(node1, node2);
|
||||||
|
|
||||||
|
String instanceId = manager.start(node1.getNodeId());
|
||||||
|
|
||||||
|
manager.reject(instanceId);
|
||||||
|
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
|
||||||
|
Assertions.assertEquals(node1.getNodeId(), manager.getNode(manager.getInstance(instanceId).getCurrentNodeId()).getNodeId());
|
||||||
|
|
||||||
|
manager.approve(instanceId);
|
||||||
|
manager.reject(instanceId, "我觉得不行");
|
||||||
|
Assertions.assertEquals(FlowableInstance.Status.RUNNING, manager.getInstance(instanceId).getStatus());
|
||||||
|
Assertions.assertEquals(node1.getNodeId(), manager.getNode(manager.getInstance(instanceId).getCurrentNodeId()).getNodeId());
|
||||||
|
|
||||||
|
manager.approve(instanceId);
|
||||||
|
manager.approve(instanceId);
|
||||||
|
Assertions.assertEquals(FlowableInstance.Status.COMPLETED, manager.getInstance(instanceId).getStatus());
|
||||||
|
Assertions.assertEquals(node2.getNodeId(), manager.getNode(manager.getInstance(instanceId).getCurrentNodeId()).getNodeId());
|
||||||
|
|
||||||
|
Assertions.assertEquals(FlowableAction.APPROVE, manager.listHistories(instanceId).get(4).getAction());
|
||||||
|
Assertions.assertEquals(5, manager.listHistories(instanceId).size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTerminal() {
|
||||||
|
FlowableManager manager = flowableManager();
|
||||||
|
FlowableNode node1 = createManualNode();
|
||||||
|
manager.create(node1);
|
||||||
|
|
||||||
|
String instanceId = manager.start(node1.getNodeId());
|
||||||
|
|
||||||
|
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(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());
|
||||||
|
|
||||||
|
Assertions.assertThrows(IllegalArgumentException.class, () -> manager.approve(instanceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.helper.ListHelper;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.helper.StringHelper;
|
||||||
|
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 java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内存存储
|
||||||
|
*
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
public class InMemoryFlowableRepository implements FlowableRepository {
|
||||||
|
private static final Map<String, FlowableNode> nodes = new HashMap<>();
|
||||||
|
private static final Map<String, FlowableInstance> instances = new HashMap<>();
|
||||||
|
private static final Map<String, List<FlowableHistory>> histories = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveNode(FlowableNode node) {
|
||||||
|
nodes.put(node.getNodeId(), node);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveNode(List<FlowableNode> nodes) {
|
||||||
|
for (FlowableNode node : nodes) {
|
||||||
|
saveNode(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlowableNode getNode(String nodeId) {
|
||||||
|
return nodes.get(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FlowableNode> listNodes() {
|
||||||
|
return new ArrayList<>(nodes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveInstance(FlowableInstance instance) {
|
||||||
|
instances.put(instance.getInstanceId(), instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlowableInstance getInstance(String instantId) {
|
||||||
|
return instances.getOrDefault(instantId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FlowableInstance> listInstances() {
|
||||||
|
return new ArrayList<>(instances.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveHistory(FlowableHistory history) {
|
||||||
|
String instanceId = history.getInstanceId();
|
||||||
|
if (!histories.containsKey(instanceId)) {
|
||||||
|
histories.put(instanceId, new ArrayList<>());
|
||||||
|
}
|
||||||
|
histories.get(instanceId).add(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlowableHistory getHistory(String historyId) {
|
||||||
|
return histories.values()
|
||||||
|
.stream()
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.filter(history -> StringHelper.equals(history.getHistoryId(), historyId))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public List<FlowableHistory> listHistories(String instanceId) {
|
||||||
|
return histories.getOrDefault(instanceId, ListHelper.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveInstanceAndHistory(FlowableInstance instance, FlowableHistory history) {
|
||||||
|
saveInstance(instance);
|
||||||
|
saveHistory(history);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20241231
|
||||||
|
*/
|
||||||
|
public class SimpleFlowableManager extends FlowableManager {
|
||||||
|
public SimpleFlowableManager() {
|
||||||
|
super(new InMemoryFlowableRepository(), () -> UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.lanyuanxiaoyao.flowable.core;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.test.TestFlowableManager;
|
||||||
|
import com.lanyuanxiaoyao.flowable.core.manager.FlowableManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lanyuanxiaoyao
|
||||||
|
* @version 20250102
|
||||||
|
*/
|
||||||
|
public class TestSimpleFlowableManager extends TestFlowableManager {
|
||||||
|
@Override
|
||||||
|
protected FlowableManager flowableManager() {
|
||||||
|
return new SimpleFlowableManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
44
pom.xml
44
pom.xml
@@ -6,26 +6,54 @@
|
|||||||
|
|
||||||
<groupId>com.lanyuanxiaoyao</groupId>
|
<groupId>com.lanyuanxiaoyao</groupId>
|
||||||
<artifactId>flowable</artifactId>
|
<artifactId>flowable</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>flowable-core</module>
|
||||||
|
<module>adapter/flowable-spring-boot-jpa-starter</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>8</maven.compiler.source>
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
<maven.compiler.target>8</maven.compiler.target>
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
|
<spring-boot.version>2.6.15</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>com.lanyuanxiaoyao</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>flowable-core</artifactId>
|
||||||
<version>1.18.34</version>
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>junit-jupiter</artifactId>
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
<version>5.11.4</version>
|
<version>${spring-boot.version}</version>
|
||||||
<scope>test</scope>
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-core</artifactId>
|
||||||
|
<version>5.8.32</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
Reference in New Issue
Block a user