> stack = EventStackContext.getInstance().getEventClasses(traceId);
+ traceKeys.remove(traceId);
+ EventStackContext.getInstance().remove(traceId);
+ eventKeyState.remove(traceId);
+ threadLocal.remove();
+ throw new EventLoopException(stack, event);
+ }
+ EventStackContext.getInstance().addEvent(traceId, event);
+ }
+}
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/Handler.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/Handler.java
similarity index 76%
rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/Handler.java
rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/Handler.java
index d4321a03..6dda24a0 100644
--- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/Handler.java
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/Handler.java
@@ -1,4 +1,4 @@
-package com.codingapi.springboot.framework.handler;
+package com.codingapi.springboot.framework.event;
import java.lang.annotation.*;
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/HandlerBeanDefinitionRegistrar.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/HandlerBeanDefinitionRegistrar.java
similarity index 96%
rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/HandlerBeanDefinitionRegistrar.java
rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/HandlerBeanDefinitionRegistrar.java
index 3f1a76b3..de878c8b 100644
--- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/HandlerBeanDefinitionRegistrar.java
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/HandlerBeanDefinitionRegistrar.java
@@ -1,4 +1,4 @@
-package com.codingapi.springboot.framework.handler;
+package com.codingapi.springboot.framework.event;
import com.codingapi.springboot.framework.registrar.RegisterBeanScanner;
import lombok.SneakyThrows;
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java
index ca03ea12..327ca279 100644
--- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IEvent.java
@@ -1,6 +1,8 @@
package com.codingapi.springboot.framework.event;
+import java.io.Serializable;
+
/**
* 默认同步事件
*
@@ -8,7 +10,7 @@
* 事件本身不应该同步主业务的事务,即事件对于主业务来说,可成功可失败,成功与失败都不应该强关联主体业务。
* 若需要让主体业务与分支做事务同步的时候,那不应该采用事件机制,而应该直接采用调用的方式实现业务绑定。
*/
-public interface IEvent {
+public interface IEvent extends Serializable {
}
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/IHandler.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IHandler.java
similarity index 73%
rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/IHandler.java
rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IHandler.java
index 21cdecd8..226ee17d 100644
--- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/IHandler.java
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/IHandler.java
@@ -1,6 +1,5 @@
-package com.codingapi.springboot.framework.handler;
+package com.codingapi.springboot.framework.event;
-import com.codingapi.springboot.framework.event.IEvent;
import org.springframework.core.ResolvableType;
/**
@@ -19,10 +18,11 @@ public interface IHandler {
/**
* 异常回掉,在多订阅的情况下,为了实现订阅的独立性,将异常的处理放在回掉函数中。
+ * 当异常抛出以后,会阻止后续的事件执行
*
* @param exception 异常信息
*/
- default void error(Exception exception) throws Exception{
+ default void error(Exception exception) throws Exception {
throw exception;
}
@@ -32,7 +32,7 @@ default void error(Exception exception) throws Exception{
*/
default Class> getHandlerEventClass() {
ResolvableType resolvableType = ResolvableType.forClass(getClass()).as(IHandler.class);
- return (Class) resolvableType.getGeneric(0).resolve();
+ return resolvableType.getGeneric(0).resolve();
}
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringEventHandler.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringEventHandler.java
similarity index 64%
rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringEventHandler.java
rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringEventHandler.java
index 9796511b..5ec7b9ba 100644
--- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringEventHandler.java
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringEventHandler.java
@@ -1,6 +1,5 @@
-package com.codingapi.springboot.framework.handler;
+package com.codingapi.springboot.framework.event;
-import com.codingapi.springboot.framework.event.DomainEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
@@ -28,11 +27,23 @@ public SpringEventHandler(List handlers) {
@Override
public void onApplicationEvent(DomainEvent domainEvent) {
+ String traceId = domainEvent.getTraceId();
+
if (domainEvent.isSync()) {
- ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent());
+ try {
+ EventTraceContext.getInstance().createEventKey(traceId);
+ ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent());
+ } finally {
+ EventTraceContext.getInstance().checkEventState();
+ }
} else {
executorService.execute(() -> {
- ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent());
+ try {
+ EventTraceContext.getInstance().createEventKey(traceId);
+ ApplicationHandlerUtils.getInstance().handler(domainEvent.getEvent());
+ } finally {
+ EventTraceContext.getInstance().checkEventState();
+ }
});
}
}
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringHandlerConfiguration.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringHandlerConfiguration.java
similarity index 89%
rename from springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringHandlerConfiguration.java
rename to springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringHandlerConfiguration.java
index 0f5c989c..e3afeb12 100644
--- a/springboot-starter/src/main/java/com/codingapi/springboot/framework/handler/SpringHandlerConfiguration.java
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/event/SpringHandlerConfiguration.java
@@ -1,4 +1,4 @@
-package com.codingapi.springboot.framework.handler;
+package com.codingapi.springboot.framework.event;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventException.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventException.java
new file mode 100644
index 00000000..68462e73
--- /dev/null
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventException.java
@@ -0,0 +1,17 @@
+package com.codingapi.springboot.framework.exception;
+
+import lombok.Getter;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Getter
+public class EventException extends RuntimeException {
+
+ private final List error;
+
+ public EventException(List error) {
+ super(error.stream().map(Exception::getMessage).collect(Collectors.joining("\n")));
+ this.error = error;
+ }
+}
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventLoopException.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventLoopException.java
new file mode 100644
index 00000000..cb17f046
--- /dev/null
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/exception/EventLoopException.java
@@ -0,0 +1,17 @@
+package com.codingapi.springboot.framework.exception;
+
+import com.codingapi.springboot.framework.event.IEvent;
+import lombok.Getter;
+
+import java.util.List;
+
+@Getter
+public class EventLoopException extends RuntimeException {
+
+ private final List> stack;
+
+ public EventLoopException(List> stack, IEvent event) {
+ super("event loop error current event class:" + event.getClass() + ", history event stack:" + stack);
+ this.stack = stack;
+ }
+}
diff --git a/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/RandomGenerator.java b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/RandomGenerator.java
new file mode 100644
index 00000000..d81723c6
--- /dev/null
+++ b/springboot-starter/src/main/java/com/codingapi/springboot/framework/utils/RandomGenerator.java
@@ -0,0 +1,22 @@
+package com.codingapi.springboot.framework.utils;
+
+import java.util.UUID;
+
+public class RandomGenerator {
+
+ public static String generateUUID() {
+ return UUID.randomUUID().toString();
+ }
+
+
+ public static String randomString(int length) {
+ String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ int number = (int) (Math.random() * str.length());
+ sb.append(str.charAt(number));
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/springboot-starter/src/main/resources/META-INF/spring.factories b/springboot-starter/src/main/resources/META-INF/spring.factories
index 5765e102..3fd6c549 100644
--- a/springboot-starter/src/main/resources/META-INF/spring.factories
+++ b/springboot-starter/src/main/resources/META-INF/spring.factories
@@ -2,6 +2,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.codingapi.springboot.framework.AutoConfiguration,\
com.codingapi.springboot.framework.event.SpringEventConfiguration,\
com.codingapi.springboot.framework.exception.ExceptionConfiguration,\
-com.codingapi.springboot.framework.handler.HandlerBeanDefinitionRegistrar,\
-com.codingapi.springboot.framework.handler.SpringHandlerConfiguration,\
-com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration
\ No newline at end of file
+com.codingapi.springboot.framework.event.HandlerBeanDefinitionRegistrar,\
+com.codingapi.springboot.framework.event.SpringHandlerConfiguration,\
+com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration
diff --git a/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 39319e71..8aa6f4cd 100644
--- a/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/springboot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,6 +1,6 @@
com.codingapi.springboot.framework.AutoConfiguration
com.codingapi.springboot.framework.event.SpringEventConfiguration
com.codingapi.springboot.framework.exception.ExceptionConfiguration
-com.codingapi.springboot.framework.handler.HandlerBeanDefinitionRegistrar
-com.codingapi.springboot.framework.handler.SpringHandlerConfiguration
-com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration
\ No newline at end of file
+com.codingapi.springboot.framework.event.HandlerBeanDefinitionRegistrar
+com.codingapi.springboot.framework.event.SpringHandlerConfiguration
+com.codingapi.springboot.framework.servlet.BasicHandlerExceptionResolverConfiguration
diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java
index 0f2d3bb1..f1efb83b 100644
--- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java
+++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoChangeLogHandler.java
@@ -1,6 +1,8 @@
package com.codingapi.springboot.framework.handler;
import com.codingapi.springboot.framework.event.DemoChangeEvent;
+import com.codingapi.springboot.framework.event.Handler;
+import com.codingapi.springboot.framework.event.IHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java
index f48c9fcc..01ab9aa5 100644
--- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java
+++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoCreateHandler.java
@@ -1,6 +1,8 @@
package com.codingapi.springboot.framework.handler;
import com.codingapi.springboot.framework.domain.event.DomainCreateEvent;
+import com.codingapi.springboot.framework.event.Handler;
+import com.codingapi.springboot.framework.event.IHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java
index 89885b16..6f02be80 100644
--- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java
+++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoDeleteHandler.java
@@ -1,6 +1,8 @@
package com.codingapi.springboot.framework.handler;
import com.codingapi.springboot.framework.domain.event.DomainDeleteEvent;
+import com.codingapi.springboot.framework.event.Handler;
+import com.codingapi.springboot.framework.event.IHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java
index cb3dae62..2d18934f 100644
--- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java
+++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/DemoPersistEventHandler.java
@@ -1,11 +1,13 @@
package com.codingapi.springboot.framework.handler;
import com.codingapi.springboot.framework.domain.event.DomainPersistEvent;
+import com.codingapi.springboot.framework.event.Handler;
+import com.codingapi.springboot.framework.event.IHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Handler
-public class DemoPersistEventHandler implements IHandler{
+public class DemoPersistEventHandler implements IHandler {
@Override
public void handler(DomainPersistEvent event) {
diff --git a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java
index b6bf661f..d4fb12b0 100644
--- a/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java
+++ b/springboot-starter/src/test/java/com/codingapi/springboot/framework/handler/EntityFiledChangeHandler.java
@@ -1,11 +1,13 @@
package com.codingapi.springboot.framework.handler;
import com.codingapi.springboot.framework.domain.event.DomainChangeEvent;
+import com.codingapi.springboot.framework.event.Handler;
+import com.codingapi.springboot.framework.event.IHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Handler
-public class EntityFiledChangeHandler implements IHandler{
+public class EntityFiledChangeHandler implements IHandler {
@Override
public void handler(DomainChangeEvent event) {
From bae16217bef88c749159a986b61195ec497082c9 Mon Sep 17 00:00:00 2001
From: lorne <1991wangliang@gmail.com>
Date: Sat, 9 Nov 2024 10:53:22 +0800
Subject: [PATCH 053/101] add flow & update event
---
pom.xml | 41 +-
springboot-starter-data-fast/pom.xml | 2 +-
springboot-starter-flow/README.md | 39 +
springboot-starter-flow/pom.xml | 52 ++
.../springboot/flow/FlowConfiguration.java | 17 +
.../flow/FlowFrameworkRegister.java | 17 +
.../flow/bind/BindDataSnapshot.java | 55 ++
.../springboot/flow/bind/IBindData.java | 18 +
.../flow/build/FlowWorkBuilder.java | 123 +++
.../springboot/flow/build/SchemaReader.java | 92 ++
.../springboot/flow/content/FlowSession.java | 77 ++
.../flow/content/FlowSessionBeanProvider.java | 30 +
.../springboot/flow/domain/FlowNode.java | 303 +++++++
.../springboot/flow/domain/FlowRelation.java | 168 ++++
.../springboot/flow/domain/FlowWork.java | 332 +++++++
.../springboot/flow/domain/Opinion.java | 78 ++
.../springboot/flow/em/ApprovalType.java | 26 +
.../flow/em/FlowSourceDirection.java | 30 +
.../springboot/flow/em/FlowStatus.java | 27 +
.../springboot/flow/em/FlowType.java | 30 +
.../springboot/flow/em/NodeType.java | 30 +
.../springboot/flow/error/ErrTrigger.java | 36 +
.../springboot/flow/error/ErrorResult.java | 17 +
.../springboot/flow/error/NodeResult.java | 16 +
.../springboot/flow/error/OperatorResult.java | 25 +
.../flow/event/FlowApprovalEvent.java | 88 ++
.../flow/generator/TitleGenerator.java | 47 +
.../flow/matcher/OperatorMatcher.java | 115 +++
.../springboot/flow/pojo/FlowDetail.java | 95 ++
.../springboot/flow/pojo/FlowResult.java | 26 +
.../flow/query/FlowRecordQuery.java | 55 ++
.../springboot/flow/record/FlowBackup.java | 61 ++
.../springboot/flow/record/FlowProcess.java | 42 +
.../springboot/flow/record/FlowRecord.java | 417 +++++++++
.../flow/repository/FlowBackupRepository.java | 33 +
.../repository/FlowBindDataRepository.java | 31 +
.../repository/FlowOperatorRepository.java | 29 +
.../repository/FlowProcessRepository.java | 16 +
.../flow/repository/FlowRecordRepository.java | 70 ++
.../flow/repository/FlowWorkRepository.java | 18 +
.../flow/script/GroovyShellContext.java | 87 ++
.../serializable/FlowNodeSerializable.java | 94 ++
.../FlowRelationSerializable.java | 82 ++
.../serializable/FlowWorkSerializable.java | 140 +++
.../flow/service/FlowDirectionService.java | 126 +++
.../service/FlowRecordBuilderService.java | 251 ++++++
.../flow/service/FlowRecordService.java | 192 ++++
.../springboot/flow/service/FlowService.java | 522 +++++++++++
.../springboot/flow/trigger/OutTrigger.java | 44 +
.../springboot/flow/user/IFlowOperator.java | 36 +
.../springboot/flow/utils/Sha256Utils.java | 25 +
...ot.autoconfigure.AutoConfiguration.imports | 1 +
.../springboot/flow/FlowTestApplication.java | 12 +
.../codingapi/springboot/flow/flow/Leave.java | 24 +
.../repository/FlowBackupRepositoryImpl.java | 29 +
.../FlowBindDataRepositoryImpl.java | 38 +
.../repository/FlowProcessRepositoryImpl.java | 39 +
.../repository/FlowRecordRepositoryImpl.java | 104 +++
.../repository/FlowWorkRepositoryImpl.java | 34 +
.../flow/repository/LeaveRepository.java | 18 +
.../flow/repository/UserRepository.java | 39 +
.../flow/script/GroovyShellContextTest.java | 30 +
.../springboot/flow/test/BuildTest.java | 49 ++
.../springboot/flow/test/ErrorTest.java | 218 +++++
.../springboot/flow/test/FlowTest.java | 821 ++++++++++++++++++
.../springboot/flow/test/FlowTest2.java | 113 +++
.../flow/test/MultiRelationFlowTest.java | 314 +++++++
.../springboot/flow/test/QueryTest.java | 540 ++++++++++++
.../springboot/flow/test/ScriptBuildTest.java | 43 +
.../springboot/flow/test/ScriptTest.java | 72 ++
.../springboot/flow/test/SignTest.java | 415 +++++++++
.../codingapi/springboot/flow/user/User.java | 46 +
springboot-starter-security/pom.xml | 2 +-
springboot-starter/pom.xml | 2 +-
.../event/ApplicationHandlerUtils.java | 38 +-
.../springboot/framework/event/IEvent.java | 1 -
.../springboot/framework/event/IHandler.java | 17 +-
77 files changed, 7452 insertions(+), 30 deletions(-)
create mode 100644 springboot-starter-flow/README.md
create mode 100644 springboot-starter-flow/pom.xml
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowConfiguration.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowFrameworkRegister.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/BindDataSnapshot.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSession.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSessionBeanProvider.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowRelation.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowWork.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/Opinion.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/ApprovalType.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowSourceDirection.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowStatus.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowType.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/NodeType.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrTrigger.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrorResult.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/NodeResult.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/OperatorResult.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/generator/TitleGenerator.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/matcher/OperatorMatcher.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowDetail.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/pojo/FlowResult.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/query/FlowRecordQuery.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowBackup.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowProcess.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/record/FlowRecord.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBackupRepository.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowBindDataRepository.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowOperatorRepository.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowProcessRepository.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowRecordRepository.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/repository/FlowWorkRepository.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/script/GroovyShellContext.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowNodeSerializable.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowRelationSerializable.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/serializable/FlowWorkSerializable.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowDirectionService.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowRecordBuilderService.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowRecordService.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/service/FlowService.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/trigger/OutTrigger.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/user/IFlowOperator.java
create mode 100644 springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/utils/Sha256Utils.java
create mode 100644 springboot-starter-flow/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/FlowTestApplication.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/flow/Leave.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBackupRepositoryImpl.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowBindDataRepositoryImpl.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowProcessRepositoryImpl.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowRecordRepositoryImpl.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/FlowWorkRepositoryImpl.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/LeaveRepository.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/repository/UserRepository.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/script/GroovyShellContextTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/BuildTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ErrorTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/FlowTest2.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/MultiRelationFlowTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/QueryTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptBuildTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/ScriptTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/test/SignTest.java
create mode 100644 springboot-starter-flow/src/test/java/com/codingapi/springboot/flow/user/User.java
diff --git a/pom.xml b/pom.xml
index f93e9daf..ceb40070 100644
--- a/pom.xml
+++ b/pom.xml
@@ -12,7 +12,7 @@
com.codingapi.springboot
springboot-parent
- 2.8.11
+ 2.9.0
https://github.com/codingapi/springboot-framewrok
springboot-parent
@@ -31,18 +31,20 @@
1.6.13
3.1.0
${project.version}
- 2.0.42
- 0.12.5
- 2.15.0
+ 2.0.53
+ 0.12.6
+ 2.17.0
+ 3.17.0
1.8.1
- 1.11.0
+ 1.12.0
0.10.2
0.9.16
- 1.77
+ 1.79
1.2.0
2.2
- 4.0.15
- 2.2.224
+ 4.0.24
+ 2.3.232
+ 5.6.2
@@ -75,6 +77,12 @@
+
+ com.esotericsoftware
+ kryo
+ ${esotericsoftware.kryo.version}
+
+
com.h2database
h2
@@ -99,6 +107,18 @@
${commons-crypto.version}
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+
+ org.apache.commons
+ commons-lang3
+ ${commons-lang3.version}
+
+
org.perf4j
perf4j
@@ -249,6 +269,7 @@
springboot-starter
+ springboot-starter-flow
springboot-starter-security
springboot-starter-data-fast
@@ -260,6 +281,7 @@
springboot-starter
+ springboot-starter-flow
springboot-starter-security
springboot-starter-data-fast
@@ -289,7 +311,7 @@
org.openclover
clover-maven-plugin
- 4.4.1
+ 4.5.2
true
true
@@ -309,6 +331,7 @@
springboot-starter
+ springboot-starter-flow
springboot-starter-security
springboot-starter-data-fast
diff --git a/springboot-starter-data-fast/pom.xml b/springboot-starter-data-fast/pom.xml
index a2fc1cdf..012a2759 100644
--- a/springboot-starter-data-fast/pom.xml
+++ b/springboot-starter-data-fast/pom.xml
@@ -5,7 +5,7 @@
springboot-parent
com.codingapi.springboot
- 2.8.11
+ 2.9.0
4.0.0
diff --git a/springboot-starter-flow/README.md b/springboot-starter-flow/README.md
new file mode 100644
index 00000000..0bea8953
--- /dev/null
+++ b/springboot-starter-flow/README.md
@@ -0,0 +1,39 @@
+# springboot-starter-flow 流程引擎
+
+流程引擎支持的功能需要包括:
+
+支持的功能如下:
+
+流程管理
+1. build模式的流程设计
+2. schema模式的流程设计
+3. 流程的启用与禁用
+4. 流程快照的存储
+
+流程设计
+1. 支持自定义节点与节点关系
+2. 支持自定义节点的操作用户,可通过groovy脚本定义
+3. 支持流程消息标题的自定义能力,可通过groovy脚本定义
+4. 支持流程异常状态的自定义能力,可通过groovy脚本定义
+5. 提供流程操作过程中的事件,可以做业务定制与延伸
+
+
+流程能力
+1. 流程发起
+ 在设计完成以后并启用以后,可通过FlowService对象发起流程。
+2. 流程审批
+ 流程的审批支持同意与拒绝,以及审批意见的填写。
+3. 流程撤销
+ 流程的发起以后,在下一节点的流程待审批且未读之前可以撤销流程。
+4. 流程转办
+ 流程的审批过程中,可以将流程转办给其他人员审批。
+5. 流程委托
+ 可设置用户的委托人,委托人可以代理委托人审批流程。
+6. 流程催办
+ 流程的审批过程中,可以催办审批人员,催办将会发送催办事件消息。
+7. 流程查询
+ 可以查询流程的待办、已办、超时、延期、全部流程等数据。
+8. 流程干预
+ 设置流程管理员的人员,可以对流程进行干预,可以直接对其他人的流程进行审批。
+9. 流程延期
+ 流程的审批过程中,可以延期流程的审批时间。
diff --git a/springboot-starter-flow/pom.xml b/springboot-starter-flow/pom.xml
new file mode 100644
index 00000000..c2d61cc1
--- /dev/null
+++ b/springboot-starter-flow/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+ springboot-parent
+ com.codingapi.springboot
+ 2.9.0
+
+
+ springboot-starter-flow
+ springboot-starter-flow project for Spring Boot
+ springboot-starter-flow
+
+
+ 8
+
+
+
+
+
+
+ com.codingapi.springboot
+ springboot-starter
+
+
+
+ com.esotericsoftware
+ kryo
+
+
+
+ org.apache.groovy
+ groovy
+
+
+
+ org.apache.groovy
+ groovy-json
+
+
+
+ org.apache.groovy
+ groovy-xml
+
+
+
+
+
+
+
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowConfiguration.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowConfiguration.java
new file mode 100644
index 00000000..7948386e
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowConfiguration.java
@@ -0,0 +1,17 @@
+package com.codingapi.springboot.flow;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class FlowConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public FlowFrameworkRegister flowFrameworkRegister(ApplicationContext spring) {
+ return new FlowFrameworkRegister(spring);
+ }
+
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowFrameworkRegister.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowFrameworkRegister.java
new file mode 100644
index 00000000..5fb46b8f
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/FlowFrameworkRegister.java
@@ -0,0 +1,17 @@
+package com.codingapi.springboot.flow;
+
+import com.codingapi.springboot.flow.content.FlowSessionBeanProvider;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+
+@AllArgsConstructor
+public class FlowFrameworkRegister implements InitializingBean {
+
+ private final ApplicationContext application;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ FlowSessionBeanProvider.getInstance().register(application);
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/BindDataSnapshot.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/BindDataSnapshot.java
new file mode 100644
index 00000000..b2d0b1a8
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/BindDataSnapshot.java
@@ -0,0 +1,55 @@
+package com.codingapi.springboot.flow.bind;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 数据快照
+ */
+@Setter
+@Getter
+@AllArgsConstructor
+public class BindDataSnapshot {
+
+ /**
+ * 数据快照id
+ */
+ private long id;
+ /**
+ * 快照信息
+ */
+ private String snapshot;
+ /**
+ * 创建时间
+ */
+ private long createTime;
+
+ /**
+ * 数据绑定类名称
+ */
+ private String clazzName;
+
+ public BindDataSnapshot(long id,IBindData bindData) {
+ if (bindData == null) {
+ throw new IllegalArgumentException("bind data is null");
+ }
+ this.snapshot = bindData.toJsonSnapshot();
+ this.clazzName = bindData.getClass().getName();
+ this.createTime = System.currentTimeMillis();
+ this.id = id;
+ }
+
+ public BindDataSnapshot(IBindData bindData) {
+ this(0,bindData);
+ }
+
+ public IBindData toBindData() {
+ try {
+ return JSONObject.parseObject(snapshot, (Class extends IBindData>) Class.forName(clazzName));
+ } catch (Exception e) {
+ throw new IllegalArgumentException("bind data error");
+ }
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java
new file mode 100644
index 00000000..6a7287bc
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/bind/IBindData.java
@@ -0,0 +1,18 @@
+package com.codingapi.springboot.flow.bind;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * 数据绑定接口
+ */
+public interface IBindData {
+
+ /**
+ * 数据快照
+ *
+ * @return 数据快照
+ */
+ default String toJsonSnapshot() {
+ return JSONObject.toJSONString(this);
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java
new file mode 100644
index 00000000..08d89bf5
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/FlowWorkBuilder.java
@@ -0,0 +1,123 @@
+package com.codingapi.springboot.flow.build;
+
+import com.codingapi.springboot.flow.domain.FlowNode;
+import com.codingapi.springboot.flow.domain.FlowRelation;
+import com.codingapi.springboot.flow.domain.FlowWork;
+import com.codingapi.springboot.flow.em.ApprovalType;
+import com.codingapi.springboot.flow.em.NodeType;
+import com.codingapi.springboot.flow.error.ErrTrigger;
+import com.codingapi.springboot.flow.generator.TitleGenerator;
+import com.codingapi.springboot.flow.matcher.OperatorMatcher;
+import com.codingapi.springboot.flow.trigger.OutTrigger;
+import com.codingapi.springboot.flow.user.IFlowOperator;
+import com.codingapi.springboot.framework.utils.RandomGenerator;
+
+/**
+ * 流程工作构建器
+ */
+public class FlowWorkBuilder {
+
+ private FlowWork work = null;
+
+ private FlowWorkBuilder(FlowWork flowWork) {
+ this.work = flowWork;
+ }
+
+
+ public static FlowWorkBuilder builder(IFlowOperator flowOperator) {
+ return new FlowWorkBuilder(new FlowWork(flowOperator));
+ }
+
+ public FlowWorkBuilder description(String description) {
+ this.work.setDescription(description);
+ return this;
+ }
+
+ public FlowWorkBuilder postponedMax(int postponedMax) {
+ this.work.setPostponedMax(postponedMax);
+ return this;
+ }
+
+ public FlowWorkBuilder title(String title) {
+ this.work.setTitle(title);
+ return this;
+ }
+
+ public FlowWorkBuilder schema(String schema) {
+ this.work.schema(schema);
+ return this;
+ }
+
+
+ public Nodes nodes() {
+ return new Nodes();
+ }
+
+ public Relations relations() {
+ return new Relations();
+ }
+
+ public FlowWork build() {
+ work.enable();
+ return work;
+ }
+
+
+ public class Nodes {
+
+ public Nodes node(String id,String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, long timeout, TitleGenerator titleGenerator, ErrTrigger errTrigger, boolean editable) {
+ FlowNode node = new FlowNode(id, name, code, view, NodeType.parser(code), approvalType, titleGenerator, operatorMatcher, timeout, errTrigger, editable);
+ work.addNode(node);
+ return this;
+ }
+
+ public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher,long timeout, boolean editable) {
+ return node(RandomGenerator.generateUUID(),name, code, view, approvalType, operatorMatcher, timeout, TitleGenerator.defaultTitleGenerator(), null, editable);
+ }
+ public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, boolean editable) {
+ return node(RandomGenerator.generateUUID(),name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), null, editable);
+ }
+
+ public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher) {
+ return node(name, code, view, approvalType, operatorMatcher, true);
+ }
+
+ public Nodes node(String name, String code, String view, ApprovalType approvalType, OperatorMatcher operatorMatcher, ErrTrigger errTrigger, boolean editable) {
+ return node(RandomGenerator.generateUUID(),name, code, view, approvalType, operatorMatcher, 0, TitleGenerator.defaultTitleGenerator(), errTrigger, editable);
+ }
+
+
+ public Relations relations() {
+ return new Relations();
+ }
+
+ public FlowWork build() {
+ work.enable();
+ return work;
+ }
+
+
+ }
+
+ public class Relations {
+
+ public Relations relation(String name, String source, String target) {
+ return relation(name,source,target,OutTrigger.defaultOutTrigger(),1,false);
+ }
+
+ public Relations relation(String name, String source, String target, OutTrigger outTrigger,int order, boolean back) {
+ FlowNode from = work.getNodeByCode(source);
+ FlowNode to = work.getNodeByCode(target);
+ FlowRelation relation = new FlowRelation(RandomGenerator.generateUUID(), name, from, to, outTrigger,order, back);
+ work.addRelation(relation);
+ return this;
+ }
+
+ public FlowWork build() {
+ work.enable();
+ return work;
+ }
+
+
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java
new file mode 100644
index 00000000..bb5abd75
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/build/SchemaReader.java
@@ -0,0 +1,92 @@
+package com.codingapi.springboot.flow.build;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.codingapi.springboot.flow.domain.FlowNode;
+import com.codingapi.springboot.flow.domain.FlowRelation;
+import com.codingapi.springboot.flow.em.ApprovalType;
+import com.codingapi.springboot.flow.em.NodeType;
+import com.codingapi.springboot.flow.error.ErrTrigger;
+import com.codingapi.springboot.flow.generator.TitleGenerator;
+import com.codingapi.springboot.flow.matcher.OperatorMatcher;
+import com.codingapi.springboot.flow.trigger.OutTrigger;
+import lombok.Getter;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 流程设计schema读取器
+ */
+public class SchemaReader {
+
+ private final JSONObject data;
+
+ @Getter
+ private final List flowNodes;
+ @Getter
+ private final List flowRelations;
+
+ public SchemaReader(String schema) {
+ this.data = JSONObject.parseObject(schema);
+ this.flowNodes = new ArrayList<>();
+ this.flowRelations = new ArrayList<>();
+ this.loadNodes();
+ this.loadEdges();
+ }
+
+
+ private void loadNodes(){
+ JSONArray nodes = data.getJSONArray("nodes");
+ for (int i = 0; i < nodes.size(); i++) {
+ JSONObject node = nodes.getJSONObject(i);
+ JSONObject properties = node.getJSONObject("properties");
+ String code = properties.getString("code");
+ String operatorMatcher = properties.getString("operatorMatcher");
+ String titleGenerator = properties.getString("titleGenerator");
+ String name = properties.getString("name");
+ boolean editable = properties.getBoolean("editable");
+ String view = properties.getString("view");
+ String type = properties.getString("type");
+ String approvalType = properties.getString("approvalType");
+ int timeout = properties.getIntValue("timeout");
+ String errTrigger = properties.getString("errTrigger");
+ String id = properties.getString("id");
+ FlowNode flowNode = new FlowNode(id,name,code,view, NodeType.parser(type),ApprovalType.parser(approvalType),new TitleGenerator(titleGenerator),
+ new OperatorMatcher(operatorMatcher),timeout, StringUtils.hasLength(errTrigger)?new ErrTrigger(errTrigger):null,editable);
+ flowNodes.add(flowNode);
+ }
+ }
+
+ private FlowNode getFlowNodeById(String id){
+ for(FlowNode flowNode:flowNodes){
+ if(flowNode.getId().equals(id)){
+ return flowNode;
+ }
+ }
+ return null;
+ }
+
+ private void loadEdges(){
+ JSONArray edges = data.getJSONArray("edges");
+ for(int i=0;i historyRecords;
+ private final FlowSessionBeanProvider provider;
+
+ public FlowSession(FlowWork flowWork, FlowNode flowNode, IFlowOperator createOperator, IFlowOperator currentOperator, IBindData bindData, Opinion opinion, List historyRecords) {
+ this.flowWork = flowWork;
+ this.flowNode = flowNode;
+ this.createOperator = createOperator;
+ this.currentOperator = currentOperator;
+ this.bindData = bindData;
+ this.opinion = opinion;
+ this.historyRecords = historyRecords;
+ this.provider = FlowSessionBeanProvider.getInstance();
+ }
+
+
+ public Object getBean(String beanName) {
+ return provider.getBean(beanName);
+ }
+
+ /**
+ * 创建节点结果
+ *
+ * @param nodeCode 节点code
+ * @return 节点结果
+ */
+ public NodeResult createNodeErrTrigger(String nodeCode) {
+ return new NodeResult(nodeCode);
+ }
+
+ /**
+ * 创建操作者结果
+ *
+ * @param operatorIds 操作者id
+ * @return 操作者结果
+ */
+ public OperatorResult createOperatorErrTrigger(List operatorIds) {
+ return new OperatorResult(operatorIds);
+ }
+
+ /**
+ * 创建操作者结果
+ *
+ * @param operatorIds 操作者id
+ * @return 操作者结果
+ */
+ public OperatorResult createOperatorErrTrigger(long... operatorIds) {
+ return new OperatorResult(operatorIds);
+ }
+
+
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSessionBeanProvider.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSessionBeanProvider.java
new file mode 100644
index 00000000..58b9351d
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/content/FlowSessionBeanProvider.java
@@ -0,0 +1,30 @@
+package com.codingapi.springboot.flow.content;
+
+import lombok.Getter;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * 流程回话 spring bean 提供者
+ */
+public class FlowSessionBeanProvider {
+
+ @Getter
+ private static final FlowSessionBeanProvider instance = new FlowSessionBeanProvider();
+
+ private FlowSessionBeanProvider() {
+ }
+
+ private ApplicationContext spring;
+
+ public void register(ApplicationContext spring) {
+ this.spring = spring;
+ }
+
+ public Object getBean(String beanName) {
+ if (spring != null) {
+ return spring.getBean(beanName);
+ }
+ return null;
+ }
+
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java
new file mode 100644
index 00000000..7d9291ed
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowNode.java
@@ -0,0 +1,303 @@
+package com.codingapi.springboot.flow.domain;
+
+import com.codingapi.springboot.flow.bind.BindDataSnapshot;
+import com.codingapi.springboot.flow.content.FlowSession;
+import com.codingapi.springboot.flow.em.*;
+import com.codingapi.springboot.flow.error.ErrTrigger;
+import com.codingapi.springboot.flow.error.ErrorResult;
+import com.codingapi.springboot.flow.generator.TitleGenerator;
+import com.codingapi.springboot.flow.matcher.OperatorMatcher;
+import com.codingapi.springboot.flow.record.FlowRecord;
+import com.codingapi.springboot.flow.repository.FlowOperatorRepository;
+import com.codingapi.springboot.flow.serializable.FlowNodeSerializable;
+import com.codingapi.springboot.flow.user.IFlowOperator;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+
+/**
+ * 流程节点
+ */
+@Getter
+@AllArgsConstructor
+public class FlowNode {
+
+ public static final String CODE_START = "start";
+ public static final String CODE_OVER = "over";
+
+ /**
+ * 节点id
+ */
+ private String id;
+
+ /**
+ * 节点编码
+ */
+ private String code;
+
+ /**
+ * 节点名称
+ */
+ private String name;
+
+ /**
+ * 节点标题创建规则
+ */
+ private TitleGenerator titleGenerator;
+
+ /**
+ * 节点类型 | 分为发起、审批、结束
+ */
+ private NodeType type;
+
+ /**
+ * 节点视图
+ */
+ private String view;
+
+ /**
+ * 流程审批类型 | 分为会签、非会签
+ */
+ private ApprovalType approvalType;
+
+ /**
+ * 操作者匹配器
+ */
+ private OperatorMatcher operatorMatcher;
+
+ /**
+ * 是否可编辑
+ */
+ private boolean editable;
+
+ /**
+ * 创建时间
+ */
+ private long createTime;
+ /**
+ * 更新时间
+ */
+ private long updateTime;
+
+ /**
+ * 超时时间(毫秒)
+ */
+ private long timeout;
+
+ /**
+ * 异常触发器,当流程发生异常时异常通常是指找不到审批人,将会触发异常触发器,异常触发器可以是一个节点
+ */
+ @Setter
+ private ErrTrigger errTrigger;
+
+
+ public void verify(){
+ if (this.titleGenerator == null) {
+ throw new IllegalArgumentException("titleGenerator is null");
+ }
+ if (this.operatorMatcher == null) {
+ throw new IllegalArgumentException("operatorMatcher is null");
+ }
+ if(timeout<0){
+ throw new IllegalArgumentException("timeout is less than 0");
+ }
+ if(!StringUtils.hasLength(id)){
+ throw new IllegalArgumentException("id is empty");
+ }
+ if(!StringUtils.hasLength(code)){
+ throw new IllegalArgumentException("code is empty");
+ }
+ }
+
+
+ /**
+ * 从序列化对象中创建节点
+ *
+ * @return FlowNodeSerializable 序列号节点
+ */
+ public FlowNodeSerializable toSerializable() {
+ return new FlowNodeSerializable(
+ this.id,
+ this.code,
+ this.name,
+ this.titleGenerator.getScript(),
+ this.type,
+ this.view,
+ this.approvalType,
+ this.operatorMatcher.getScript(),
+ this.editable,
+ this.createTime,
+ this.updateTime,
+ this.timeout,
+ this.errTrigger == null ? null : this.errTrigger.getScript()
+ );
+ }
+
+
+ public FlowNode(String id,
+ String name,
+ String code,
+ String view,
+ NodeType type,
+ ApprovalType approvalType,
+ TitleGenerator titleGenerator,
+ OperatorMatcher operatorMatcher,
+ long timeout,
+ ErrTrigger errTrigger,
+ boolean editable) {
+ this.id = id;
+ this.code = code;
+ this.name = name;
+ this.titleGenerator = titleGenerator;
+ this.type = type;
+ this.view = view;
+ this.approvalType = approvalType;
+ this.operatorMatcher = operatorMatcher;
+ this.createTime = System.currentTimeMillis();
+ this.updateTime = System.currentTimeMillis();
+ this.errTrigger = errTrigger;
+ this.timeout = timeout;
+ this.editable = editable;
+ }
+
+
+ /**
+ * 加载节点的操作者
+ *
+ * @param flowSession 操作内容
+ * @return 是否匹配
+ */
+ public List extends IFlowOperator> loadFlowNodeOperator(FlowSession flowSession, FlowOperatorRepository flowOperatorRepository) {
+ return flowOperatorRepository.findByIds(this.operatorMatcher.matcher(flowSession));
+ }
+
+
+ /**
+ * 创建流程记录
+ *
+ * @param workId 流程设计id
+ * @param processId 流程id
+ * @param preId 上一条流程记录id
+ * @param title 流程标题
+ * @param createOperator 流程操作者
+ * @param currentOperator 当前操作者
+ * @param snapshot 快照数据
+ * @return 流程记录
+ */
+ public FlowRecord createRecord(long workId,
+ String processId,
+ long preId,
+ String title,
+ IFlowOperator createOperator,
+ IFlowOperator currentOperator,
+ BindDataSnapshot snapshot
+ ) {
+
+ // 当前操作者存在委托人时,才需要寻找委托人
+ IFlowOperator flowOperator = currentOperator;
+ while (flowOperator.entrustOperator() != null) {
+ //寻找委托人
+ flowOperator = flowOperator.entrustOperator();
+ }
+ FlowRecord record = new FlowRecord();
+ record.setProcessId(processId);
+ record.setNodeCode(this.code);
+ record.setCreateTime(System.currentTimeMillis());
+ record.setWorkId(workId);
+ record.setFlowStatus(FlowStatus.RUNNING);
+ record.setPostponedCount(0);
+ record.setCreateOperator(createOperator);
+ record.setBindClass(snapshot.getClazzName());
+ record.setCurrentOperator(flowOperator);
+ record.setPreId(preId);
+ record.setTitle(title);
+ record.setTimeoutTime(this.loadTimeoutTime());
+ record.setFlowType(FlowType.TODO);
+ record.setErrMessage(null);
+ record.setSnapshotId(snapshot.getId());
+ return record;
+ }
+
+
+ /**
+ * 获取超时时间
+ *
+ * @return 超时时间
+ */
+ private long loadTimeoutTime() {
+ if (this.timeout > 0) {
+ return System.currentTimeMillis() + this.timeout;
+ }
+ return 0;
+ }
+
+ /**
+ * 是否有任意操作者匹配
+ */
+ public boolean isAnyOperatorMatcher() {
+ return operatorMatcher.isAny();
+ }
+
+ /**
+ * 异常匹配
+ *
+ * @param flowSession 操作内容
+ */
+ public ErrorResult errMatcher(FlowSession flowSession) {
+ if (errTrigger != null) {
+ return errTrigger.trigger(flowSession);
+ }
+ return null;
+ }
+
+ /**
+ * 是否有异常触发器
+ *
+ * @return 是否有异常触发器
+ */
+ public boolean hasErrTrigger() {
+ return errTrigger != null;
+ }
+
+ /**
+ * 生成标题
+ *
+ * @param flowSession 流程内容
+ * @return 标题
+ */
+ public String generateTitle(FlowSession flowSession) {
+ return titleGenerator.generate(flowSession);
+ }
+
+
+ /**
+ * 是否会签节点
+ */
+ public boolean isSign() {
+ return approvalType == ApprovalType.SIGN;
+ }
+
+ /**
+ * 是否非会签节点
+ */
+ public boolean isUnSign() {
+ return approvalType == ApprovalType.UN_SIGN;
+ }
+
+ /**
+ * 是否结束节点
+ */
+ public boolean isOverNode() {
+ return CODE_OVER.equals(this.code);
+ }
+
+ /**
+ * 是否开始节点
+ */
+ public boolean isStartNode() {
+ return CODE_START.equals(this.code);
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowRelation.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowRelation.java
new file mode 100644
index 00000000..c93dead9
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowRelation.java
@@ -0,0 +1,168 @@
+package com.codingapi.springboot.flow.domain;
+
+import com.codingapi.springboot.flow.content.FlowSession;
+import com.codingapi.springboot.flow.em.NodeType;
+import com.codingapi.springboot.flow.serializable.FlowRelationSerializable;
+import com.codingapi.springboot.flow.trigger.OutTrigger;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springframework.util.StringUtils;
+
+import java.util.List;
+
+/**
+ * 流程关系
+ */
+@Getter
+@AllArgsConstructor
+public class FlowRelation {
+
+ /**
+ * 关系id
+ */
+ private String id;
+
+ /**
+ * 名称
+ */
+ private String name;
+
+ /**
+ * 源节点
+ */
+ private FlowNode source;
+
+ /**
+ * 目标节点
+ */
+ private FlowNode target;
+
+ /**
+ * 排序
+ */
+ private int order;
+
+ /**
+ * 是否退回
+ */
+ private boolean back;
+
+ /**
+ * 出口触发器
+ */
+ private OutTrigger outTrigger;
+
+
+ /**
+ * 创建时间
+ */
+ private long createTime;
+
+ /**
+ * 修改时间
+ */
+ private long updateTime;
+
+
+ /**
+ * 序列化
+ *
+ * @return 序列化对象
+ */
+ public FlowRelationSerializable toSerializable() {
+ return new FlowRelationSerializable(id,
+ name,
+ source.getId(),
+ target.getId(),
+ order,
+ back,
+ outTrigger.getScript(),
+ createTime,
+ updateTime
+ );
+ }
+
+ /**
+ * 匹配节点
+ *
+ * @param nodeCode 节点编码
+ * @return 是否匹配
+ */
+ public boolean sourceMatcher(String nodeCode) {
+ return source.getCode().equals(nodeCode);
+ }
+
+
+ /**
+ * 重新排序
+ *
+ * @param order 排序
+ */
+ public void resort(int order) {
+ this.order = order;
+ }
+
+
+ public FlowRelation(String id, String name, FlowNode source, FlowNode target, OutTrigger outTrigger, int order, boolean back) {
+ this.id = id;
+ this.name = name;
+ this.source = source;
+ this.target = target;
+ this.outTrigger = outTrigger;
+ this.order = order;
+ this.back = back;
+ this.createTime = System.currentTimeMillis();
+ this.updateTime = System.currentTimeMillis();
+ }
+
+ /**
+ * 触发条件
+ *
+ * @param flowSession 流程内容
+ * @return 下一个节点
+ */
+ public FlowNode trigger(FlowSession flowSession) {
+ if (outTrigger.trigger(flowSession)) {
+ return target;
+ }
+ return null;
+ }
+
+
+ /**
+ * 验证
+ */
+ public void verify() {
+ if (!StringUtils.hasLength(id)) {
+ throw new RuntimeException("id is null");
+ }
+
+ if (source == null || target == null) {
+ throw new RuntimeException("source or target is null");
+ }
+
+ if (outTrigger == null) {
+ throw new RuntimeException("outTrigger is null");
+ }
+
+ if(source.getCode().equals(target.getCode())){
+ throw new RuntimeException("source node code is equals target node code");
+ }
+
+ if(back){
+ if(source.getType() != NodeType.APPROVAL){
+ throw new RuntimeException("source node type is not approval");
+ }
+ }
+ }
+
+ public void verifyNodes(List nodes) {
+ if (nodes.stream().noneMatch(node -> node.getId().equals(source.getId()))) {
+ throw new RuntimeException("source node is not exists");
+ }
+
+ if (nodes.stream().noneMatch(node -> node.getId().equals(target.getId()))) {
+ throw new RuntimeException("target node is not exists");
+ }
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowWork.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowWork.java
new file mode 100644
index 00000000..377aedd2
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/FlowWork.java
@@ -0,0 +1,332 @@
+package com.codingapi.springboot.flow.domain;
+
+import com.codingapi.springboot.flow.build.SchemaReader;
+import com.codingapi.springboot.flow.serializable.FlowWorkSerializable;
+import com.codingapi.springboot.flow.user.IFlowOperator;
+import com.codingapi.springboot.framework.utils.RandomGenerator;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.util.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 流程设计
+ */
+@Getter
+@AllArgsConstructor
+public class FlowWork {
+
+ /**
+ * 流程的设计id
+ */
+ @Setter
+ private long id;
+
+ /**
+ * 流程编码 (唯一值)
+ */
+ @Setter
+ private String code;
+
+ /**
+ * 流程标题
+ */
+ @Setter
+ private String title;
+ /**
+ * 流程描述
+ */
+ @Setter
+ private String description;
+ /**
+ * 流程创建者
+ */
+ private IFlowOperator createUser;
+ /**
+ * 创建时间
+ */
+ private long createTime;
+ /**
+ * 更新时间
+ * 也是流程的版本号
+ */
+ private long updateTime;
+ /**
+ * 是否启用
+ */
+ private boolean enable;
+
+ /**
+ * 最大延期次数
+ */
+ @Setter
+ private int postponedMax;
+
+ /**
+ * 流程的节点(发起节点)
+ */
+ private List nodes;
+
+ /**
+ * 流程的关系
+ */
+ private List relations;
+
+ /**
+ * 界面设计脚本
+ */
+ private String schema;
+
+ /**
+ * 构造函数
+ *
+ * @param createUser 创建者
+ */
+ public FlowWork(IFlowOperator createUser) {
+ this.createUser = createUser;
+ this.createTime = System.currentTimeMillis();
+ this.updateTime = System.currentTimeMillis();
+ this.nodes = new ArrayList<>();
+ this.relations = new ArrayList<>();
+ this.enable = false;
+ this.postponedMax = 1;
+ this.code = RandomGenerator.randomString(8);
+ }
+
+
+ /**
+ * 流程设计复制
+ * @return FlowWork 流程设计
+ */
+ public FlowWork copy(){
+ if(!StringUtils.hasLength(schema)){
+ throw new IllegalArgumentException("schema is empty");
+ }
+ String schema = this.getSchema();
+ for(FlowNode flowNode:this.getNodes()){
+ String newId = RandomGenerator.generateUUID();
+ schema = schema.replaceAll(flowNode.getId(),newId);
+ }
+
+ for(FlowRelation relation:this.getRelations()){
+ String newId = RandomGenerator.generateUUID();
+ schema = schema.replaceAll(relation.getId(),newId);
+ }
+
+ FlowWork flowWork = new FlowWork(this.createUser);
+ flowWork.setDescription(this.getDescription());
+ flowWork.setTitle(this.getTitle());
+ flowWork.setCode(RandomGenerator.randomString(8));
+ flowWork.setPostponedMax(this.getPostponedMax());
+ flowWork.schema(schema);
+ return flowWork;
+ }
+
+
+ public FlowWork(String code,String title, String description, int postponedMax, IFlowOperator createUser) {
+ this.title = title;
+ this.code = code;
+ this.description = description;
+ this.postponedMax = postponedMax;
+ this.createUser = createUser;
+ this.createTime = System.currentTimeMillis();
+ this.updateTime = System.currentTimeMillis();
+ this.nodes = new ArrayList<>();
+ this.relations = new ArrayList<>();
+ this.enable = false;
+ }
+
+
+ public void verify() {
+ if (this.nodes == null || this.nodes.isEmpty()) {
+ throw new IllegalArgumentException("nodes is empty");
+ }
+ if (this.relations == null || this.relations.isEmpty()) {
+ throw new IllegalArgumentException("relations is empty");
+ }
+ if (!StringUtils.hasLength(title)) {
+ throw new IllegalArgumentException("title is empty");
+ }
+ if (!StringUtils.hasLength(code)) {
+ throw new IllegalArgumentException("code is empty");
+ }
+
+ this.verifyNodes();
+ this.verifyRelations();
+ this.checkRelation();
+ }
+
+
+ private void checkRelation() {
+ FlowNode startNode = getNodeByCode(FlowNode.CODE_START);
+ if (startNode == null) {
+ throw new IllegalArgumentException("start node is not exist");
+ }
+ FlowNode overNode = getNodeByCode(FlowNode.CODE_OVER);
+ if (overNode == null) {
+ throw new IllegalArgumentException("over node is not exist");
+ }
+
+ List sourceCodes = new ArrayList<>();
+ List targetCodes = new ArrayList<>();
+ for (FlowRelation relation : relations) {
+ sourceCodes.add(relation.getSource().getCode());
+ targetCodes.add(relation.getTarget().getCode());
+ }
+
+ if (!sourceCodes.contains(FlowNode.CODE_START)) {
+ throw new IllegalArgumentException("start node relation is not exist");
+ }
+
+ if (!targetCodes.contains(FlowNode.CODE_OVER)) {
+ throw new IllegalArgumentException("over node relation is not exist");
+ }
+
+ }
+
+
+ private void verifyNodes() {
+ List nodeCodes = new ArrayList<>();
+
+ for (FlowNode node : nodes) {
+ node.verify();
+ if (nodeCodes.contains(node.getCode())) {
+ throw new IllegalArgumentException("node code is exist");
+ }
+ nodeCodes.add(node.getCode());
+ }
+ }
+
+
+ private void verifyRelations() {
+ for (FlowRelation relation : relations) {
+ relation.verify();
+
+ relation.verifyNodes(nodes);
+ }
+ }
+
+
+ /**
+ * 序列化
+ *
+ * @return FlowSerializable 流程序列化对象
+ */
+ public FlowWorkSerializable toSerializable() {
+ return new FlowWorkSerializable(
+ id,
+ code,
+ title,
+ description,
+ createUser.getUserId(),
+ createTime,
+ updateTime,
+ enable,
+ postponedMax,
+ nodes.stream().map(FlowNode::toSerializable).collect(Collectors.toCollection(ArrayList::new)),
+ relations.stream().map(FlowRelation::toSerializable).collect(Collectors.toCollection(ArrayList::new)));
+ }
+
+
+ /**
+ * schema解析流程设计
+ *
+ * @param schema schema
+ */
+ public void schema(String schema) {
+ SchemaReader schemaReader = new SchemaReader(schema);
+ this.relations = schemaReader.getFlowRelations();
+ this.nodes = schemaReader.getFlowNodes();
+ this.schema = schema;
+ this.verify();
+ this.updateTime = System.currentTimeMillis();
+ }
+
+ /**
+ * 添加节点
+ *
+ * @param node 节点
+ */
+ public void addNode(FlowNode node) {
+ List codes = nodes.stream().map(FlowNode::getCode).collect(Collectors.toList());
+ if (codes.contains(node.getCode())) {
+ throw new IllegalArgumentException("node code is exist");
+ }
+ nodes.add(node);
+ this.updateTime = System.currentTimeMillis();
+ }
+
+ /**
+ * 添加关系
+ *
+ * @param relation 关系
+ */
+ public void addRelation(FlowRelation relation) {
+ relations.add(relation);
+ this.updateTime = System.currentTimeMillis();
+ }
+
+
+ /**
+ * 获取节点
+ *
+ * @param code 节点编码
+ * @return 节点
+ */
+ public FlowNode getNodeByCode(String code) {
+ for (FlowNode node : nodes) {
+ if (node.getCode().equals(code)) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * 获取开始节点
+ *
+ * @return 开始节点
+ */
+ public FlowNode getStartNode() {
+ return getNodeByCode(FlowNode.CODE_START);
+ }
+
+
+ /**
+ * 是否存在退回关系
+ */
+ public boolean hasBackRelation() {
+ return relations.stream().anyMatch(FlowRelation::isBack);
+ }
+
+
+ /**
+ * 启用检测
+ */
+ public void enableValidate() {
+ if (!this.isEnable()) {
+ throw new IllegalArgumentException("flow work is disable");
+ }
+ }
+
+
+ /**
+ * 启用
+ */
+ public void enable() {
+ this.verify();
+ this.enable = true;
+ }
+
+ /**
+ * 禁用
+ */
+ public void disbale() {
+ this.enable = false;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/Opinion.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/Opinion.java
new file mode 100644
index 00000000..3f3321ae
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/domain/Opinion.java
@@ -0,0 +1,78 @@
+package com.codingapi.springboot.flow.domain;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * 审批意见
+ */
+@Getter
+@Setter
+@ToString
+@NoArgsConstructor
+public class Opinion {
+
+ // 默认审批(人工审批)
+ public static final int TYPE_DEFAULT = 0;
+ // 系统自动审批
+ public static final int TYPE_AUTO = 1;
+
+
+ // 审批结果 暂存
+ public static final int RESULT_SAVE = 0;
+ // 审批结果 转办
+ public static final int RESULT_TRANSFER = 1;
+ // 审批结果 通过
+ public static final int RESULT_PASS = 2;
+ // 审批结果 驳回
+ public static final int RESULT_REJECT = 3;
+
+ /**
+ * 审批意见
+ */
+ private String advice;
+ /**
+ * 审批结果
+ */
+ private int result;
+ /**
+ * 意见类型
+ */
+ private int type;
+
+ public Opinion(String advice, int result, int type) {
+ this.advice = advice;
+ this.result = result;
+ this.type = type;
+ }
+
+ public static Opinion save(String advice) {
+ return new Opinion(advice, RESULT_SAVE, TYPE_DEFAULT);
+ }
+
+ public static Opinion pass(String advice) {
+ return new Opinion(advice, RESULT_PASS, TYPE_DEFAULT);
+ }
+
+ public static Opinion reject(String advice) {
+ return new Opinion(advice, RESULT_REJECT, TYPE_DEFAULT);
+ }
+
+ public static Opinion transfer(String advice) {
+ return new Opinion(advice, RESULT_TRANSFER, TYPE_DEFAULT);
+ }
+
+ public static Opinion unSignAutoSuccess() {
+ return new Opinion("非会签自动审批", RESULT_PASS, TYPE_AUTO);
+ }
+
+ public boolean isSuccess() {
+ return result == RESULT_PASS;
+ }
+
+ public boolean isReject() {
+ return result == RESULT_REJECT;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/ApprovalType.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/ApprovalType.java
new file mode 100644
index 00000000..60747587
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/ApprovalType.java
@@ -0,0 +1,26 @@
+package com.codingapi.springboot.flow.em;
+
+/**
+ * 审批类型:会签与非会签
+ */
+public enum ApprovalType {
+
+ /**
+ * 会签
+ */
+ SIGN,
+ /**
+ * 非会签
+ */
+ UN_SIGN;
+
+
+ public static ApprovalType parser(String approvalType) {
+ for(ApprovalType type:values()){
+ if(type.name().equalsIgnoreCase(approvalType)){
+ return type;
+ }
+ }
+ return UN_SIGN;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowSourceDirection.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowSourceDirection.java
new file mode 100644
index 00000000..deb82606
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowSourceDirection.java
@@ -0,0 +1,30 @@
+package com.codingapi.springboot.flow.em;
+
+/**
+ * 流程来源的方式
+ * 包括 同意、拒绝、转办
+ */
+public enum FlowSourceDirection {
+
+ /**
+ * 同意
+ */
+ PASS,
+ /**
+ * 拒绝
+ */
+ REJECT,
+ /**
+ *
+ */
+ TRANSFER;
+
+ public static FlowSourceDirection parser(String type){
+ for(FlowSourceDirection flowSourceDirection :values()){
+ if(flowSourceDirection.name().equalsIgnoreCase(type)){
+ return flowSourceDirection;
+ }
+ }
+ return null;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowStatus.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowStatus.java
new file mode 100644
index 00000000..7c4bceec
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowStatus.java
@@ -0,0 +1,27 @@
+package com.codingapi.springboot.flow.em;
+
+/**
+ * 流程状态
+ * 进行中、已完成
+ */
+public enum FlowStatus {
+
+ /**
+ * 进行中
+ */
+ RUNNING,
+ /**
+ * 已完成
+ */
+ FINISH;
+
+
+ public static FlowStatus parser(String status) {
+ for (FlowStatus flowStatus : FlowStatus.values()) {
+ if (flowStatus.name().equalsIgnoreCase(status)) {
+ return flowStatus;
+ }
+ }
+ return RUNNING;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowType.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowType.java
new file mode 100644
index 00000000..abee3ff0
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/FlowType.java
@@ -0,0 +1,30 @@
+package com.codingapi.springboot.flow.em;
+
+/**
+ * 流程的类型
+ */
+public enum FlowType {
+
+ /**
+ * 待办
+ */
+ TODO,
+ /**
+ * 已办
+ */
+ DONE,
+ /**
+ * 转办
+ */
+ TRANSFER;
+
+
+ public static FlowType parser(String type){
+ for(FlowType flowType :values()){
+ if(flowType.name().equalsIgnoreCase(type)){
+ return flowType;
+ }
+ }
+ return TODO;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/NodeType.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/NodeType.java
new file mode 100644
index 00000000..ac87e905
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/em/NodeType.java
@@ -0,0 +1,30 @@
+package com.codingapi.springboot.flow.em;
+
+/**
+ * 节点类型
+ */
+public enum NodeType {
+
+ /**
+ * 发起
+ */
+ START,
+ /**
+ * 审批
+ */
+ APPROVAL,
+ /**
+ * 结束
+ */
+ OVER;
+
+
+ public static NodeType parser(String type) {
+ for (NodeType nodeType : values()) {
+ if (nodeType.name().equalsIgnoreCase(type)) {
+ return nodeType;
+ }
+ }
+ return APPROVAL;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrTrigger.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrTrigger.java
new file mode 100644
index 00000000..fbdd6751
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrTrigger.java
@@ -0,0 +1,36 @@
+package com.codingapi.springboot.flow.error;
+
+import com.codingapi.springboot.flow.content.FlowSession;
+import com.codingapi.springboot.flow.script.GroovyShellContext;
+import lombok.Getter;
+import org.springframework.util.StringUtils;
+
+/**
+ * 错误触发器
+ */
+public class ErrTrigger {
+
+ @Getter
+ private final String script;
+
+ private final GroovyShellContext.ShellScript runtime;
+
+
+ public ErrTrigger(String script) {
+ if (!StringUtils.hasLength(script)) {
+ throw new IllegalArgumentException("script is empty");
+ }
+ this.script = script;
+ this.runtime = GroovyShellContext.getInstance().parse(script);
+ }
+
+ /**
+ * 触发
+ *
+ * @param flowSession 流程内容
+ */
+ public ErrorResult trigger(FlowSession flowSession) {
+ return (ErrorResult) runtime.invokeMethod("run", flowSession);
+ }
+
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrorResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrorResult.java
new file mode 100644
index 00000000..96d8b272
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/ErrorResult.java
@@ -0,0 +1,17 @@
+package com.codingapi.springboot.flow.error;
+
+
+/**
+ * 异常匹配的结果对象
+ */
+public abstract class ErrorResult {
+
+ public boolean isNode(){
+ return this instanceof NodeResult;
+ }
+
+ public boolean isOperator(){
+ return this instanceof OperatorResult;
+ }
+
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/NodeResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/NodeResult.java
new file mode 100644
index 00000000..ebcf5fd0
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/NodeResult.java
@@ -0,0 +1,16 @@
+package com.codingapi.springboot.flow.error;
+
+import lombok.Getter;
+
+/**
+ * 节点的异常匹配对象
+ */
+@Getter
+public class NodeResult extends ErrorResult{
+
+ private final String node;
+
+ public NodeResult(String node) {
+ this.node = node;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/OperatorResult.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/OperatorResult.java
new file mode 100644
index 00000000..9467626a
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/error/OperatorResult.java
@@ -0,0 +1,25 @@
+package com.codingapi.springboot.flow.error;
+
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 操作人员的异常匹配对象
+ */
+@Getter
+public class OperatorResult extends ErrorResult {
+
+ private final List operatorIds;
+
+ public OperatorResult(List operatorIds) {
+ this.operatorIds = operatorIds;
+ }
+
+ public OperatorResult(long... operatorIds) {
+ this.operatorIds = new ArrayList<>();
+ Arrays.stream(operatorIds).forEach(this.operatorIds::add);
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java
new file mode 100644
index 00000000..f005767e
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/event/FlowApprovalEvent.java
@@ -0,0 +1,88 @@
+package com.codingapi.springboot.flow.event;
+
+import com.codingapi.springboot.flow.bind.IBindData;
+import com.codingapi.springboot.flow.domain.FlowWork;
+import com.codingapi.springboot.flow.record.FlowRecord;
+import com.codingapi.springboot.flow.user.IFlowOperator;
+import com.codingapi.springboot.framework.event.ISyncEvent;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 流程审批事件
+ */
+@Slf4j
+@Getter
+@ToString
+public class FlowApprovalEvent implements ISyncEvent {
+ // 创建流程
+ public static final int STATE_CREATE = 1;
+ // 流程审批通过
+ public static final int STATE_PASS = 2;
+ // 流程审批拒绝
+ public static final int STATE_REJECT = 3;
+ // 流程转办
+ public static final int STATE_TRANSFER = 4;
+ // 流程撤销
+ public static final int STATE_RECALL = 5;
+ // 流程完成
+ public static final int STATE_FINISH = 6;
+ // 创建待办
+ public static final int STATE_TODO = 7;
+ // 催办
+ public static final int STATE_URGE = 8;
+
+
+ private final int state;
+ private final IFlowOperator operator;
+ private final FlowRecord flowRecord;
+ private final FlowWork flowWork;
+ private final IBindData bindData;
+
+ public FlowApprovalEvent(int state, FlowRecord flowRecord, IFlowOperator operator, FlowWork flowWork, IBindData bindData) {
+ this.state = state;
+ this.operator = operator;
+ this.flowRecord = flowRecord;
+ this.flowWork = flowWork;
+ this.bindData = bindData;
+ log.debug("FlowApprovalEvent:{}", this);
+ }
+
+
+ public boolean match(Class> bindDataClass) {
+ return bindDataClass.isInstance(bindData);
+ }
+
+ public boolean isUrge() {
+ return state == STATE_URGE;
+ }
+
+ public boolean isTodo() {
+ return state == STATE_TODO;
+ }
+
+ public boolean isCreate() {
+ return state == STATE_CREATE;
+ }
+
+ public boolean isPass() {
+ return state == STATE_PASS;
+ }
+
+ public boolean isReject() {
+ return state == STATE_REJECT;
+ }
+
+ public boolean isTransfer() {
+ return state == STATE_TRANSFER;
+ }
+
+ public boolean isRecall() {
+ return state == STATE_RECALL;
+ }
+
+ public boolean isFinish() {
+ return state == STATE_FINISH;
+ }
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/generator/TitleGenerator.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/generator/TitleGenerator.java
new file mode 100644
index 00000000..2fa5f840
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/generator/TitleGenerator.java
@@ -0,0 +1,47 @@
+package com.codingapi.springboot.flow.generator;
+
+import com.codingapi.springboot.flow.content.FlowSession;
+import com.codingapi.springboot.flow.script.GroovyShellContext;
+import lombok.Getter;
+import org.springframework.util.StringUtils;
+
+/**
+ * 标题生成器
+ */
+public class TitleGenerator {
+
+ @Getter
+ private final String script;
+
+ private final GroovyShellContext.ShellScript runtime;
+
+ public TitleGenerator(String script) {
+ if (!StringUtils.hasLength(script)) {
+ throw new IllegalArgumentException("script is empty");
+ }
+ this.script = script;
+ this.runtime = GroovyShellContext.getInstance().parse(script);
+ }
+
+
+ /**
+ * 默认标题生成器
+ *
+ * @return 标题生成器
+ */
+ public static TitleGenerator defaultTitleGenerator() {
+ return new TitleGenerator("def run(content){ return content.getCurrentOperator().getName() + '-' + content.getFlowWork().getTitle() + '-' + content.getFlowNode().getName();}");
+ }
+
+
+ /**
+ * 生成标题
+ *
+ * @param flowSession 流程内容
+ * @return 标题
+ */
+ public String generate(FlowSession flowSession) {
+ return (String) this.runtime.invokeMethod("run", flowSession);
+ }
+
+}
diff --git a/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/matcher/OperatorMatcher.java b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/matcher/OperatorMatcher.java
new file mode 100644
index 00000000..ae252f0b
--- /dev/null
+++ b/springboot-starter-flow/src/main/java/com/codingapi/springboot/flow/matcher/OperatorMatcher.java
@@ -0,0 +1,115 @@
+package com.codingapi.springboot.flow.matcher;
+
+import com.codingapi.springboot.flow.content.FlowSession;
+import com.codingapi.springboot.flow.script.GroovyShellContext;
+import lombok.Getter;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 操作者匹配器
+ */
+public class OperatorMatcher {
+
+ @Getter
+ private final String script;
+
+ private final int state;
+
+ private final GroovyShellContext.ShellScript runtime;
+
+ // 指定用户
+ public static final int STATE_SPECIFY = 1;
+ // 创建者
+ public static final int STATE_CREATOR = 2;
+ // 任意人
+ public static final int STATE_ANY = 3;
+
+
+ public boolean isAny() {
+ return state == STATE_ANY;
+ }
+
+ public boolean isCreator() {
+ return state == STATE_CREATOR;
+ }
+
+ public boolean isSpecify() {
+ return state == STATE_SPECIFY;
+ }
+
+ private static int parseState(String script) {
+ if (script.contains("content.getCurrentOperator().getUserId()")) {
+ return STATE_ANY;
+ } else if (script.contains("content.getCreateOperator().getUserId()")) {
+ return STATE_CREATOR;
+ } else {
+ return STATE_SPECIFY;
+ }
+ }
+
+
+ public OperatorMatcher(String script) {
+ this(script, parseState(script));
+ }
+
+ public OperatorMatcher(String script, int state) {
+ if (!StringUtils.hasLength(script)) {
+ throw new IllegalArgumentException("script is empty");
+ }
+ this.script = script;
+ this.state = state;
+ this.runtime = GroovyShellContext.getInstance().parse(script);
+ }
+
+ /**
+ * 默认操作者匹配器
+ *
+ * @return 操作者匹配器
+ */
+ public static OperatorMatcher anyOperatorMatcher() {
+ return new OperatorMatcher("def run(content) {return [content.getCurrentOperator().getUserId()];}", STATE_ANY);
+ }
+
+ /**
+ * 指定操作者匹配器
+ *
+ * @param userIds 用户ids
+ * @return 操作者匹配器
+ */
+ public static OperatorMatcher specifyOperatorMatcher(long... userIds) {
+ String userIdsStr = Arrays.stream(userIds).mapToObj(String::valueOf).collect(Collectors.joining(","));
+ return new OperatorMatcher("def run(content) {return [" + userIdsStr + "];}", STATE_SPECIFY);
+ }
+
+ /**
+ * 创建者操作者匹配器
+ *
+ * @param userIds 用户ids
+ * @return 操作者匹配器
+ */
+ public static OperatorMatcher creatorOperatorMatcher() {
+ return new OperatorMatcher("def run(content) {return [content.getCreateOperator().getUserId()];}", STATE_CREATOR);
+ }
+
+ /**
+ * 匹配操作者
+ *
+ * @param flowSession 流程内容
+ * @return 是否匹配
+ */
+ public List matcher(FlowSession flowSession) {
+ List