add 新增 warmflow 源码级定制化开发

This commit is contained in:
疯狂的狮子Li 2026-09-11 16:19:24 +08:00
parent 4d1448b4b4
commit ad017ca4da
158 changed files with 13409 additions and 414 deletions

14
pom.xml
View File

@ -62,7 +62,6 @@
<!-- SMS 配置 -->
<sms4j.version>3.3.5</sms4j.version>
<!-- 工作流配置 -->
<warm-flow.version>1.8.9</warm-flow.version>
<liteflow.version>2.16.1.2</liteflow.version>
<!-- mqtt客户端 -->
<mica-mqtt.version>2.6.8</mica-mqtt.version>
@ -346,19 +345,6 @@
<version>${mapstruct-plus.version}</version>
</dependency>
<!-- Warm-Flow国产工作流引擎, 在线文档http://warm-flow.cn/ -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb4-starter</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<!-- Warm-Flow UI 插件 -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
<version>${warm-flow.version}</version>
</dependency>
<!-- LiteFlow 规则编排引擎 -->
<dependency>
<groupId>com.yomahub</groupId>

View File

@ -69,4 +69,18 @@ public final class SpringUtils extends SpringUtil {
return Threading.VIRTUAL.isActive(getBean(Environment.class));
}
/**
* 获取Bean 容器中不存在时返回null而非抛出异常
*
* @param clazz Bean类型
* @return Bean实例 不存在时返回null
*/
public static <T> T getBeanOrNull(Class<T> clazz) {
try {
return getBean(clazz);
} catch (NoSuchBeanDefinitionException e) {
return null;
}
}
}

View File

@ -84,18 +84,6 @@
<artifactId>ruoyi-api</artifactId>
</dependency>
<!-- Warm-Flow 工作流引擎 -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-mybatis-plus-sb4-starter</artifactId>
</dependency>
<!-- Warm-Flow UI 插件 -->
<dependency>
<groupId>org.dromara.warm</groupId>
<artifactId>warm-flow-plugin-ui-sb-web</artifactId>
</dependency>
<!-- LiteFlow 规则编排 -->
<dependency>
<groupId>org.dromara</groupId>

View File

@ -0,0 +1,152 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow;
import cn.hutool.core.util.StrUtil;
import org.dromara.warm.flow.config.WarmFlowProperties;
import org.dromara.warm.flow.handler.DataFillHandler;
import org.dromara.warm.flow.handler.PermissionHandler;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.warm.flow.listener.GlobalListener;
import org.dromara.warm.flow.service.*;
import cn.hutool.core.util.ClassUtil;
import java.lang.reflect.Constructor;
import java.util.function.Supplier;
/**
* 流程引擎通过静态方法驱动流程流转
*/
public class FlowEngine {
private static WarmFlowProperties flowConfig;
private static DataFillHandler dataFillHandler;
private static PermissionHandler permissionHandler;
private static GlobalListener globalListener;
public static DefService defService() {
return SpringUtils.getBeanOrNull(DefService.class);
}
public static NodeService nodeService() {
return SpringUtils.getBeanOrNull(NodeService.class);
}
public static SkipService skipService() {
return SpringUtils.getBeanOrNull(SkipService.class);
}
public static InsService insService() {
return SpringUtils.getBeanOrNull(InsService.class);
}
public static TaskService taskService() {
return SpringUtils.getBeanOrNull(TaskService.class);
}
public static HisTaskService hisTaskService() {
return SpringUtils.getBeanOrNull(HisTaskService.class);
}
public static UserService userService() {
return SpringUtils.getBeanOrNull(UserService.class);
}
public static ChartService chartService() {
return SpringUtils.getBeanOrNull(ChartService.class);
}
public static WarmFlowProperties getFlowConfig() {
return flowConfig;
}
public static void setFlowConfig(WarmFlowProperties flowConfig) {
FlowEngine.flowConfig = flowConfig;
}
public static void initDataFillHandler(String handlerPath) {
dataFillHandler = initBean(DataFillHandler.class, handlerPath, () -> new DataFillHandler() {});
}
public static void initPermissionHandler(String handlerPath) {
permissionHandler = initBean(PermissionHandler.class, handlerPath, null);
}
public static void initGlobalListener(String handlerPath) {
globalListener = initBean(GlobalListener.class, handlerPath, null);
}
/**
* 获取填充类
*/
public static DataFillHandler dataFillHandler() {
return dataFillHandler;
}
/**
* 获取填充类
*/
public static PermissionHandler permissionHandler() {
return permissionHandler;
}
/**
* 获取全局监听器
*/
public static GlobalListener globalListener() {
return globalListener;
}
/**
* 获取数据库类型
*/
public static String dataSourceType() {
return flowConfig.getDataSourceType();
}
/**
* 初始化bean先从yml配置获取bean的全包名路径否则从spring容器获取bean如果都没有则通过supplier获取bean
*
* @param tClazz bean的class类型
* @param beanPath bean全包名路径
* @param supplier 获取bean的lambda
* @param <T> bean类型
* @return bean
*/
private static <T> T initBean(Class<T> tClazz, String beanPath, Supplier<T> supplier) {
T hander = null;
try {
if (!StrUtil.isEmpty(beanPath)) {
Class<?> clazz = ClassUtil.loadClass(beanPath);
if (clazz != null && tClazz.isAssignableFrom(clazz)) {
Constructor<?> constructor = clazz.getConstructor();
hander = tClazz.cast(constructor.newInstance());
}
}
} catch (Exception ignored) {
}
if (hander == null) {
hander = SpringUtils.getBeanOrNull(tClazz);
}
if (hander == null && supplier != null) {
hander = supplier.get();
}
return hander;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.config;
import jakarta.annotation.PostConstruct;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.enums.FrameworkType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 工作流引擎初始化各服务由注解扫描注册 flow.service 包的 @Service
*
* @author warm
* @since 2023/6/5 23:01
*/
@Component
@EnableConfigurationProperties(WarmFlowProperties.class)
@ConditionalOnProperty(value = "warm-flow.enabled", havingValue = "true", matchIfMissing = true)
public class WarmFlowInitializer {
private static final Logger log = LoggerFactory.getLogger(WarmFlowInitializer.class);
private final WarmFlowProperties warmFlow;
public WarmFlowInitializer(WarmFlowProperties warmFlow) {
this.warmFlow = warmFlow;
}
@PostConstruct
public void init() {
warmFlow.init();
warmFlow.setFramework(FrameworkType.SPRING_BOOT);
FlowEngine.setFlowConfig(warmFlow);
log.info("【warm-flow】加载完成");
}
}

View File

@ -0,0 +1,136 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.config;
import lombok.Getter;
import lombok.Setter;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.enums.ChartStatus;
import org.dromara.warm.flow.enums.FrameworkType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
import java.util.List;
/**
* WarmFlow属性配置文件warm-flow 前缀绑定
*
* @author warm
*/
@Getter
@Setter
@ConfigurationProperties("warm-flow")
public class WarmFlowProperties implements Serializable {
/**
* 开关
*/
private boolean enabled = true;
/**
* 框架类型: springbootsolon
*/
private FrameworkType framework;
/**
* 启动banner
*/
private boolean banner = true;
/**
* 数据填充处理类路径
*/
private String dataFillHandlerPath;
/**
* 办理人权限处理器类路径
*/
private String permissionHandlerPath;
/**
* 全局监听器类路径
*/
private String globalListenerPath;
/**
* 数据源类型, mybatis模块对orm进一步的封装, 由于各数据库分页语句存在差异,
* 当配置此参数时, 以此参数结果为基准, 未配置时, 取DataSource中数据源类型,
* 兜底为mysql数据库
*/
private String dataSourceType;
/**
* ui开关
*/
private boolean ui = true;
/**
* 如果需要工作流共享业务系统权限默认Authorization如果有多个token用逗号分隔
*/
private String tokenName = "Authorization";
/**
* 公共模型流程状态对应的三原色
*/
private List<String> chartStatusColor;
/**
* 经典模式流程状态对应的三原色
*/
private List<String> chartStatusColorClassics;
/**
* 仿钉钉模式流程状态对应的三原色
*/
private List<String> chartStatusColorMimic;
/**
* 是否显示流程图顶部文字
*/
private boolean topTextShow = true;
public void init() {;
// 设置数据填充处理类
FlowEngine.initDataFillHandler(this.getDataFillHandlerPath());
// 设置办理人权限处理类
FlowEngine.initPermissionHandler(this.getPermissionHandlerPath());
// 设置全局监听器
FlowEngine.initGlobalListener(this.getGlobalListenerPath());
// 打印banner图
printBanner();
// 初始化流程状态对应的自定义三原色
ChartStatus.initCustomColor(this.getChartStatusColor(), this.getChartStatusColorClassics(), this.getChartStatusColorMimic());
}
private void printBanner() {
if (this.isBanner()) {
System.out.println("\n" +
" ▄ ▄ ▄▄▄▄▄▄ ▄ \n" +
" █ █ █ ▄▄▄ ▄ ▄▄ ▄▄▄▄▄ █ █ ▄▄▄ ▄ ▄ \n" +
" ▀ █▀█ █ ▀ █ █▀ ▀ █ █ █ ▄▄▄▄▄ █▄▄▄▄▄ █ █▀ ▀█ ▀▄ ▄ ▄▀ \n" +
" ██ ██▀ ▄▀▀▀█ █ █ █ █ █ █ █ █ █▄█▄█ \n" +
" █ █ ▀▄▄▀█ █ █ █ █ █ █▄▄ ▀█▄█▀ █ █ \n" +
"\n" +
"\033[32m :: Warm-Flow :: (v" + WarmFlowProperties.class.getPackage()
.getImplementationVersion() + ")\033[0m\n");
}
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.constant;
/**
* 工作流中用到的一些常量
*
* @author warm
* @since 2023/3/30 14:05
*/
public class ExceptionCons {
public static final String SAME_CONDITION_VALUE = "中间节点,同一个节点不能有相同跳转类型,跳转同一个目标节点!";
public static final String SAME_CONDITION_NODE = "互斥网关,同一个节点不能有相同跳转条件,跳转同一个目标节点!";
public static final String SAME_DEST_NODE = "并行网关,同一个节点不能跳转同一个目标节点!";
public static final String MUL_START_NODE = "开始节点不能超过1个!";
public static final String MUL_SKIP_BETWEEN = "不可同时通过或者退回到多个中间节点,必须先流转到网关节点!";
public static final String MUL_START_SKIP = "节点流转条件不能超过1个!";
public static final String LOST_START_NODE = "流程缺少开始节点!";
public static final String LOST_NODE_CODE = "节点编码缺失";
public static final String MUST_SKIP = "开始或者中间节点必须画跳转线";
public static final String SAME_NODE_CODE = "同一流程中节点编码重复!";
public static final String NULL_DEST_NODE = "无法他跳转,未配置目标节点!";
public static final String NULL_SKIP_TYPE = "未找到跳转类型匹配的目标节点!";
public static final String NULL_CONDITION_VALUE_NODE = "未找到跳转条件,不支持跳转!";
public static final String NULL_CONDITION_VALUE = "跳转条件不能为空!";
public static final String FIRST_FORBID_BACK = "禁止退回到第一个节点";
public static final String NULL_ROLE_NODE = "无法跳转到该节点,请检查当前用户是否有权限!";
public static final String LOST_DEST_NODE = "目标节点为空!";
public static final String LOST_CUR_NODE = "当前流程节点丢失!";
public static final String NULL_NODE_CODE = "目标节点编码不存在!";
public static final String NULL_BUSINESS_ID = "业务id为空!";
public static final String NULL_FLOW_CODE = "流程编码缺失!";
public static final String NOT_FOUNT_DEF = "流程定义不存在!";
public static final String NOT_FOUNT_INSTANCE = "流程实例获取失败!";
public static final String NULL_INSTANCE_ID = "流程实例id不能为空!";
public static final String NULL_TASK_ID = "任务id不能为空!";
public static final String NOT_FOUNT_TASK = "未找到待办任务!";
public static final String TASK_NOT_ONE = "此接口不能同时跳转多个待办任务,请更换!";
public static final String NOT_DEFINITION_ID = "流程定义id不能为空!";
public static final String NOT_NODE_DATA = "流程节点数据缺失!";
public static final String EXIST_START_TASK = "流程定义已开启过审批任务,不可操作!";
public static final String FLOW_FINISH = "流程已完成!";
public static final String NOT_AUTHORITY = "请检查当前用户是否有权限!";
public static final String SIGN_NULL_HANDLER = "会签票签时,办理人标识不能为空";
public static final String REDUCTION_SIGN_ONE_ERROR = "办理人不足或者只有一人,不可减签";
public static final String IS_ALREADY_SIGN = "已经是待办人,不可加签";
public static final String IS_ALREADY_TRANSFER = "已经是转办人,不可转办";
public static final String IS_ALREADY_DEPUTE = "已经是受托人,不可委托";
public static final String NOT_ACTIVITY = "当前流程定义或者实例已经挂起,请先激活";
public static final String NOT_DEFINITION_ACTIVITY = "当前流程定义已挂起,不可开启新的流程";
public static final String DEFINITION_ALREADY_ACTIVITY = "当前流程定义已经激活";
public static final String DEFINITION_ALREADY_SUSPENDED = "当前流程定义已经挂起";
public static final String INSTANCE_ALREADY_ACTIVITY = "当前流程实例已经激活";
public static final String INSTANCE_ALREADY_SUSPENDED = "当前流程实例已经挂起";
public static final String FORM_ALREADY_PUBLISH = "当前表单状态已发布";
public static final String FORM_ALREADY_UN_PUBLISH = "当前表单状态未发布";
public static final String FORM_NOT_ONE = "表单数据错误, 请联系管理员排查!";
public static final String ID_EMPTY = "ID不能为空";
public static final String NOT_DEF_PROMOTER_NOT_CANCEL = "不是当前流程的发起人,无法撤销";
public static final String HANDLER_NOT_EMPTY = "办理人不能为空";
public static final String TAR_NOT_GATEWAY = "目标节点不能是网关节点!";
public static final String NOT_FOUND_FLOW_TASK = "未获取到流程任务";
public static final String FLOW_HAVE_USELESS_SKIP = "存在无用的跳转";
public static final String NULL_TRANSFER_HANDLER = "转办对象不能为空";
public static final String NULL_DEPUTE_HANDLER = "委托对象不能为空";
public static final String NULL_ADD_SIGNATURE_HANDLER = "加签对象不能为空";
public static final String NULL_REDUCTION_SIGNATURE_HANDLER = "减签对象不能为空";
public static final String READ_IS_ERROR = "读取is流失败";
public static final String EXIST_USE_FORM = "流程表单已使用,不可操作!";
public static final String NOT_FOUNT_LAST_TASK = "未找到前置任务!";
public static final String NOT_FOUNT_HANDLED_TASK = "未找到您已办理过的任务!";
public static final String NOT_FOUNT_HANDLED_TASK_HANDLER = "拿回的任务未办理!";
public static final String START_NODE_NOT_ALLOW_JUMP = "开始节点不允许跳转!";
public static final String NOT_DRAW_FLOW_ERROR = "未绘制流程图,不可发布";
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.constant;
import java.util.regex.Pattern;
/**
* warm-flow常量
*
* @author warm
*/
public class FlowCons {
/**
* 分隔符
*/
public static final String SPLIT_AT = "@@";
public static final String SPLIT_VERTICAL = "\\|";
public static final String DEFAULT = "default";
public static final String SPEL = "spel";
public static final String SNEL = "snel";
public static final Pattern LISTENER_PATTERN = Pattern.compile("^([^()]*)(.*)$");
/**
* 权限标识中的发起人标识符办理过程中进行替换
*/
public static final String WARMFLOWINITIATOR = "warmFlowInitiator";
/**
* 监听器参数
*/
public static final String WARM_LISTENER_PARAM = "WarmListenerParam";
/**
* 表单自定义状态
* 内置表单
*/
public static final String FORM_CUSTOM_Y = "Y";
/**
* 外挂表单路径
*/
public static final String FORM_CUSTOM_N = "N";
/**
* 表单数据
*/
public static final String FORM_DATA = "formData";
public static final String PREVIOUS = "previous";
public static final String SUFFIX = "suffix";
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* 响应信息主体
*
* @author ruoyi
*/
@Setter
@Getter
public class ApiResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功
*/
public static final int SUCCESS = 200;
/**
* 失败
*/
public static final int FAIL = 500;
private int code;
private String msg;
private T data;
public static <T> ApiResult<T> ok() {
return restResult(null, SUCCESS, "操作成功");
}
public static <T> ApiResult<T> ok(T data) {
return restResult(data, SUCCESS, "操作成功");
}
public static <T> ApiResult<T> ok(T data, String msg) {
return restResult(data, SUCCESS, msg);
}
public static <T> ApiResult<T> fail() {
return restResult(null, FAIL, "操作失败");
}
public static <T> ApiResult<T> fail(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> ApiResult<T> fail(T data) {
return restResult(data, FAIL, "操作失败");
}
public static <T> ApiResult<T> fail(T data, String msg) {
return restResult(data, FAIL, msg);
}
public static <T> ApiResult<T> fail(int code, String msg) {
return restResult(null, code, msg);
}
private static <T> ApiResult<T> restResult(T data, int code, String msg) {
ApiResult<T> apiResult = new ApiResult<>();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
return apiResult;
}
public static <T> Boolean isError(ApiResult<T> ret) {
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(ApiResult<T> ret) {
return ApiResult.SUCCESS == ret.getCode();
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
/**
* 流程图所需数据集合
*
* @author warm
* @since 2023/3/30 14:27
*/
@Getter
@Setter
public class DefChart {
/**
* 流程图所需的流程定义
*/
private DefJson defJson = new DefJson();
/**
* 流程图所需的流程节点
*/
private List<NodeJson> nodeJsonList = new ArrayList<>();
/**
* 流程图所需的流程跳转
*/
private List<SkipJson> skipJsonList = new ArrayList<>();
}

View File

@ -0,0 +1,292 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import org.dromara.warm.flow.entity.*;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.entity.FlowDefinition;
import org.dromara.warm.flow.entity.FlowInstance;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowSkip;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
* 流程定义json对象
*
* @author warm
* @since 2023-03-29
*/
@Setter
@Getter
@Accessors(chain = true)
@ToString
public class DefJson {
/**
* 主键
*/
private Long id;
/**
* 流程编码
*/
private String flowCode;
/**
* 流程名称
*/
private String flowName;
/**
* 设计器模型CLASSICS经典模型 MIMIC仿钉钉模型
*/
private String modelValue;
/**
* 流程类别
*/
private String category;
/**
* 流程版本
*/
private String version;
/**
* 是否发布0未开启 1开启
*/
private Integer isPublish;
/**
* 审批表单是否自定义Y= N=
*/
private String formCustom;
/**
* 审批表单是否自定义Y= N=
*/
private String formPath;
/**
* 监听器类型
*/
private String listenerType;
/**
* 监听器路径
*/
private String listenerPath;
/**
* 实例对象
*/
private FlowInstance instance;
/**
* 扩展字段预留给业务系统使用
*/
private String ext;
/**
* 扩展map保存业务自定义扩展属性
*/
private Map<String, Object> extMap;
/**
* 所有节点结合
*/
private List<NodeJson> nodeList = new ArrayList<>();
/**
* 流程状态对应的三原色
*/
private List<String> chartStatusColor;
/**
* 顶部信息: 比如流程名称等
*/
private String topText;
/**
* 顶部信息: 流程名称是否显示
*/
private boolean topTextShow;
private String createBy;
private String updateBy;
/**
* 流程类别
*/
private List<Tree> categoryList;
/**
* 自定义表单的唯一标识如formCode+version
*/
private List<Tree> formPathList;
public String getModelValue() {
if (StrUtil.isEmpty(modelValue)) {
modelValue = "CLASSICS";
}
return modelValue;
}
public static DefJson copyDef(FlowDefinition definition) {
DefJson defJson = new DefJson()
.setFlowCode(definition.getFlowCode())
.setFlowName(definition.getFlowName())
.setModelValue(definition.getModelValue())
.setVersion(definition.getVersion())
.setIsPublish(definition.getIsPublish())
.setCategory(definition.getCategory())
.setFormCustom(definition.getFormCustom())
.setFormPath(definition.getFormPath())
.setListenerType(definition.getListenerType())
.setListenerPath(definition.getListenerPath())
.setExt(definition.getExt())
.setCreateBy(definition.getCreateBy())
.setUpdateBy(definition.getUpdateBy());
List<NodeJson> nodeList = new ArrayList<>();
defJson.setNodeList(nodeList);
for (FlowNode node : definition.getNodeList()) {
// 向节点中添加子节点
NodeJson nodeJson = new NodeJson()
.setNodeType(node.getNodeType())
.setNodeCode(node.getNodeCode())
.setNodeName(node.getNodeName())
.setPermissionFlag(node.getPermissionFlag())
.setNodeRatio(node.getNodeRatio())
.setCoordinate(node.getCoordinate())
.setAnyNodeSkip(node.getAnyNodeSkip())
.setListenerType(node.getListenerType())
.setListenerPath(node.getListenerPath())
.setFormCustom(node.getFormCustom())
.setFormPath(node.getFormPath())
.setExt(node.getExt())
.setCreateBy(node.getCreateBy())
.setUpdateBy(node.getUpdateBy());
nodeList.add(nodeJson);
List<SkipJson> skipList = new ArrayList<>();
nodeJson.setSkipList(skipList);
if (CollUtil.isNotEmpty(node.getSkipList())) {
for (FlowSkip skip : node.getSkipList()) {
skipList.add(new SkipJson()
.setCoordinate(skip.getCoordinate())
.setSkipType(skip.getSkipType())
.setSkipName(skip.getSkipName())
.setSkipCondition(skip.getSkipCondition())
.setNowNodeCode(skip.getNowNodeCode())
.setNextNodeCode(skip.getNextNodeCode())
.setCreateBy(skip.getCreateBy())
.setUpdateBy(skip.getUpdateBy()));
}
}
}
return defJson;
}
public static FlowDefinition copyDef(DefJson defJson) {
FlowDefinition definition = new FlowDefinition()
.setId(defJson.getId())
.setFlowCode(defJson.getFlowCode())
.setFlowName(defJson.getFlowName())
.setModelValue(defJson.getModelValue())
.setVersion(defJson.getVersion())
.setCategory(defJson.getCategory())
.setFormCustom(defJson.getFormCustom())
.setFormPath(defJson.getFormPath())
.setListenerType(defJson.getListenerType())
.setListenerPath(defJson.getListenerPath())
.setExt(defJson.getExt())
.setCreateBy(defJson.getCreateBy())
.setUpdateBy(defJson.getUpdateBy());
List<FlowNode> nodeList = new ArrayList<>();
definition.setNodeList(nodeList);
for (NodeJson nodeJson : defJson.getNodeList()) {
// 向节点中添加子节点
FlowNode node = new FlowNode()
.setNodeType(nodeJson.getNodeType())
.setNodeCode(nodeJson.getNodeCode())
.setNodeName(nodeJson.getNodeName())
.setPermissionFlag(nodeJson.getPermissionFlag())
.setNodeRatio(nodeJson.getNodeRatio() != null ? nodeJson.getNodeRatio() : "0")
.setCoordinate(nodeJson.getCoordinate())
.setAnyNodeSkip(nodeJson.getAnyNodeSkip())
.setListenerType(nodeJson.getListenerType())
.setListenerPath(nodeJson.getListenerPath())
.setFormCustom(nodeJson.getFormCustom())
.setFormPath(nodeJson.getFormPath())
.setExt(nodeJson.getExt())
.setCreateBy(nodeJson.getCreateBy())
.setUpdateBy(nodeJson.getUpdateBy());
nodeList.add(node);
List<FlowSkip> skipList = new ArrayList<>();
node.setSkipList(skipList);
if (CollUtil.isNotEmpty(nodeJson.getSkipList())) {
for (SkipJson skipJson : nodeJson.getSkipList()) {
skipList.add(new FlowSkip()
.setCoordinate(skipJson.getCoordinate())
.setSkipType(skipJson.getSkipType())
.setSkipName(skipJson.getSkipName())
.setSkipCondition(skipJson.getSkipCondition())
.setNowNodeCode(skipJson.getNowNodeCode())
.setNextNodeCode(skipJson.getNextNodeCode())
.setCreateBy(skipJson.getCreateBy())
.setUpdateBy(skipJson.getUpdateBy()));
}
}
}
return definition;
}
public static FlowCombine copyCombine(DefJson defJson) {
FlowDefinition definition = copyDef(defJson);
FlowCombine flowCombine = new FlowCombine();
flowCombine.setDefinition(definition);
flowCombine.setAllNodes(definition.getNodeList());
List<FlowSkip> skipList = definition.getNodeList().stream()
.map(FlowNode::getSkipList)
.filter(Objects::nonNull)
.flatMap(List::stream)
.collect(Collectors.toList());
flowCombine.setAllSkips(skipList);
return flowCombine;
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import org.dromara.warm.flow.entity.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.entity.FlowDefinition;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowSkip;
import java.util.ArrayList;
import java.util.List;
/**
* 流程数据集合
*
* @author warm
* @since 2023/3/30 14:27
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class FlowCombine {
/**
* 所有的流程定义
*/
private FlowDefinition definition = new FlowDefinition();
/**
* 所有的流程节点
*/
private List<FlowNode> allNodes = new ArrayList<>();
/**
* 所有的流程节点跳转关联
*/
private List<FlowSkip> allSkips = new ArrayList<>();
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
* @author vanlin
* @since 2024-9-24 11:11
*/
@Getter
@Setter
public class FlowDto implements Serializable {
/**
* ID
*/
private Long id;
/**
* 数据
*/
private Object data;
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 表格分页数据对象
*
* @author ruoyi
*/
@Getter
@Setter
@NoArgsConstructor
@Accessors(chain = true)
public class FlowPage<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private long total;
/**
* 列表数据
*/
private List<T> rows;
/**
* 消息状态码
*/
private int code;
/**
* 消息内容
*/
private String msg;
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public FlowPage(List<T> list, int total) {
this.rows = list;
this.total = total;
}
}

View File

@ -0,0 +1,340 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import org.dromara.warm.flow.json.JsonUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.handler.PermissionHandler;
import java.io.Serializable;
import java.util.*;
/**
* 工作流内置参数
*
* @author warm
* @since 2023/3/31 17:18
*/
public class FlowParams implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 流程编码
*/
@Getter
private String flowCode;
/**
* 当前办理人唯一标识就是确定唯一用的如用户id通常用来入库记录流程实例创建人办理人
*/
private String handler;
/**
* 节点编码如果要指定跳转节点传入
*/
@Getter
private String nodeCode;
/**
* 用户权限标识和办理权限有关是否有办理权限通俗来说就是设计器里面预设的办理人和这个标识是否有交集有交集就可以办理审批的时候就不会提示报错
*/
private List<String> permissionFlag;
/**
* 跳转类型PASS审批通过 REJECT退回
*/
@Getter
private String skipType;
/**
* 审批意见
*/
@Getter
private String message;
/**
* 流程变量
*/
@Getter
private Map<String, Object> variable = new HashMap<>();
/**
* 流程实例状态
*/
@Getter
private String flowStatus;
/**
* 历史任务表状态
*/
@Getter
private String hisStatus;
/**
* 流程激活状态0挂起 1激活
*/
@Getter
private Integer activityStatus;
/**
* 协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)
*/
@Getter
private Integer cooperateType;
/**
* 扩展字段预留给业务系统使用
*/
@Getter
private String ext;
/**
* 扩展字段预留给业务系统使用
*/
@Getter
private String hisTaskExt;
/**
* 增加办理人加签转办委托
*/
@Getter
private List<String> addHandlers;
/**
* 减少办理人减签委托
*/
@Getter
private List<String> reductionHandlers;
/**
* 忽略-办理权限校验true忽略false不忽略
*/
@Getter
private boolean ignore;
/**
* 忽略-委派处理true忽略false不忽略
*/
@Getter
private boolean ignoreDepute;
/**
* 忽略-会签票签处理true忽略false不忽略
*/
@Getter
private boolean ignoreCooperate;
/**
* 执行的下个任务的办理人
*/
@Getter
private String[] nextHandler;
/**
* 下个任务处理人配置类型true-追加false-覆盖默认false
*/
@Getter
private boolean nextHandlerAppend;
public FlowParams() {
}
public FlowParams(String skipType, String message, Map<String, Object> variable) {
this.skipType = skipType;
this.message = message;
this.variable = variable;
}
public FlowParams(String nodeCode, String skipType, String message, Map<String, Object> variable) {
this.nodeCode = nodeCode;
this.skipType = skipType;
this.message = message;
this.variable = variable;
}
public FlowParams(String skipType, String message, Map<String, Object> variable
, String flowStatus, String hisStatus) {
this.skipType = skipType;
this.message = message;
this.variable = variable;
this.flowStatus = flowStatus;
this.hisStatus = hisStatus;
}
public FlowParams(String nodeCode, String skipType, String message, Map<String, Object> variable
, String flowStatus, String hisStatus) {
this.nodeCode = nodeCode;
this.skipType = skipType;
this.message = message;
this.variable = variable;
this.flowStatus = flowStatus;
this.hisStatus = hisStatus;
}
public static FlowParams build() {
return new FlowParams();
}
public FlowParams flowCode(String flowCode) {
this.flowCode = flowCode;
return this;
}
public FlowParams handler(String handler) {
this.handler = handler;
return this;
}
public FlowParams nodeCode(String nodeCode) {
this.nodeCode = nodeCode;
return this;
}
public FlowParams permissionFlag(List<String> permissionFlag) {
this.permissionFlag = permissionFlag;
return this;
}
public FlowParams message(String message) {
this.message = message;
return this;
}
public FlowParams variable(Map<String, Object> variable) {
this.variable = variable;
return this;
}
public FlowParams flowStatus(String flowStatus) {
this.flowStatus = flowStatus;
return this;
}
public FlowParams hisStatus(String hisStatus) {
this.hisStatus = hisStatus;
return this;
}
public FlowParams activityStatus(Integer activityStatus) {
this.activityStatus = activityStatus;
return this;
}
public FlowParams cooperateType(Integer cooperateType) {
this.cooperateType = cooperateType;
return this;
}
public FlowParams ext(String ext) {
this.ext = ext;
return this;
}
public FlowParams hisTaskExt(String hisTaskExt) {
this.hisTaskExt = hisTaskExt;
return this;
}
public FlowParams nextHandler(String... nextHandler) {
// 如果外部传递 null , 就忽略 , 维持节点本身审批,防止 null 数据变成 [null]
if (nextHandler == null) {
return this;
}
this.nextHandler = Arrays.stream(nextHandler).filter(Objects::nonNull).toArray(String[]::new);
return this;
}
public String getVariableStr() {
return JsonUtil.objToStr(variable);
}
public String getHandler() {
if (StrUtil.isEmpty(handler)) {
PermissionHandler permissionHandler = FlowEngine.permissionHandler();
if (permissionHandler != null) {
try {
handler = permissionHandler.getHandler();
} catch (Exception ignored) {
}
}
}
return handler;
}
public List<String> getPermissionFlag() {
if (CollUtil.isEmpty(permissionFlag)) {
PermissionHandler permissionHandler = FlowEngine.permissionHandler();
if (permissionHandler != null) {
try {
permissionFlag = permissionHandler.permissions();
} catch (Exception ignored) {
}
}
}
return permissionFlag;
}
public FlowParams skipType(String skipType) {
this.skipType = skipType;
return this;
}
public FlowParams addHandlers(List<String> addHandlers) {
this.addHandlers = addHandlers;
return this;
}
public FlowParams reductionHandlers(List<String> reductionHandlers) {
this.reductionHandlers = reductionHandlers;
return this;
}
public FlowParams ignore(boolean ignore) {
this.ignore = ignore;
return this;
}
public FlowParams ignoreDepute(boolean ignoreDepute) {
this.ignoreDepute = ignoreDepute;
return this;
}
public FlowParams ignoreCooperate(boolean ignoreCooperate) {
this.ignoreCooperate = ignoreCooperate;
return this;
}
public FlowParams nextHandlerAppend(boolean nextHandlerAppend) {
this.nextHandlerAppend = nextHandlerAppend;
return this;
}
public FlowParams formData(Map<String, Object> formData) {
if (this.variable == null) {
this.variable = new HashMap<>();
}
this.variable.put("formData", formData);
return this;
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.utils.MapUtil;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 流程节点对象Vo
*
* @author warm
* @since 2023-03-29
*/
@Setter
@Getter
@Accessors(chain = true)
public class NodeJson {
/**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 流程节点版本
*/
private String version;
/**
* 权限标识权限类型:权限标识可以多个@@隔开)
*/
private String permissionFlag;
/**
* 流程签署比例值
*/
private String nodeRatio;
/**
* 流程节点坐标
*/
private String coordinate;
/**
* 任意结点跳转
*/
private String anyNodeSkip;
/**
* 监听器类型
*/
private String listenerType;
/**
* 监听器路径
*/
private String listenerPath;
/**
* 审批表单是否自定义Y= N=
*/
private String formCustom;
/**
* 审批表单是否自定义Y= N=
*/
private String formPath;
/**
* 节点扩展属性
*/
private String ext;
/**
* 办理状态: 0未办理 1待办理 2已办理
*/
private Integer status;
/**
* 扩展map保存业务自定义扩展属性
*/
private Map<String, Object> extMap;
/**
* 流程图节点提示内容
*/
private PromptContent promptContent;
/**
* 跳转条件
*/
private List<SkipJson> skipList = new ArrayList<>();
private String createBy;
private String updateBy;
public Map<String, Object> getExtMap() {
if (MapUtil.isEmpty(extMap)) {
extMap = new HashMap<>();
}
return extMap;
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowSkip;
import java.util.ArrayList;
import java.util.List;
/**
* 办理过程中途径数据用于渲染流程图
*
* @author warm
* @since 2025/1/4
*/
@Getter
@Setter
@Accessors(chain = true)
public class PathWayData {
/**
* 流程定义id
*/
private Long defId;
/**
* 流程实例id
*/
private Long insId;
/**
* 跳转类型PASS审批通过 REJECT退回
*/
private String skipType;
/**
* 目标结点集合
*/
private List<FlowNode> targetNodes = new ArrayList<>();
/**
* 途径结点集合
*/
private List<FlowNode> pathWayNodes = new ArrayList<>();
/**
* 途径流程跳转线
*/
private List<FlowSkip> pathWaySkips = new ArrayList<>();
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.Map;
/**
* 提示信息
*
* @author warm
* @since 2025/6/5
*/
@Getter
@Setter
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class PromptContent {
/**
* 弹窗样式
*/
private Map<String, Object> dialogStyle;
/**
* 提示信息
*/
private List<InfoItem> info;
/**
* 提示信息项
*/
@Getter
@Setter
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public static class InfoItem {
/**
* 前缀
*/
private String prefix;
/**
* 前缀样式
*/
private Map<String, Object> prefixStyle;
/**
* 内容
*/
private String content;
/**
* 内容样式
*/
private Map<String, Object> contentStyle;
/**
* 行样式
*/
private Map<String, Object> rowStyle;
}
}

View File

@ -0,0 +1,85 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.Map;
/**
* 节点跳转关联对象Vo
*
* @author warm
* @since 2023-03-29
*/
@Setter
@Getter
@Accessors(chain = true)
public class SkipJson {
/**
* 当前流程节点的编码
*/
private String nowNodeCode;
/**
* 下一个流程节点的编码
*/
private String nextNodeCode;
/**
* 跳转名称
*/
private String skipName;
/**
* 跳转类型PASS审批通过 REJECT退回
*/
private String skipType;
/**
* 跳转条件
*/
private String skipCondition;
/**
* 流程跳转坐标
*/
private String coordinate;
/**
* 办理状态: 0未办理 1待办理 2已办理
*/
private Integer status;
/**
* 扩展map保存业务自定义扩展属性
*/
private Map<String, Object> extMap;
/**
* 流程图节点提示内容
*/
private List<String> promptContent;
private String createBy;
private String updateBy;
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 页面树列表
*
* @author ruoyi
*/
@Getter
@Setter
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Tree implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private String id;
/**
* 名称
*/
private String name;
/**
* 父ID
*/
private String parentId;
/**
*
*/
private List<Tree> children = new ArrayList<>();
}

View File

@ -0,0 +1,154 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import org.dromara.warm.flow.entity.RootEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowDefinition;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowUser;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 流程定义
*/
@Data
@Accessors(chain = true)
public class FlowDefinition implements RootEntity {
/**
* 主键
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
/**
*
* 删除标记
*/
@TableLogic(value = "0", delval = "1")
private String delFlag;
/**
* 流程编码
*/
private String flowCode;
/**
* 流程名称
*/
private String flowName;
/**
* 设计器模型CLASSICS经典模型 MIMIC仿钉钉模型
*/
private String modelValue;
/**
* 流程类别
*/
private String category;
/**
* 流程版本
*/
private String version;
/**
* 是否发布0未开启 1开启
*/
private Integer isPublish;
/**
* 审批表单是否自定义Y是 N否
*/
private String formCustom;
/**
* 审批表单路径
*/
private String formPath;
/**
* 流程激活状态0挂起 1激活
*/
private Integer activityStatus;
/**
* 监听器类型
*/
private String listenerType;
/**
* 监听器路径
*/
private String listenerPath;
/**
* 扩展字段预留给业务系统使用
*/
private String ext;
@TableField(exist = false)
private List<FlowNode> nodeList = new ArrayList<>();
@TableField(exist = false)
private List<FlowUser> userList = new ArrayList<>();
/**
* 复制当前对象不含主键时间字段
*/
public FlowDefinition copy() {
return new FlowDefinition()
.setDelFlag(this.getDelFlag())
.setFlowCode(this.getFlowCode())
.setFlowName(this.getFlowName())
.setModelValue(this.getModelValue())
.setCategory(this.getCategory())
.setVersion(this.getVersion())
.setFormCustom(this.getFormCustom())
.setFormPath(this.getFormPath())
.setListenerType(this.getListenerType())
.setListenerPath(this.getListenerPath())
.setExt(this.getExt())
.setCreateBy(this.getCreateBy())
.setUpdateBy(this.getUpdateBy());
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import org.dromara.warm.flow.json.JsonUtil;
import org.dromara.warm.flow.entity.RootEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowHisTask;
import java.util.Date;
import java.util.List;
/**
* 历史任务记录
*/
@Data
@Accessors(chain = true)
public class FlowHisTask implements RootEntity {
/**
* 主键
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 删除标记
*/
@TableLogic(value = "0", delval = "1")
private String delFlag;
/**
* 对应flow_definition表的id
*/
private Long definitionId;
/**
* 流程名称
*/
@TableField(exist = false)
private String flowName;
/**
* 流程实例表id
*/
private Long instanceId;
/**
* 任务表id
*/
private Long taskId;
/**
* 协作方式(1审批 2转办 3委派 4会签 5票签 6加签 7减签)
*/
private Integer cooperateType;
/**
* 业务id
*/
@TableField(exist = false)
private String businessId;
/**
* 开始节点编码
*/
private String nodeCode;
/**
* 开始节点名称
*/
private String nodeName;
/**
* 开始节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 目标节点编码
*/
private String targetNodeCode;
/**
* 结束节点名称
*/
private String targetNodeName;
/**
* 审批者
*/
private String approver;
/**
* 协作人(只有转办会签票签委派)
*/
private String collaborator;
/**
* 权限标识 permissionFlag的list形式
*/
@TableField(exist = false)
private List<String> permissionList;
/**
* 跳转类型PASS通过 REJECT退回 NONE无动作
*/
private String skipType;
/**
* 流程状态0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回
*/
private String flowStatus;
/**
* 审批意见
*/
private String message;
/**
* 流程变量
*/
private String variable;
/**
* 业务详情 存业务类的json
*/
private String ext;
/**
* 审批表单是否自定义Y= N=
*/
private String formCustom;
/**
* 审批表单路径
*/
private String formPath;
/**
* 流程变量转 map
*/
public java.util.Map<String, Object> getVariableMap() {
return JsonUtil.strToMap(getVariable());
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import org.dromara.warm.flow.json.JsonUtil;
import org.dromara.warm.flow.entity.RootEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowInstance;
import java.util.Date;
/**
* 流程实例
*/
@Data
@Accessors(chain = true)
public class FlowInstance implements RootEntity {
/**
* 主键
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
/**
* 删除标记
*/
@TableLogic(value = "0", delval = "1")
private String delFlag;
/**
* 对应flow_definition表的id
*/
private Long definitionId;
/**
* 流程名称
*/
@TableField(exist = false)
private String flowName;
/**
* 业务id
*/
private String businessId;
/**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 流程变量
*/
private String variable;
/**
* 流程状态0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回
*/
private String flowStatus;
/**
* 流程激活状态0挂起 1激活
*/
private Integer activityStatus;
/**
* 审批表单是否自定义Y= N=
*/
@TableField(exist = false)
private String formCustom;
/**
* 审批表单是否自定义Y= N=
*/
@TableField(exist = false)
private String formPath;
/**
* 流程定义json
*/
private String defJson;
/**
* 扩展字段预留给业务系统使用
*/
private String ext;
/**
* 流程变量转 map
*/
public java.util.Map<String, Object> getVariableMap() {
return JsonUtil.strToMap(getVariable());
}
}

View File

@ -0,0 +1,157 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import org.dromara.warm.flow.entity.RootEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowSkip;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 流程节点
*/
@Data
@Accessors(chain = true)
public class FlowNode implements RootEntity {
/**
* 节点跳转列表非表字段
*/
@TableField(exist = false)
List<FlowSkip> skipList;
/**
* 主键
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
/**
* 删除标记
*/
@TableLogic(value = "0", delval = "1")
private String delFlag;
/**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程id
*/
private Long definitionId;
/**
* 流程节点编码 每个流程的nodeCode是唯一的,即definitionId+nodeCode唯一,在数据库层面做了控制
*/
private String nodeCode;
/**
* 流程节点名称
*/
private String nodeName;
/**
* 权限标识权限类型:权限标识可以多个@@隔开)
*/
private String permissionFlag;
/**
* 流程签署比例值
*/
private String nodeRatio;
/**
* 流程节点坐标
*/
private String coordinate;
/**
* 版本
*
* @deprecated 下个版本废弃
*/
@Deprecated
private String version;
/**
* 任意结点跳转
*/
private String anyNodeSkip;
/**
* 监听器类型
*/
private String listenerType;
/**
* 监听器路径
*/
private String listenerPath;
/**
* 审批表单是否自定义Y= N=
*/
private String formCustom;
/**
* 审批表单路径
*/
private String formPath;
/**
* 节点扩展属性
*/
private String ext;
/**
* 复制当前对象不含主键时间字段
*/
public FlowNode copy() {
return new FlowNode()
.setDelFlag(this.getDelFlag())
.setNodeType(this.getNodeType())
.setDefinitionId(this.getDefinitionId())
.setNodeCode(this.getNodeCode())
.setNodeName(this.getNodeName())
.setNodeRatio(this.getNodeRatio())
.setPermissionFlag(this.getPermissionFlag())
.setCoordinate(this.getCoordinate())
.setVersion(this.getVersion())
.setAnyNodeSkip(this.getAnyNodeSkip())
.setListenerType(this.getListenerType())
.setListenerPath(this.getListenerPath())
.setFormCustom(this.getFormCustom())
.setFormPath(this.getFormPath())
.setExt(this.getExt());
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import org.dromara.warm.flow.entity.RootEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowSkip;
import java.util.Date;
/**
* 节点跳转关联
*/
@Data
@Accessors(chain = true)
public class FlowSkip implements RootEntity {
/**
* 主键
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
/**
* 删除标记
*/
@TableLogic(value = "0", delval = "1")
private String delFlag;
/**
* 流程id
*/
private Long definitionId;
/**
* 节点id
*/
@TableField(exist = false)
private Long nodeId;
/**
* 当前流程节点的编码
*/
private String nowNodeCode;
/**
* 当前节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nowNodeType;
/**
* 下一个流程节点的编码
*/
private String nextNodeCode;
/**
* 下一个节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nextNodeType;
/**
* 跳转名称
*/
private String skipName;
/**
* 跳转类型PASS审批通过 REJECT退回
*/
private String skipType;
/**
* 跳转条件
*/
private String skipCondition;
/**
* 流程跳转坐标
*/
private String coordinate;
/**
* 复制当前对象不含主键时间字段
*/
public FlowSkip copy() {
return new FlowSkip()
.setDelFlag(getDelFlag())
.setDefinitionId(getDefinitionId())
.setNowNodeCode(getNowNodeCode())
.setNowNodeType(getNowNodeType())
.setNextNodeCode(getNextNodeCode())
.setNextNodeType(getNextNodeType())
.setSkipName(getSkipName())
.setSkipType(getSkipType())
.setSkipCondition(getSkipCondition())
.setCoordinate(getCoordinate());
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import org.dromara.warm.flow.entity.RootEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowTask;
import org.dromara.warm.flow.entity.FlowUser;
import java.util.Date;
import java.util.List;
/**
* 待办任务
*/
@Data
@Accessors(chain = true)
public class FlowTask implements RootEntity {
/**
* 主键
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
/**
* 删除标记
*/
@TableLogic(value = "0", delval = "1")
private String delFlag;
/**
* 对应flow_definition表的id
*/
private Long definitionId;
/**
* 流程实例表id
*/
private Long instanceId;
/**
* 流程名称
*/
@TableField(exist = false)
private String flowName;
/**
* 业务id
*/
@TableField(exist = false)
private String businessId;
/**
* 节点编码
*/
private String nodeCode;
/**
* 节点名称
*/
private String nodeName;
/**
* 节点类型0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关
*/
private Integer nodeType;
/**
* 流程状态0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回
*/
private String flowStatus;
/**
* 权限标识 permissionFlag的list形式
*/
@TableField(exist = false)
private List<String> permissionList;
/**
* 流程用户列表
*/
@TableField(exist = false)
private List<FlowUser> userList;
/**
* 审批表单是否自定义Y= N=
*/
private String formCustom;
/**
* 审批表单
*/
private String formPath;
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import org.dromara.warm.flow.entity.RootEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.entity.FlowUser;
import java.util.Date;
/**
* 流程用户
*/
@Data
@Accessors(chain = true)
public class FlowUser implements RootEntity {
/**
* 主键
*/
private Long id;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
/**
* 删除标记
*/
@TableLogic(value = "0", delval = "1")
private String delFlag;
/**
* 人员类型1待办任务的审批人权限 2待办任务的转办人权限 3待办任务的委托人权限
*/
private String type;
/**
* 权限人
*/
private String processedBy;
/**
* 任务表id
*/
private Long associated;
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.entity;
import java.io.Serializable;
import java.util.Date;
/**
* 流程基础entity
*
* @author warm
* @since 2023/5/17 17:23
*/
public interface RootEntity extends Serializable {
Long getId();
RootEntity setId(Long id);
Date getCreateTime();
RootEntity setCreateTime(Date createTime);
Date getUpdateTime();
RootEntity setUpdateTime(Date updateTime);
default String getCreateBy() {
return null;
}
default RootEntity setCreateBy(String createBy) {
return this;
}
default String getUpdateBy() {
return null;
}
default RootEntity setUpdateBy(String updateBy) {
return this;
}
String getDelFlag();
RootEntity setDelFlag(String delFlag);
}

View File

@ -0,0 +1,57 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 激活状态
*
* @author warm
* @since 2025/6/25
*/
@Getter
@AllArgsConstructor
public enum ActivityStatus {
/**
* 激活状态
*/
SUSPENDED(0, "挂起"),
ACTIVITY(1, "激活");
private final Integer key;
private final String value;
/**
* 判断流程是否激活
*/
public static Boolean isActivity(Integer key) {
return ObjectUtil.isNotNull(key) && (ActivityStatus.ACTIVITY.getKey().equals(key));
}
/**
* 判断流程是否挂起
*/
public static Boolean isSuspended(Integer key) {
return ObjectUtil.isNotNull(key) && (ActivityStatus.SUSPENDED.getKey().equals(key));
}
}

View File

@ -0,0 +1,158 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 流程图状态
*
* @author warm
* @since 2023/3/31 12:16
*/
@Getter
@AllArgsConstructor
public enum ChartStatus {
/**
* 流程图状态
*/
NOT_DONE(0, "未办理", new Color(107,114,128)),
TO_DO(1, "待办理", new Color(245,158,11)),
DONE(2, "已办理", new Color(56,161,105));
private final Integer key;
private final String value;
private final Color color;
private static final Map<Integer, Color> CUSTOM_COLOR = new HashMap<>();
private static final Map<Integer, Color> CUSTOM_COLOR_CLASSICS = new HashMap<>();
private static final Map<Integer, Color> CUSTOM_COLOR_MIMIC = new HashMap<>();
public static void initCustomColor(List<String> chartStatusColor, List<String> chartStatusColorClassics,
List<String> chartStatusColorMimic) {
if (CollUtil.isNotEmpty(chartStatusColor) && chartStatusColor.size() == 3) {
for (int i = 0; i < chartStatusColor.size(); i++) {
String statusColor = chartStatusColor.get(i);
if (StrUtil.isNotEmpty(statusColor)) {
String[] colorArr = statusColor.split(",");
if (colorArr.length == 3) {
ChartStatus.CUSTOM_COLOR.put(i, new Color(Integer.parseInt(colorArr[0]), Integer.parseInt(colorArr[1]), Integer.parseInt(colorArr[2])));
}
}
}
}
if (CollUtil.isNotEmpty(chartStatusColorClassics) && chartStatusColorClassics.size() == 3) {
for (int i = 0; i < chartStatusColorClassics.size(); i++) {
String statusColor = chartStatusColorClassics.get(i);
if (StrUtil.isNotEmpty(statusColor)) {
String[] colorArr = statusColor.split(",");
if (colorArr.length == 3) {
ChartStatus.CUSTOM_COLOR_CLASSICS.put(i, new Color(Integer.parseInt(colorArr[0]), Integer.parseInt(colorArr[1]), Integer.parseInt(colorArr[2])));
}
}
}
}
if (CollUtil.isNotEmpty(chartStatusColorMimic) && chartStatusColorMimic.size() == 3) {
for (int i = 0; i < chartStatusColorMimic.size(); i++) {
String statusColor = chartStatusColorMimic.get(i);
if (StrUtil.isNotEmpty(statusColor)) {
String[] colorArr = statusColor.split(",");
if (colorArr.length == 3) {
ChartStatus.CUSTOM_COLOR_MIMIC.put(i, new Color(Integer.parseInt(colorArr[0]), Integer.parseInt(colorArr[1]), Integer.parseInt(colorArr[2])));
}
}
}
}
}
public static Color getNotDone(String modelValue) {
return getColorByKey(ChartStatus.NOT_DONE, modelValue);
}
public static Color getToDo(String modelValue) {
return getColorByKey(ChartStatus.TO_DO, modelValue);
}
public static Color getDone(String modelValue) {
return getColorByKey(ChartStatus.DONE, modelValue);
}
public static Color getColorByKey(ChartStatus chartStatus, String modelValue) {
Color color = null;
if (ModelEnum.CLASSICS.name().equals(modelValue)) {
color = ChartStatus.CUSTOM_COLOR_CLASSICS.get(chartStatus.getKey());
} else if (ModelEnum.MIMIC.name().equals(modelValue)) {
color = ChartStatus.CUSTOM_COLOR_MIMIC.get(chartStatus.getKey());
}
if (ObjectUtil.isNull(color)) {
color = ChartStatus.CUSTOM_COLOR.get(chartStatus.getKey());
}
return ObjectUtil.defaultIfNull(color, chartStatus.getColor());
}
public static Color getColorByKey(Integer key) {
for (ChartStatus item : ChartStatus.values()) {
if (item.getKey().equals(key)) {
Color color = ChartStatus.CUSTOM_COLOR.get(key);
return ObjectUtil.defaultIfNull(color, item.getColor());
}
}
return null;
}
/**
* 判断是否未办理
*
* @param key 状态
*/
public static Boolean isNotDone(Integer key) {
return ObjectUtil.isNotNull(key) && (ChartStatus.NOT_DONE.getKey().equals(key));
}
/**
* 判断是否待办理
*
* @param key 状态
*/
public static Boolean isToDo(Integer key) {
return ObjectUtil.isNotNull(key) && (ChartStatus.TO_DO.getKey().equals(key));
}
/**
* 判断是否已办理
*
* @param key 状态
*/
public static Boolean isDone(Integer key) {
return ObjectUtil.isNotNull(key) && (ChartStatus.DONE.getKey().equals(key));
}
}

View File

@ -0,0 +1,180 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import org.dromara.warm.flow.constant.ExceptionCons;
import org.dromara.warm.flow.constant.FlowCons;
import org.dromara.warm.flow.expression.SpelHelper;
import org.dromara.warm.flow.strategy.ConditionStrategy;
import org.dromara.warm.flow.utils.AssertUtil;
import org.dromara.warm.flow.utils.MathUtil;
import java.util.Map;
/**
* 内置条件表达式策略合并原 eq/ne/gt/ge/lt/le/like/notLike/spel/default 十个策略类及 ConditionType 枚举
* 表达式格式eq@@flag|4前缀 eq@@ {@link ConditionStrategy} 截取进入 eval 的为 flag|4
*
* @author warm
*/
public enum Condition implements ConditionStrategy {
/**
* 等于
*/
EQ("eq") {
@Override
public Boolean afterEval(String value, String variableValue) {
if (MathUtil.isNumeric(value)) {
return MathUtil.determineSize(variableValue, value) == 0;
}
return variableValue.equals(value);
}
},
/**
* 不等于
*/
NE("ne") {
@Override
public Boolean afterEval(String value, String variableValue) {
if (MathUtil.isNumeric(value)) {
return MathUtil.determineSize(variableValue, value) != 0;
}
return !variableValue.equals(value);
}
},
/**
* 大于仅数值比较
*/
GT("gt") {
@Override
public Boolean afterEval(String value, String variableValue) {
return MathUtil.isNumeric(value) && MathUtil.determineSize(variableValue, value) > 0;
}
},
/**
* 大于等于仅数值比较
*/
GE("ge") {
@Override
public Boolean afterEval(String value, String variableValue) {
return MathUtil.isNumeric(value) && MathUtil.determineSize(variableValue, value) >= 0;
}
},
/**
* 小于仅数值比较
*/
LT("lt") {
@Override
public Boolean afterEval(String value, String variableValue) {
return MathUtil.isNumeric(value) && MathUtil.determineSize(variableValue, value) < 0;
}
},
/**
* 小于等于仅数值比较
*/
LE("le") {
@Override
public Boolean afterEval(String value, String variableValue) {
return MathUtil.isNumeric(value) && MathUtil.determineSize(variableValue, value) <= 0;
}
},
/**
* 包含
*/
LIKE("like") {
@Override
public Boolean afterEval(String value, String variableValue) {
return variableValue.contains(value);
}
},
/**
* 不包含
*/
NOT_LIKE("notLike") {
@Override
public Boolean afterEval(String value, String variableValue) {
return !variableValue.contains(value);
}
},
/**
* spel 条件表达式 spel@@#{@user.eval()}
*/
SPEL(FlowCons.SPEL) {
@Override
public Boolean eval(String expression, Map<String, Object> variable) {
return SpelHelper.evalBool(expression, variable);
}
@Override
public Boolean afterEval(String value, String variableValue) {
throw new UnsupportedOperationException("spel 条件表达式不走值比较");
}
},
/**
* 默认条件表达式 default@@${flag == 5 && flag > 4}基于 spel 简化使用
*/
DEFAULT(FlowCons.DEFAULT) {
@Override
public Boolean eval(String expression, Map<String, Object> variable) {
return SPEL.eval(SpelHelper.replace(expression, variable), variable);
}
@Override
public Boolean afterEval(String value, String variableValue) {
throw new UnsupportedOperationException("默认条件表达式不走值比较");
}
};
private final String key;
Condition(String key) {
this.key = key;
}
@Override
public String getType() {
return key;
}
@Override
public Boolean eval(String expression, Map<String, Object> variable) {
AssertUtil.isEmpty(variable, ExceptionCons.NULL_CONDITION_VALUE);
String[] split = expression.split(FlowCons.SPLIT_VERTICAL);
String name = split[0].trim();
Object o = variable.get(name);
AssertUtil.isNull(o, ExceptionCons.NULL_CONDITION_VALUE);
return afterEval(split[1].trim(), String.valueOf(o));
}
/**
* 比较表达式值与流程变量值
*
* @param value 表达式最后一个参数比如eq@@flag|5 [5]
* @param variableValue 流程变量值
* @return 比较结果
*/
public abstract Boolean afterEval(String value, String variableValue);
}

View File

@ -0,0 +1,198 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.warm.flow.constant.FlowCons;
import org.dromara.warm.flow.utils.MathUtil;
/**
* 协作类型
* APPROVAL-无其他协作方式
* TRANSFER-转办任务转给其他人办理
* DEPUTE-委派求助其他人审批然后参照他的意见决定是否审批通过
* COUNTERSIGN-会签和其他人一起审批通过才算通过
* VOTE-票签和部分人一起审批达到一定通过率才算通过
* ADD_SIGNATURE-加签办理中途希望其他人一起参与办理
* REDUCTION_SIGNATURE-减签办理中途希望某些人不参与办理
*
* @author xiarg
* @since 2024/5/10 16:04
*/
@Getter
@AllArgsConstructor
public enum CooperateType {
/**
* 协作类型
*/
APPROVAL(1, ""),
TRANSFER(2, "转办"),
DEPUTE(3, "委派"),
COUNTERSIGN(4, "会签"),
VOTE(5, "票签"),
ADD_SIGNATURE(6, "加签"),
REDUCTION_SIGNATURE(7, "减签");
private final Integer key;
private final String value;
/**
* 票签中的固定通过人数策略前缀
*/
public static final String PASS_COUNT = "passCount";
/**
* 票签中的固定驳回人数策略前缀
*/
public static final String REJECT_COUNT = "rejectCount";
/**
* 顺签
*/
public static final String SEQUENCE = "sequence";
public static Integer getKeyByValue(String value) {
for (CooperateType item : CooperateType.values()) {
if (item.getValue().equals(value)) {
return item.getKey();
}
}
return null;
}
public static String getValueByKey(Integer key) {
for (CooperateType item : CooperateType.values()) {
if (item.getKey().equals(key)) {
return item.getValue();
}
}
return null;
}
public static CooperateType getByKey(Integer key) {
for (CooperateType item : CooperateType.values()) {
if (item.getKey().equals(key)) {
return item;
}
}
return null;
}
/**
* 判断是否为或签
* @param ratio 比例
* @return truefalse不是
*/
public static boolean isOrSign(String ratio) {
return MathUtil.isZero(ratio);
}
/**
* 判断是否是会签
*
* @param ratio 比例
* @return truefalse不是
*/
public static boolean isCountersign(String ratio) {
return MathUtil.isHundred(ratio);
}
/**
* 判断是否是票签中通过率策略
*
* @param ratio 比例
* @return truefalse不是
*/
public static boolean isVoteSignPassRatio(String ratio) {
return MathUtil.isBetweenZeroAndHundred(ratio);
}
/**
* 判断是否是票签中的固定通过人数策略
*
* @param passCount 固定通过人数
* @return truefalse不是
*/
public static boolean isVoteSignPassCount(String passCount) {
return StrUtil.isNotEmpty(passCount) && passCount.startsWith(PASS_COUNT);
}
/**
* 判断是否是票签中的固定驳回人数策略
*
* @param rejectCount 固定驳回人数
* @return truefalse不是
*/
public static boolean isVoteSignRejectCount(String rejectCount) {
return StrUtil.isNotEmpty(rejectCount) && rejectCount.startsWith(REJECT_COUNT);
}
/**
* 判断是否是票签中的默认表达式策略
*
* @param expression 默认表达式
* @return truefalse不是
*/
public static boolean isVoteSignDefault(String expression) {
return StrUtil.isNotEmpty(expression) && expression.startsWith(FlowCons.DEFAULT);
}
/**
* 判断是否是票签中的spel表达式策略
*
* @param expression spel表达式
* @return truefalse不是
*/
public static boolean isVoteSignRejectSpel(String expression) {
return StrUtil.isNotEmpty(expression) && expression.startsWith(FlowCons.SPEL);
}
/**
* 判断是否是顺签
*
* @param expression 表达式
* @return truefalse不是
*/
public static boolean isSequenceSign(String expression) {
return StrUtil.isNotEmpty(expression) && expression.endsWith(FlowCons.SPLIT_AT + SEQUENCE);
}
/**
* 判断是否是顺签
*
* @param expression 表达式
* @return truefalse不是
*/
public static String removeSequence(String expression) {
if (isSequenceSign(expression)) {
return expression.substring(0, expression.lastIndexOf(FlowCons.SPLIT_AT + SEQUENCE));
}
return expression;
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程状态
*
* @author warm
* @since 2023/3/31 12:16
*/
@Getter
@AllArgsConstructor
public enum FlowStatus {
/**
* 流程状态
*/
TOBESUBMIT("0", "待提交"),
APPROVAL("1", "审批中"),
PASS("2", "审批通过"),
AUTO_PASS("3", "自动完成"),
TERMINATE("4", "终止"),
NULLIFY("5", "作废"),
CANCEL("6", "撤销"),
RETRIEVE("7", "取回"),
FINISHED("8", "已完成"),
REJECT("9", "已退回"),
INVALID("10", "失效"),
TASK_BACK("11", "拿回"),
RE_START("12", "重启"),
PENDING("13", "暂存");
private final String key;
private final String value;
public static String getKeyByValue(String value) {
for (FlowStatus item : FlowStatus.values()) {
if (item.getValue().equals(value)) {
return item.getKey();
}
}
return null;
}
public static String getValueByKey(String key) {
for (FlowStatus item : FlowStatus.values()) {
if (item.getKey().equals(key)) {
return item.getValue();
}
}
return null;
}
public static FlowStatus getByKey(String key) {
for (FlowStatus item : FlowStatus.values()) {
if (item.getKey().equals(key)) {
return item;
}
}
return null;
}
/**
* 判断是否结束节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isFinished(String key) {
return ObjectUtil.isNotNull(key) && (FlowStatus.FINISHED.getKey().equals(key));
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 表单类型
*
* @author warm
* @since 2025/6/25
*/
@Getter
@AllArgsConstructor
public enum FormCustomEnum {
/**
* 表单路径
*/
N,
/**
* 表单路径
*/
Y,
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 框架类型: springbootsolon
*
* @author warm
* @since 2026/3/24
*/
@Getter
@AllArgsConstructor
public enum FrameworkType {
SPRING_BOOT,
SOLON;
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import org.dromara.warm.flow.expression.SpelHelper;
import org.dromara.warm.flow.strategy.HandlerStrategy;
import java.util.Map;
/**
* 内置办理人表达式策略合并原 DefaultHandlerStrategy/HandlerStrategySpel 两个策略类
* 表达式格式${flag} #{@user.evalVar()}
*
* @author warm,battcn
*/
public enum Handler implements HandlerStrategy {
/**
* 默认办理人表达式 ${flag}
*/
DEFAULT("$") {
@Override
public Object preEval(String expression, Map<String, Object> variable) {
String result = expression.replace("${", "").replace("}", "");
return variable.get(result);
}
},
/**
* spel 办理人表达式 #{@user.evalVar()}
*/
SPEL("#") {
@Override
public Object preEval(String expression, Map<String, Object> variable) {
return SpelHelper.parseExpression(expression, variable);
}
};
private final String key;
Handler(String key) {
this.key = key;
}
@Override
public String getType() {
return key;
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 设计器模型CLASSICS经典模型 MIMIC仿钉钉模型
*
* @author warm
* @since 2025/6/25
*/
@Getter
@AllArgsConstructor
public enum ModelEnum {
/**
* 经典模型
*/
CLASSICS,
/**
* 仿钉钉模型
*/
MIMIC
}

View File

@ -0,0 +1,162 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 节点类型
*
* @author warm
* @since 2023/3/31 12:16
*/
@AllArgsConstructor
@Getter
public enum NodeType {
/**
* 开始节点
*/
START(0, "start"),
/**
* 中间节点
*/
BETWEEN(1, "between"),
/**
* 结束节点
*/
END(2, "end"),
/**
* 互斥网关
*/
SERIAL(3, "serial"),
/**
* 并行网关
*/
PARALLEL(4, "parallel"),
/**
* 包容网关
*/
INCLUSIVE(5, "inclusive");
private final Integer key;
private final String value;
public static Integer getKeyByValue(String value) {
for (NodeType item : NodeType.values()) {
if (item.getValue().equals(value)) {
return item.getKey();
}
}
return null;
}
public static String getValueByKey(Integer key) {
for (NodeType item : NodeType.values()) {
if (item.getKey().equals(key)) {
return item.getValue();
}
}
return null;
}
public static NodeType getByKey(Integer key) {
for (NodeType item : NodeType.values()) {
if (item.getKey().equals(key)) {
return item;
}
}
return null;
}
/**
* 判断是否开始节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isStart(Integer key) {
return ObjectUtil.isNotNull(key) && (NodeType.START.getKey().equals(key));
}
/**
* 判断是否中间节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isBetween(Integer key) {
return ObjectUtil.isNotNull(key) && (NodeType.BETWEEN.getKey().equals(key));
}
/**
* 判断是否结束节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isEnd(Integer key) {
return ObjectUtil.isNotNull(key) && (NodeType.END.getKey().equals(key));
}
/**
* 判断是否网关节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isGateWay(Integer key) {
return ObjectUtil.isNotNull(key) && (NodeType.SERIAL.getKey().equals(key)
|| NodeType.PARALLEL.getKey().equals(key)|| NodeType.INCLUSIVE.getKey().equals(key));
}
/**
* 判断是否互斥网关节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isGateWaySerial(Integer key) {
return ObjectUtil.isNotNull(key) && NodeType.SERIAL.getKey().equals(key);
}
/**
* 判断是否并行网关节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isGateWayParallel(Integer key) {
return ObjectUtil.isNotNull(key) && NodeType.PARALLEL.getKey().equals(key);
}
/**
* 判断是否包容网关节点
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isGateWayInclusive(Integer key) {
return ObjectUtil.isNotNull(key) && NodeType.INCLUSIVE.getKey().equals(key);
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 节点类型
*
* @author warm
* @since 2023/3/31 12:16
*/
@Getter
@AllArgsConstructor
public enum PublishStatus {
/**
* 9=已失效0=未发布1=已发布
*/
EXPIRED(9, "已失效"),
UNPUBLISHED(0, "未发布"),
PUBLISHED(1, "已发布");
private final Integer key;
private final String value;
public static Integer getKeyByValue(String value) {
for (PublishStatus item : PublishStatus.values()) {
if (item.getValue().equals(value)) {
return item.getKey();
}
}
return null;
}
public static String getValueByKey(Integer key) {
for (PublishStatus item : PublishStatus.values()) {
if (item.getKey().equals(key)) {
return item.getValue();
}
}
return null;
}
public static PublishStatus getByKey(Integer key) {
for (PublishStatus item : PublishStatus.values()) {
if (item.getKey().equals(key)) {
return item;
}
}
return null;
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 审批动作
*
* @author warm
* @since 2023/3/31 12:16
*/
@Getter
@AllArgsConstructor
public enum SkipType {
/**
* 审批动作
*/
PASS("PASS", "审批通过"),
REJECT("REJECT", "退回"),
NONE("NONE", "无动作");
private final String key;
private final String value;
public static String getKeyByValue(String value) {
for (SkipType item : SkipType.values()) {
if (item.getValue().equals(value)) {
return item.getKey();
}
}
return null;
}
public static String getValueByKey(String key) {
for (SkipType item : SkipType.values()) {
if (item.getKey().equals(key)) {
return item.getValue();
}
}
return null;
}
public static SkipType getByKey(String key) {
for (SkipType item : SkipType.values()) {
if (item.getKey().equals(key)) {
return item;
}
}
return null;
}
/**
* 判断是否通过类型
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isPass(String key) {
return StrUtil.isNotEmpty(key) && (SkipType.PASS.getKey().equals(key));
}
/**
* 判断是否退回类型
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isReject(String key) {
return StrUtil.isNotEmpty(key) && (SkipType.REJECT.getKey().equals(key));
}
/**
* 判断是否无动作类型
*
* @param key 枚举key
* @return boolean
*/
public static Boolean isNone(String key) {
return StrUtil.isNotEmpty(key) && (SkipType.NONE.getKey().equals(key));
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程用户类型
*
* @author xiarg
* @since 2024/5/10 16:04
*/
@Getter
@AllArgsConstructor
public enum UserType {
/**
* 流程用户类型
*/
APPROVAL("1", "待办任务的审批人权限"),
TRANSFER("2", "待办任务的转办人权限"),
DEPUTE("3", "待办任务的委托人权限");
private final String key;
private final String value;
public static String getKeyByValue(String value) {
for (UserType item : UserType.values()) {
if (item.getValue().equals(value)) {
return item.getKey();
}
}
return null;
}
public static String getValueByKey(String key) {
for (UserType item : UserType.values()) {
if (item.getKey().equals(key)) {
return item.getValue();
}
}
return null;
}
public static UserType getByKey(String key) {
for (UserType item : UserType.values()) {
if (item.getKey().equals(key)) {
return item;
}
}
return null;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.enums;
import org.dromara.warm.flow.constant.FlowCons;
import org.dromara.warm.flow.expression.SpelHelper;
import org.dromara.warm.flow.strategy.VoteSignStrategy;
import java.util.Map;
/**
* 内置会签票签表达式策略合并原 VoteSignStrategySpel/VoteSignStrategyDefault 两个策略类
* 表达式格式spel@@#{@user.eval()} default@@${flag == 5 && flag > 4}
*
* @author warm
*/
public enum VoteSign implements VoteSignStrategy {
/**
* spel 会签表达式 spel@@#{@user.eval()}
*/
SPEL(FlowCons.SPEL) {
@Override
public Boolean eval(String expression, Map<String, Object> variable) {
return SpelHelper.evalBool(expression, variable);
}
},
/**
* 默认会签表达式 default@@${flag == 5 && flag > 4}基于 spel 简化使用
*/
DEFAULT(FlowCons.DEFAULT) {
@Override
public Boolean eval(String expression, Map<String, Object> variable) {
return SPEL.eval(SpelHelper.replace(expression, variable), variable);
}
};
private final String key;
VoteSign(String key) {
this.key = key;
}
@Override
public String getType() {
return key;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.exception;
import lombok.Getter;
/**
* 流程异常
*
* @author warm
*/
public final class FlowException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
@Getter
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细内部调试错误
*/
@Getter
private String detailMessage;
/**
* 空构造方法避免反序列化问题
*/
public FlowException() {
}
public FlowException(String message) {
this.message = message;
}
public FlowException(String message, Throwable cause) {
super(message, cause);
this.message = message;
}
public FlowException(String message, Integer code) {
this.message = message;
this.code = code;
}
public FlowException setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
return this;
}
@Override
public String getMessage() {
return message;
}
public FlowException setMessage(String message) {
this.message = message;
return this;
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.expression;
import org.dromara.warm.flow.strategy.ListenerStrategy;
import org.dromara.warm.flow.expression.SpelHelper;
import java.util.Map;
/**
* spel监听器表达式 #{@user.eval()}
*
* @author warm
*/
public class ListenerStrategySpel implements ListenerStrategy {
@Override
public String getType() {
return "#";
}
@Override
public Boolean eval(String expression, Map<String, Object> variable) {
SpelHelper.parseExpression(expression, variable);
// 恒返回true说明匹配上监听器表达式扩展策略也一定要返回true
return true;
}
}

View File

@ -0,0 +1,53 @@
package org.dromara.warm.flow.expression;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.MethodExecutor;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.spel.support.DataBindingMethodResolver;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 安全的方法解析器限制可调用的方法
*
* @author warm
* @since 2026/3/31
*/
public class SafeMethodResolver implements MethodResolver {
private static final Set<String> DANGEROUS_METHODS = new HashSet<>(Arrays.asList(
"getRuntime",
"exec",
"forName",
"loadClass",
"getClassLoader",
"setAccessible",
"newInstance",
"invoke",
"getField",
"getDeclaredField",
"getMethod",
"getDeclaredMethod"
));
@Nullable
@Override
public MethodExecutor resolve(@NonNull EvaluationContext context, @NonNull Object targetObject
, @NonNull String name, @NonNull List<TypeDescriptor> argumentTypes) throws AccessException {
if (DANGEROUS_METHODS.contains(name)) {
throw new AccessException("不允许调用方法:" + name);
}
// 委托给默认的方法解析器
return DataBindingMethodResolver.forInstanceMethodInvocation()
.resolve(context, targetObject, name, argumentTypes);
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.expression;
import cn.hutool.core.util.StrUtil;
import org.dromara.warm.flow.exception.FlowException;
import org.springframework.expression.TypeLocator;
import org.springframework.expression.spel.support.StandardTypeLocator;
import org.springframework.lang.NonNull;
import java.util.HashSet;
import java.util.Set;
/**
* 自定义类型定位器用于限制访问的类
*
* @author warm
* @since 2026/3/31
*/
// ... existing code ...
/**
* 自定义类型定位器用于限制访问的类
*
* @author warm
* @since 2026/3/31
*/
public class SafeTypeLocator implements TypeLocator {
private final StandardTypeLocator defaultTypeLocator;
private final Set<String> allowedClasses;
private static final Set<String> DANGEROUS_CLASSES;
static {
// 初始化危险类黑名单使用全限定名便于前缀匹配
DANGEROUS_CLASSES = new HashSet<>();
DANGEROUS_CLASSES.add("java.lang.Runtime");
DANGEROUS_CLASSES.add("java.lang.ProcessBuilder");
DANGEROUS_CLASSES.add("java.lang.System");
DANGEROUS_CLASSES.add("java.lang.Class");
DANGEROUS_CLASSES.add("java.lang.reflect");
DANGEROUS_CLASSES.add("sun.misc.Unsafe");
DANGEROUS_CLASSES.add("sun.reflect");
}
{
allowedClasses = new HashSet<>();
allowedClasses.add("java.lang.String");
allowedClasses.add("java.lang.Integer");
allowedClasses.add("java.lang.Long");
allowedClasses.add("java.lang.Double");
allowedClasses.add("java.lang.Float");
allowedClasses.add("java.lang.Boolean");
allowedClasses.add("java.lang.Byte");
allowedClasses.add("java.lang.Short");
allowedClasses.add("java.lang.Character");
allowedClasses.add("java.lang.Object");
allowedClasses.add("java.util.List");
allowedClasses.add("java.util.ArrayList");
allowedClasses.add("java.util.Map");
allowedClasses.add("java.util.HashMap");
allowedClasses.add("java.util.Set");
allowedClasses.add("java.util.HashSet");
allowedClasses.add("java.util.Date");
}
public SafeTypeLocator() {
this.defaultTypeLocator = new StandardTypeLocator();
}
@Override
public Class<?> findType(@NonNull String typeName) {
if (StrUtil.isEmpty(typeName)) {
throw new FlowException("Type name cannot be null or empty");
}
// 检查是否为危险类黑名单
if (isDangerousClass(typeName)) {
throw new FlowException("Forbidden dangerous class: " + typeName);
}
// 检查是否在白名单内
if (!allowedClasses.contains(typeName)) {
throw new FlowException("Forbidden class not in whitelist: " + typeName);
}
return defaultTypeLocator.findType(typeName);
}
private boolean isDangerousClass(String typeName) {
for (String dangerousClass : DANGEROUS_CLASSES) {
if (typeName.equals(dangerousClass) || typeName.startsWith(dangerousClass + ".")) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.expression;
import org.dromara.warm.flow.exception.FlowException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.NonNull;
import java.util.Map;
/**
* 条件表达式 spel
*
* @author warm,battcn
*/
@Configuration
public class SpelHelper implements ApplicationContextAware {
private static final ExpressionParser PARSER = new SpelExpressionParser();
private final static ParserContext PARSER_CONTEXT = new TemplateParserContext();
private static ApplicationContext applicationContext;
/**
* bean解析器 用于处理 spel 表达式中对 bean 的调用
*/
private static BeanResolver beanResolver = null;
public static BeanResolver beanResolver() {
if (beanResolver == null) {
beanResolver = new BeanFactoryResolver(getBeanFactory());
}
return beanResolver;
}
/**
* @param expression expression
* @return Object
*/
public static Object parseExpression(String expression, Map<String, Object> variable) {
// 创建带沙箱保护的评估上下文
StandardEvaluationContext context = new StandardEvaluationContext();
// 设置 Bean 解析器支持 @beanName 引用
context.setBeanResolver(beanResolver());
// 设置变量
context.setVariables(variable);
// 设置沙箱限制类型访问白名单机制
context.setTypeLocator(new SafeTypeLocator());
// 设置沙箱限制方法调用
context.addMethodResolver(new SafeMethodResolver());
return PARSER.parseExpression(expression, PARSER_CONTEXT).getValue(context, Object.class);
}
/**
* 执行 spel 表达式并按布尔结果返回
*
* @param expression 表达式
* @param variable 流程变量
* @return 布尔结果
*/
public static boolean evalBool(String expression, Map<String, Object> variable) {
return Boolean.TRUE.equals(parseExpression(expression, variable));
}
@Override
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
if (SpelHelper.applicationContext == null) {
SpelHelper.applicationContext = applicationContext;
}
}
public static ListableBeanFactory getBeanFactory() {
if (null == applicationContext) {
throw new FlowException("No ConfigurableListableBeanFactory or ApplicationContext injected, maybe not in the Spring environment?");
} else {
return applicationContext;
}
}
public static String replace(String expression, Map<String, Object> variable) {
expression = expression.replace("$", "#");
for (Map.Entry<String, Object> entry : variable.entrySet()) {
if (expression.contains(entry.getKey())) {
expression = expression.replace(entry.getKey(), "#" + entry.getKey());
}
}
return expression;
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.handler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.entity.RootEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Date;
import java.util.Objects;
/**
* 数据填充handler以下三个接口按照实际情况实现
*
* @author warm
* @since 2023/4/1 15:37
*/
public interface DataFillHandler {
Logger logger = LoggerFactory.getLogger(DataFillHandler.class);
/**
* id填充引擎在入库前就需要主键做行间互引如历史任务的 instanceId/taskId
* 必须此处生成而非留给 MyBatis-Plus insert 时补与全局 IdentifierGenerator 同源
* insert 时对非空 id 不会重复生成
*
* @param object object
*/
default void idFill(Object object) {
if (object instanceof RootEntity entity && ObjectUtil.isNull(entity.getId())) {
IdentifierGenerator generator = SpringUtils.getBeanOrNull(IdentifierGenerator.class);
entity.setId(generator != null ? generator.nextId(entity).longValue() : IdWorker.getId());
}
}
/**
* 新增填充
*
* @param object object
*/
default void insertFill(Object object) {
RootEntity entity = (RootEntity) object;
if (ObjectUtil.isNull(entity)) {
logger.warn("Insert operation failed - Reason: Entity is null after casting");
return;
}
entity.setCreateTime(ObjectUtil.isNotNull(entity.getCreateTime()) ? entity.getCreateTime() : new Date());
entity.setUpdateTime(ObjectUtil.isNotNull(entity.getUpdateTime()) ? entity.getUpdateTime() : new Date());
PermissionHandler permissionHandler = FlowEngine.permissionHandler();
String handler = null;
if (permissionHandler != null) {
try {
handler = permissionHandler.getHandler();
} catch (Exception ignored) {
}
}
entity.setCreateBy(StrUtil.isNotEmpty(handler) ? handler : entity.getCreateBy());
entity.setUpdateBy(StrUtil.isNotEmpty(handler) ? handler : entity.getUpdateBy());
}
/**
* 设置更新常用参数
*
* @param object object
*/
default void updateFill(Object object) {
RootEntity entity = (RootEntity) object;
if (ObjectUtil.isNull(entity)) {
logger.warn("Insert operation failed - Reason: Entity is null after casting");
return;
}
entity.setUpdateTime(ObjectUtil.isNotNull(entity.getUpdateTime()) ? entity.getUpdateTime() : new Date());
PermissionHandler permissionHandler = FlowEngine.permissionHandler();
String handler = null;
if (permissionHandler != null) {
try {
handler = permissionHandler.getHandler();
} catch (Exception ignored) {
}
}
entity.setUpdateBy(StrUtil.isNotEmpty(handler) ? handler : entity.getUpdateBy());
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.handler;
import org.dromara.warm.flow.dto.FlowParams;
import java.util.List;
/**
* 办理人权限处理器
* 用户获取工作流中用到的permissionFlag和handler
* permissionFlag: 办理人权限标识比如用户角色部门等用于校验是否有权限办理任务
* handler: 当前办理人唯一标识就是确定唯一用的如用户id通常用来入库记录流程实例创建人办理人
*
* @author shadow
*/
public interface PermissionHandler {
/**
* 办理人权限标识比如用户角色部门等用于校验是否有权限办理任务
* 后续在{@link FlowParams#getPermissionFlag} 中获取
* 返回当前用户权限集合
*
*/
List<String> permissions();
/**
* 获取当前办理人就是确定唯一用的如用户id通常用来入库记录流程实例创建人办理人
* 后续在{@link FlowParams#getHandler()} 中获取
*
* @return 当前办理人
*/
String getHandler();
/**
* 转换办理人比如设计器中预设了能办理的人如果其中包含角色或者部门id等可以通过此接口进行转换成用户id
*/
default List<String> convertPermissions(List<String> permissions) {
return permissions;
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.json;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import org.dromara.warm.flow.exception.FlowException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.json.JsonMapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Jackson 3map和json字符串转换工具类
*
* @author warm
*/
public final class JsonUtil {
private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
private static final JsonMapper JSON_MAPPER = JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.build();
private JsonUtil() {
}
/**
* 将字符串转为map
*
* @param jsonStr json字符串
* @return map
*/
public static Map<String, Object> strToMap(String jsonStr) {
if (StrUtil.isNotEmpty(jsonStr)) {
try {
return JSON_MAPPER.readValue(jsonStr,
JSON_MAPPER.getTypeFactory().constructMapType(Map.class, String.class, Object.class));
} catch (Exception e) {
log.error("json转换异常", e);
throw new FlowException("json转换异常");
}
}
return new HashMap<>();
}
/**
* 将字符串转为bean
*
* @param jsonStr json字符串
* @param clazz Class<T>
* @return T
*/
public static <T> T strToBean(String jsonStr, Class<T> clazz) {
if (StrUtil.isNotEmpty(jsonStr)) {
try {
return JSON_MAPPER.readValue(jsonStr, clazz);
} catch (Exception e) {
log.error("json转换异常", e);
throw new FlowException("json转换异常");
}
}
return null;
}
/**
* 将字符串转为集合
*
* @param jsonStr json字符串
* @return List<T>
*/
public static <T> List<T> strToList(String jsonStr) {
if (StrUtil.isNotEmpty(jsonStr)) {
try {
return JSON_MAPPER.readValue(jsonStr, new TypeReference<List<T>>() {
});
} catch (Exception e) {
log.error("json转换异常", e);
throw new FlowException("json转换异常");
}
}
return null;
}
/**
* 将对象转为字符串
*
* @param variable object
* @return json字符串
*/
public static String objToStr(Object variable) {
if (ObjectUtil.isNotNull(variable)) {
try {
return JSON_MAPPER.writeValueAsString(variable);
} catch (Exception e) {
log.error("Map转换异常", e);
throw new FlowException("Map转换异常");
}
}
return null;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.listener;
import java.io.Serializable;
/**
* 全局监听器: 整个系统只有一个任务开始分派完成和创建时期执行
*
* @author warm
* @since 2024/11/17
*/
public interface GlobalListener extends Serializable {
/**
* 开始监听器任务开始办理时执行
*
* @param listenerVariable 监听器变量
*/
default void start(ListenerVariable listenerVariable) {
}
/**
* 分派监听器动态修改代办任务信息
*
* @param listenerVariable 监听器变量
*/
default void assignment(ListenerVariable listenerVariable) {
}
/**
* 完成监听器当前任务完成后执行
*
* @param listenerVariable 监听器变量
*/
default void finish(ListenerVariable listenerVariable) {
}
/**
* 创建监听器任务创建时执行
*
* @param listenerVariable 监听器变量
*/
default void create(ListenerVariable listenerVariable) {
}
default void notify(String type, ListenerVariable listenerVariable) {
switch (type) {
case Listener.LISTENER_START:
start(listenerVariable);
break;
case Listener.LISTENER_ASSIGNMENT:
assignment(listenerVariable);
break;
case Listener.LISTENER_FINISH:
finish(listenerVariable);
break;
case Listener.LISTENER_CREATE:
create(listenerVariable);
break;
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.listener;
import java.io.Serializable;
/**
* 监听器
*
* @author warm
*/
public interface Listener extends Serializable {
/**
* 开始监听器任务开始办理时执行
*/
String LISTENER_START = "start";
/**
* 分派监听器动态修改代办任务信息
*/
String LISTENER_ASSIGNMENT = "assignment";
/**
* 完成监听器当前任务完成后执行
*/
String LISTENER_FINISH = "finish";
/**
* 创建监听器任务创建时执行
*/
String LISTENER_CREATE = "create";
/**
* 表单数据加载监听器1.3.0 内置表单使用
*/
String LISTENER_FORM_LOAD = "formLoad";
/**
* 通知
*
* @param variable variable
*/
void notify(ListenerVariable variable);
}

View File

@ -0,0 +1,212 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.listener;
import org.dromara.warm.flow.dto.FlowParams;
import org.dromara.warm.flow.entity.FlowDefinition;
import org.dromara.warm.flow.entity.FlowInstance;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowTask;
import java.util.List;
import java.util.Map;
/**
* 监听器变量
*
* @author warm
*/
public class ListenerVariable {
/**
* 流程定义
*/
private FlowDefinition definition;
/**
* 流程实例
*/
private FlowInstance instance;
/**
* 监听器对应的节点
*/
private FlowNode node;
/**
* 当前任务
*/
private FlowTask task;
/**
* 下一次执行的节点集合
*/
private List<FlowNode> nextNodes;
/**
* 新创建任务集合
*/
private List<FlowTask> nextTasks;
/**
* 流程变量
*/
private Map<String, Object> variable;
/**
* 工作流内置参数
*/
private FlowParams flowParams;
public ListenerVariable() {
}
public ListenerVariable(FlowDefinition definition, FlowInstance instance, Map<String, Object> variable) {
this.definition = definition;
this.instance = instance;
this.variable = variable;
}
public ListenerVariable(FlowDefinition definition, FlowInstance instance, FlowNode node, Map<String, Object> variable) {
this.definition = definition;
this.instance = instance;
this.node = node;
this.variable = variable;
}
public ListenerVariable(FlowDefinition definition, FlowInstance instance, Map<String, Object> variable, FlowTask task) {
this.definition = definition;
this.instance = instance;
this.variable = variable;
this.task = task;
}
public ListenerVariable(FlowDefinition definition, FlowInstance instance, FlowNode node, Map<String, Object> variable, FlowTask task) {
this.definition = definition;
this.instance = instance;
this.node = node;
this.variable = variable;
this.task = task;
}
public ListenerVariable(FlowDefinition definition, FlowInstance instance, FlowNode node, Map<String, Object> variable, FlowTask task, List<FlowNode> nextNodes) {
this.definition = definition;
this.instance = instance;
this.node = node;
this.variable = variable;
this.task = task;
this.nextNodes = nextNodes;
}
public ListenerVariable(FlowDefinition definition, FlowInstance instance, FlowNode node, Map<String, Object> variable, FlowTask task
, List<FlowNode> nextNodes, List<FlowTask> nextTasks) {
this.definition = definition;
this.instance = instance;
this.node = node;
this.variable = variable;
this.task = task;
this.nextNodes = nextNodes;
this.nextTasks = nextTasks;
}
public FlowDefinition getDefinition() {
return definition;
}
public ListenerVariable setDefinition(FlowDefinition definition) {
this.definition = definition;
return this;
}
public FlowInstance getInstance() {
return instance;
}
public ListenerVariable setInstance(FlowInstance instance) {
this.instance = instance;
return this;
}
public FlowNode getNode() {
return node;
}
public ListenerVariable setNode(FlowNode node) {
this.node = node;
return this;
}
public FlowTask getTask() {
return task;
}
public ListenerVariable setTask(FlowTask task) {
this.task = task;
return this;
}
public List<FlowNode> getNextNodes() {
return nextNodes;
}
public ListenerVariable setNextNodes(List<FlowNode> nextNodes) {
this.nextNodes = nextNodes;
return this;
}
public List<FlowTask> getNextTasks() {
return nextTasks;
}
public ListenerVariable setNextTasks(List<FlowTask> nextTasks) {
this.nextTasks = nextTasks;
return this;
}
public Map<String, Object> getVariable() {
return variable;
}
public ListenerVariable setVariable(Map<String, Object> variable) {
this.variable = variable;
return this;
}
public FlowParams getFlowParams() {
return flowParams;
}
public ListenerVariable setFlowParams(FlowParams flowParams) {
this.flowParams = flowParams;
return this;
}
@Override
public String toString() {
return "ListenerVariable{" +
"definition=" + definition +
", instance=" + instance +
", node=" + node +
", task=" + task +
", nextNodes=" + nextNodes +
", nextTasks=" + nextTasks +
", variable=" + variable +
", flowParams=" + flowParams +
'}';
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.listener;
import lombok.Data;
/**
* @author warm
*/
@Data
public class ValueHolder {
/**
* 路径
*/
private String path;
/**
* 监听器
*/
private Listener listener;
/**
* 参数
*/
private String params;
}

View File

@ -0,0 +1,55 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.warm.flow.entity.FlowDefinition;
import java.util.List;
/**
* 流程定义Mapper接口
*
* @author warm
* @since 2023-03-29
*/
public interface FlowDefinitionMapper extends WarmMapper<FlowDefinition> {
/**
* 根据编码批量查询
*
* @param flowCodeList 流程编码集
* @return 查询结果
*/
default List<FlowDefinition> queryByCodeList(List<String> flowCodeList) {
LambdaQueryWrapper<FlowDefinition> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(FlowDefinition::getFlowCode, flowCodeList);
return selectList(queryWrapper);
}
/**
* 根据ID批量修改发布状态
*
* @param ids ids
* @param publishStatus 发布状态(9=已失效0=未发布1=已发布)
* @see org.dromara.warm.flow.enums.PublishStatus
*/
default void updatePublishStatus(List<Long> ids, Integer publishStatus) {
LambdaQueryWrapper<FlowDefinition> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(FlowDefinition::getId, ids);
update(new FlowDefinition().setIsPublish(publishStatus), queryWrapper);
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.warm.flow.entity.FlowHisTask;
import org.dromara.warm.flow.enums.SkipType;
import java.util.Arrays;
import java.util.List;
/**
* 历史任务记录Mapper接口
*
* @author warm
* @since 2023-03-29
*/
public interface FlowHisTaskMapper extends WarmMapper<FlowHisTask> {
/**
* 根据instanceId获取未退回的历史记录
*/
default List<FlowHisTask> getNoReject(Long instanceId) {
LambdaQueryWrapper<FlowHisTask> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowHisTask::getInstanceId, instanceId)
.eq(FlowHisTask::getSkipType, SkipType.PASS.getKey())
.orderByDesc(FlowHisTask::getCreateTime);
return selectList(queryWrapper);
}
/**
* 根据instanceId和流程编码获取未退回的历史记录
*/
default List<FlowHisTask> getByInsAndNodeCodes(Long instanceId, List<String> nodeCodes) {
LambdaQueryWrapper<FlowHisTask> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowHisTask::getInstanceId, instanceId)
.in(CollUtil.isNotEmpty(nodeCodes), FlowHisTask::getNodeCode, nodeCodes)
.orderByDesc(FlowHisTask::getCreateTime);
return selectList(queryWrapper);
}
/**
* 根据instanceIds删除
*
* @param instanceIds 主键
* @return 结果
*/
default int deleteByInsIds(List<Long> instanceIds) {
return delete(new LambdaQueryWrapper<FlowHisTask>().in(FlowHisTask::getInstanceId, instanceIds));
}
/**
* 根据任务id和协作类型查询
*/
default List<FlowHisTask> listByTaskIdAndCooperateTypes(Long taskId, Integer[] cooperateTypes) {
LambdaQueryWrapper<FlowHisTask> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowHisTask::getTaskId, taskId).in(FlowHisTask::getCooperateType, Arrays.asList(cooperateTypes));
return selectList(queryWrapper);
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.warm.flow.entity.FlowInstance;
import java.util.List;
/**
* 流程实例Mapper接口
*
* @author warm
* @since 2023-03-29
*/
public interface FlowInstanceMapper extends WarmMapper<FlowInstance> {
/**
* 根据流程定义ID,查询流程实例集合
*
* @param defIds 流程定义ID集合
* @return 流程实例集合
*/
default List<FlowInstance> getByDefIds(List<Long> defIds) {
LambdaQueryWrapper<FlowInstance> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(FlowInstance::getDefinitionId, defIds);
return selectList(queryWrapper);
}
}

View File

@ -0,0 +1,50 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.warm.flow.entity.FlowNode;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* 流程节点Mapper接口
*
* @author warm
* @since 2023-03-29
*/
public interface FlowNodeMapper extends WarmMapper<FlowNode> {
default List<FlowNode> getByNodeCodes(List<String> nodeCodes, Long definitionId) {
LambdaQueryWrapper<FlowNode> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(CollUtil.isNotEmpty(nodeCodes), FlowNode::getNodeCode, nodeCodes)
.eq(FlowNode::getDefinitionId, definitionId);
return selectList(queryWrapper);
}
/**
* 批量删除流程节点
*
* @param defIds 需要删除的数据主键集合
* @return 结果
*/
default int deleteNodeByDefIds(Collection<? extends Serializable> defIds) {
return delete(new LambdaQueryWrapper<FlowNode>().in(FlowNode::getDefinitionId, defIds));
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.warm.flow.entity.FlowSkip;
import java.io.Serializable;
import java.util.Collection;
/**
* 节点跳转关联Mapper接口
*
* @author warm
* @since 2023-03-29
*/
public interface FlowSkipMapper extends WarmMapper<FlowSkip> {
/**
* 批量删除节点跳转关联
*
* @param defIds 需要删除的数据主键集合
* @return 结果
*/
default int deleteSkipByDefIds(Collection<? extends Serializable> defIds) {
return delete(new LambdaQueryWrapper<FlowSkip>().in(FlowSkip::getDefinitionId, defIds));
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.warm.flow.entity.FlowTask;
import java.util.List;
/**
* 待办任务Mapper接口
*
* @author warm
* @since 2023-03-29
*/
public interface FlowTaskMapper extends WarmMapper<FlowTask> {
/**
* 根据instanceIds删除
*
* @param instanceIds 主键
* @return 结果
*/
default int deleteByInsIds(List<Long> instanceIds) {
return delete(new LambdaQueryWrapper<FlowTask>().in(FlowTask::getInstanceId, instanceIds));
}
default List<FlowTask> getByInsIdAndNodeCodes(Long instanceId, List<String> nodeCodes) {
LambdaQueryWrapper<FlowTask> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(FlowTask::getInstanceId, instanceId);
queryWrapper.in(FlowTask::getNodeCode, nodeCodes);
return selectList(queryWrapper);
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.warm.flow.entity.FlowUser;
import java.util.Arrays;
import java.util.List;
/**
* 流程用户Mapper接口
*
* @author warm
* @since 2023-03-29
*/
public interface FlowUserMapper extends WarmMapper<FlowUser> {
/**
* 根据taskId删除
*
* @param taskIdList 待办任务主键集合
* @return 结果
*/
default int deleteByTaskIds(List<Long> taskIdList) {
return delete(new LambdaQueryWrapper<FlowUser>().in(FlowUser::getAssociated, taskIdList));
}
/**
* 根据(待办任务实例历史表节点等)id查询权限人或者处理人
*
* @param associatedList (待办任务实例历史表节点等)id集合
* @param types 用户表类型
* @return 查询结果
*/
default List<FlowUser> listByAssociatedAndTypes(List<Long> associatedList, String[] types) {
LambdaQueryWrapper<FlowUser> queryWrapper = new LambdaQueryWrapper<>();
if (CollUtil.isNotEmpty(associatedList)) {
if (associatedList.size() == 1) {
queryWrapper.eq(FlowUser::getAssociated, associatedList.get(0));
} else {
queryWrapper.in(FlowUser::getAssociated, associatedList);
}
}
queryWrapper.in(ArrayUtil.isNotEmpty(types), FlowUser::getType, Arrays.asList(types));
return selectList(queryWrapper);
}
/**
* 根据办理人查询
*
* @param associated 待办任务id
* @param processedBys 办理人id集合
* @param types 用户表类型
* @return 查询结果
*/
default List<FlowUser> listByProcessedBys(Long associated, List<String> processedBys, String[] types) {
LambdaQueryWrapper<FlowUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ObjectUtil.isNotNull(associated), FlowUser::getAssociated, associated);
if (CollUtil.isNotEmpty(processedBys)) {
if (processedBys.size() == 1) {
queryWrapper.eq(FlowUser::getProcessedBy, processedBys.get(0));
} else {
queryWrapper.in(FlowUser::getProcessedBy, processedBys);
}
}
queryWrapper.in(ArrayUtil.isNotEmpty(types), FlowUser::getType, types);
return selectList(queryWrapper);
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.mapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.dromara.warm.flow.entity.RootEntity;
import java.util.List;
/**
* BaseMapper接口含原 Dao 层通用查询default 方法由 MyBatis 直接调用
*
* @author warm
* @since 2023-03-17
*/
public interface WarmMapper<T extends RootEntity> extends BaseMapper<T> {
/**
* 实体条件查询
*
* @param entity 条件实体
*/
default List<T> selectList(T entity) {
return selectList(new QueryWrapper<>(entity));
}
/**
* 实体条件计数接口类型 lambda 需显式设置实体类
*/
@SuppressWarnings("unchecked")
default long selectCount(T entity) {
LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>(entity);
queryWrapper.setEntityClass((Class<T>) entity.getClass());
return selectCount(queryWrapper);
}
/**
* 按主键更新接口类型 lambda 需显式设置实体类
*/
@SuppressWarnings("unchecked")
default int update(T entity) {
LambdaQueryWrapper<T> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.setEntityClass((Class<T>) entity.getClass());
queryWrapper.eq(RootEntity::getId, entity.getId());
return update(entity, queryWrapper);
}
/**
* 实体条件删除
*/
default int delete(T entity) {
return delete(new LambdaQueryWrapper<>(entity));
}
}

View File

@ -0,0 +1,173 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.json.JsonUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.dto.DefJson;
import org.dromara.warm.flow.dto.NodeJson;
import org.dromara.warm.flow.dto.PathWayData;
import org.dromara.warm.flow.dto.SkipJson;
import org.dromara.warm.flow.entity.FlowInstance;
import org.dromara.warm.flow.entity.FlowSkip;
import org.dromara.warm.flow.enums.ChartStatus;
import org.dromara.warm.flow.enums.NodeType;
import org.dromara.warm.flow.enums.SkipType;
import org.dromara.common.core.utils.StreamUtils;
import org.springframework.stereotype.Service;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 流程图绘制Service业务层处理
*
* @author warm
* @since 2024-12-30
*/
@Service
public class ChartService {
public String startMetadata(PathWayData pathWayData) {
DefJson defJson = FlowEngine.defService().queryDesign(pathWayData.getDefId());
List<NodeJson> nodeList = defJson.getNodeList();
Map<String, NodeJson> nodeMap = StreamUtils.toMap(nodeList, NodeJson::getNodeCode
, node -> node.setStatus(ChartStatus.NOT_DONE.getKey()));
Map<String, SkipJson> skipMap = nodeList.stream().map(NodeJson::getSkipList).flatMap(List::stream)
.collect(Collectors.toMap(this::getSkipKey, skip -> skip.setStatus(ChartStatus.NOT_DONE.getKey())));
pathWayData.getPathWayNodes().forEach(node -> nodeMap.get(node.getNodeCode()).setStatus(ChartStatus.DONE.getKey()));
pathWayData.getPathWaySkips().forEach(skip -> skipMap.get(getSkipKey(skip)).setStatus(ChartStatus.DONE.getKey()));
pathWayData.getTargetNodes().forEach(node -> nodeMap.get(node.getNodeCode()).setStatus(
NodeType.isEnd(node.getNodeType()) ? ChartStatus.DONE.getKey() : ChartStatus.TO_DO.getKey()
));
return JsonUtil.objToStr(defJson);
}
public String skipMetadata(PathWayData pathWayData) {
FlowInstance instance = FlowEngine.insService().getById(pathWayData.getInsId());
DefJson defJson = JsonUtil.strToBean(instance.getDefJson(), DefJson.class);
List<NodeJson> nodeList = defJson.getNodeList();
List<SkipJson> skipList = defJson.getNodeList().stream().map(NodeJson::getSkipList)
.filter(Objects::nonNull).flatMap(List::stream).collect(Collectors.toList());
Map<String, NodeJson> nodeMap = StreamUtils.toMap(nodeList, NodeJson::getNodeCode, node -> node);
Map<String, SkipJson> skipMap = StreamUtils.toMap(skipList, this::getSkipKey, skip -> skip);
pathWayData.getPathWayNodes().forEach(node -> {
NodeJson nodeJson = nodeMap.get(node.getNodeCode());
if (SkipType.isPass(pathWayData.getSkipType())) {
nodeJson.setStatus(ChartStatus.DONE.getKey());
} else if (SkipType.isReject(pathWayData.getSkipType())) {
nodeJson.setStatus(ChartStatus.NOT_DONE.getKey());
}
});
pathWayData.getPathWaySkips().forEach(skip -> {
SkipJson skipJson = skipMap.get(getSkipKey(skip));
if (SkipType.isPass(pathWayData.getSkipType())) {
skipJson.setStatus(ChartStatus.DONE.getKey());
} else if (SkipType.isReject(pathWayData.getSkipType())) {
skipJson.setStatus(ChartStatus.NOT_DONE.getKey());
}
});
pathWayData.getTargetNodes().forEach(node -> {
NodeJson nodeJson = nodeMap.get(node.getNodeCode());
if (NodeType.isEnd(node.getNodeType())) {
nodeJson.setStatus(ChartStatus.DONE.getKey());
} else {
nodeJson.setStatus(ChartStatus.TO_DO.getKey());
}
});
if (SkipType.isReject(pathWayData.getSkipType())) {
Map<String, List<SkipJson>> skipNextMap = StreamUtils.groupByKey(
StreamUtils.filter(skipList, skip -> !SkipType.isReject(skip.getSkipType())), SkipJson::getNowNodeCode);
pathWayData.getTargetNodes().forEach(node -> rejectReset(node.getNodeCode(), skipNextMap, nodeMap));
}
pathWayData.getTargetNodes().forEach(node -> {
if (NodeType.isEnd(node.getNodeType())) {
nodeList.forEach(nodeJson -> {
if (ChartStatus.isToDo(nodeJson.getStatus())) {
nodeJson.setStatus(ChartStatus.NOT_DONE.getKey());
}
});
}
});
return JsonUtil.objToStr(defJson);
}
public List<String> getChartRgb(String modelValue) {
List<String> chartStatusColor = new ArrayList<>();
Color done = ChartStatus.getDone(modelValue);
chartStatusColor.add(done.getRed() + "," + done.getGreen() + "," + done.getBlue());
Color toDo = ChartStatus.getToDo(modelValue);
chartStatusColor.add(toDo.getRed() + "," + toDo.getGreen() + "," + toDo.getBlue());
Color notDone = ChartStatus.getNotDone(modelValue);
chartStatusColor.add(notDone.getRed() + "," + notDone.getGreen() + "," + notDone.getBlue());
return chartStatusColor;
}
private String getSkipKey(SkipJson skip) {
return ArrayUtil.join(new String[]{
skip.getNowNodeCode(),
skip.getSkipType(),
skip.getSkipCondition(),
skip.getNextNodeCode()}, ":");
}
private String getSkipKey(FlowSkip skip) {
return ArrayUtil.join(new String[]{
skip.getNowNodeCode(),
skip.getSkipType(),
skip.getSkipCondition(),
skip.getNextNodeCode()}, ":");
}
private void rejectReset(String nodeCode, Map<String, List<SkipJson>> skipNextMap, Map<String, NodeJson> nodeMap) {
List<SkipJson> oneNextSkips = skipNextMap.get(nodeCode);
if (CollUtil.isNotEmpty(oneNextSkips)) {
oneNextSkips.forEach(oneNextSkip -> {
if (ObjectUtil.isNotNull(oneNextSkip) && !ChartStatus.isNotDone(oneNextSkip.getStatus())) {
oneNextSkip.setStatus(ChartStatus.NOT_DONE.getKey());
NodeJson nodeJson = nodeMap.get(oneNextSkip.getNextNodeCode());
if (ObjectUtil.isNotNull(nodeJson) && !ChartStatus.isNotDone(nodeJson.getStatus())) {
nodeJson.setStatus(ChartStatus.NOT_DONE.getKey());
rejectReset(nodeJson.getNodeCode(), skipNextMap, nodeMap);
}
}
});
}
}
}

View File

@ -0,0 +1,361 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.json.JsonUtil;
import org.dromara.warm.flow.entity.*;
import org.dromara.common.core.utils.StreamUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.constant.ExceptionCons;
import org.dromara.warm.flow.dto.DefJson;
import org.dromara.warm.flow.dto.FlowCombine;
import org.dromara.warm.flow.entity.FlowDefinition;
import org.dromara.warm.flow.entity.FlowInstance;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowSkip;
import org.dromara.warm.flow.enums.ActivityStatus;
import org.dromara.warm.flow.enums.PublishStatus;
import org.dromara.warm.flow.exception.FlowException;
import org.dromara.warm.flow.service.WarmServiceImpl;
import org.dromara.warm.flow.utils.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.stereotype.Service;
import org.dromara.warm.flow.mapper.FlowDefinitionMapper;
import java.util.List;
/**
* 流程定义Service业务层处理
*
* @author warm
* @since 2023-03-29
*/
@Slf4j
@Service
public class DefService extends WarmServiceImpl<FlowDefinition> {
public FlowDefinition importIs(InputStream is) {
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append(System.lineSeparator());
}
} catch (IOException e) {
throw new FlowException(ExceptionCons.READ_IS_ERROR);
}
return importJson(stringBuilder.toString());
}
public FlowDefinition importJson(String defJson) {
return importDef(JsonUtil.strToBean(defJson, DefJson.class));
}
public FlowDefinition importDef(DefJson defJson) {
FlowDefinition definition = DefJson.copyDef(defJson);
FlowCombine flowCombine = FlowConfigUtil.structureFlow(definition);
return insertFlow(flowCombine.getDefinition(), flowCombine.getAllNodes(), flowCombine.getAllSkips());
}
public FlowDefinition insertFlow(FlowDefinition definition, List<FlowNode> nodeList, List<FlowSkip> skipList) {
definition.setVersion(getNewVersion(definition));
for (FlowNode node : nodeList) {
node.setVersion(definition.getVersion());
}
FlowEngine.defService().save(definition);
FlowEngine.nodeService().saveBatch(nodeList);
FlowEngine.skipService().saveBatch(skipList);
return definition;
}
public boolean checkAndSave(FlowDefinition definition) {
return save(definition.setVersion(getNewVersion(definition)));
}
public void saveDef(DefJson defJson, boolean onlyNodeSkip) {
if (ObjectUtil.isNull(defJson)) {
return;
}
FlowCombine flowCombine = DefJson.copyCombine(defJson);
FlowDefinition definition = flowCombine.getDefinition();
Long id = definition.getId();
// 如果是新增的流程定义
if (ObjectUtil.isNull(id)) {
definition.setVersion(getNewVersion(definition));
FlowEngine.dataFillHandler().idFill(definition);
}
// 校验流程定义合法性
checkFlowLegal(flowCombine);
// 如果是新增的流程定义
if (ObjectUtil.isNull(id)) {
FlowEngine.defService().save(definition);
} else {
if (!onlyNodeSkip) {
FlowEngine.defService().updateById(definition);
}
// 删除所有节点和连线
FlowEngine.nodeService().remove(new FlowNode().setDefinitionId(id));
FlowEngine.skipService().remove(new FlowSkip().setDefinitionId(id));
}
// 保存流程节点和跳转
List<FlowNode> allNodes = flowCombine.getAllNodes();
allNodes.forEach(node -> {
if(StrUtil.isEmpty(node.getNodeRatio())) {
node.setNodeRatio("0");
}
});
// 所有的流程连线
List<FlowSkip> allSkips = flowCombine.getAllSkips();
// 保存节点流程连线权利人
FlowEngine.nodeService().saveBatch(allNodes);
FlowEngine.skipService().saveBatch(allSkips);
}
public String exportJson(Long id) {
return JsonUtil.objToStr(queryDesign(id).setIsPublish(null));
}
public FlowDefinition getAllDataDefinition(Long id) {
FlowDefinition definition = getMapper().selectById(id);
List<FlowNode> nodeList = FlowEngine.nodeService().getByDefId(id);
definition.setNodeList(nodeList);
List<FlowSkip> skips = FlowEngine.skipService().getByDefId(id);
Map<String, List<FlowSkip>> flowSkipMap = skips.stream()
.collect(Collectors.groupingBy(FlowSkip::getNowNodeCode));
nodeList.forEach(flowNode -> flowNode.setSkipList(flowSkipMap.get(flowNode.getNodeCode())));
return definition;
}
public FlowCombine getFlowCombine(Long id) {
return getFlowCombine(getMapper().selectById(id));
}
public FlowCombine getFlowCombineNoDef(Long id) {
FlowCombine flowCombine = new FlowCombine();
flowCombine.setAllNodes(FlowEngine.nodeService().getByDefId(id));
flowCombine.setAllSkips(FlowEngine.skipService().getByDefId(id));
return flowCombine;
}
public FlowCombine getFlowCombine(FlowDefinition definition) {
FlowCombine flowCombine = getFlowCombineNoDef(definition.getId());
flowCombine.setDefinition(definition);
return flowCombine;
}
public DefJson queryDesign(Long id) {
return DefJson.copyDef(getAllDataDefinition(id));
}
public List<FlowDefinition> queryByCodeList(List<String> flowCodeList) {
return getMapper().queryByCodeList(flowCodeList);
}
public void updatePublishStatus(List<Long> ids, Integer publishStatus) {
getMapper().updatePublishStatus(ids, publishStatus);
}
/**
* 删除流程定义
*
* @param ids 流程定义id
*/
public boolean removeDef(List<Long> ids) {
ids.forEach(id -> {
List<FlowInstance> instances = FlowEngine.insService().getByDefId(id);
AssertUtil.isNotEmpty(instances, ExceptionCons.EXIST_START_TASK);
});
FlowEngine.nodeService().deleteNodeByDefIds(ids);
FlowEngine.skipService().deleteSkipByDefIds(ids);
return removeByIds(ids);
}
public boolean publish(Long id) {
List<FlowNode> nodeList = FlowEngine.nodeService().getByDefId(id);
AssertUtil.isEmpty(nodeList, ExceptionCons.NOT_DRAW_FLOW_ERROR);
FlowDefinition definition = getById(id);
List<FlowDefinition> definitions = getByFlowCode(definition.getFlowCode());
// 已发布流程定义改为已失效或者未发布状态
List<Long> otherDefIds = definitions.stream()
.filter(item -> !Objects.equals(definition.getId(), item.getId())
&& PublishStatus.PUBLISHED.getKey().equals(item.getIsPublish()))
.map(FlowDefinition::getId)
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(otherDefIds)) {
List<FlowInstance> instanceList = FlowEngine.insService().listByDefIds(otherDefIds);
if (CollUtil.isNotEmpty(instanceList)) {
// 已发布已使用过的流程定义
Set<Long> useDefIds = StreamUtils.toSet(instanceList, FlowInstance::getDefinitionId);
if (CollUtil.isNotEmpty(useDefIds)) {
// 已发布已使用过的流程定义改为已失效
getMapper().updatePublishStatus(new ArrayList<>(useDefIds), PublishStatus.EXPIRED.getKey());
// 过滤掉已发布已使用-->已发布未使用
otherDefIds.removeIf(useDefIds::contains);
}
}
if (CollUtil.isNotEmpty(otherDefIds)) {
// 已发布未使用过的流程定义改为未发布
getMapper().updatePublishStatus(otherDefIds, PublishStatus.UNPUBLISHED.getKey());
}
}
FlowDefinition flowDefinition = new FlowDefinition();
flowDefinition.setId(id);
flowDefinition.setIsPublish(PublishStatus.PUBLISHED.getKey());
return updateById(flowDefinition);
}
public boolean unPublish(Long id) {
List<FlowInstance> instances = FlowEngine.insService().getByDefId(id);
AssertUtil.isNotEmpty(instances, ExceptionCons.EXIST_START_TASK);
FlowDefinition definition = new FlowDefinition().setId(id);
definition.setIsPublish(PublishStatus.UNPUBLISHED.getKey());
return updateById(definition);
}
public boolean copyDef(Long id) {
FlowDefinition sourceDef = getById(id);
AssertUtil.isNull(sourceDef, ExceptionCons.NOT_FOUNT_DEF);
FlowDefinition definition = sourceDef.copy();
definition.setVersion(getNewVersion(definition));
List<FlowNode> nodeList = FlowEngine.nodeService().getByDefId(id).stream().map(FlowNode::copy).collect(Collectors.toList());
List<FlowSkip> skipList = FlowEngine.skipService().getByDefId(id).stream().map(FlowSkip::copy).collect(Collectors.toList());
FlowEngine.dataFillHandler().idFill(definition);
nodeList.forEach(node -> node.setDefinitionId(definition.getId()).setVersion(definition.getVersion()));
FlowEngine.nodeService().saveBatch(nodeList);
skipList.forEach(skip -> skip.setDefinitionId(definition.getId()));
FlowEngine.skipService().saveBatch(skipList);
return save(definition);
}
public boolean active(Long id) {
FlowDefinition definition = getById(id);
AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF);
AssertUtil.isTrue(ActivityStatus.isActivity(definition.getActivityStatus()), ExceptionCons.DEFINITION_ALREADY_ACTIVITY);
definition.setActivityStatus(ActivityStatus.ACTIVITY.getKey());
return updateById(definition);
}
public boolean unActive(Long id) {
FlowDefinition definition = getById(id);
AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF);
AssertUtil.isTrue(ActivityStatus.isSuspended(definition.getActivityStatus()), ExceptionCons.DEFINITION_ALREADY_SUSPENDED);
definition.setActivityStatus(ActivityStatus.SUSPENDED.getKey());
return updateById(definition);
}
public List<FlowDefinition> getByFlowCode(String flowCode) {
return list(new FlowDefinition().setFlowCode(flowCode));
}
public FlowDefinition getPublishByFlowCode(String flowCode) {
return FlowEngine.defService().getOne(new FlowDefinition()
.setFlowCode(flowCode).setIsPublish(PublishStatus.PUBLISHED.getKey()));
}
private String getNewVersion(FlowDefinition definition) {
List<String> flowCodeList = Collections.singletonList(definition.getFlowCode());
List<FlowDefinition> definitions = getMapper().queryByCodeList(flowCodeList);
int highestVersion = 0;
String latestNonPositiveVersion = null;
long latestTimestamp = Long.MIN_VALUE;
for (FlowDefinition otherDef : definitions) {
if (definition.getFlowCode().equals(otherDef.getFlowCode())) {
try {
int version = Integer.parseInt(otherDef.getVersion());
if (version > highestVersion) {
highestVersion = version;
}
} catch (NumberFormatException e) {
long timestamp = otherDef.getCreateTime().getTime();
if (timestamp > latestTimestamp) {
latestTimestamp = timestamp;
latestNonPositiveVersion = otherDef.getVersion();
}
}
}
}
String version = "1";
if (highestVersion > 0) {
version = String.valueOf(highestVersion + 1);
} else if (latestNonPositiveVersion != null) {
version = latestNonPositiveVersion + "_1";
}
return version;
}
private void checkFlowLegal(FlowCombine flowCombine) {
FlowDefinition definition = flowCombine.getDefinition();
String flowName = definition.getFlowName();
AssertUtil.isEmpty(definition.getFlowCode(), "" + flowName + "】流程flowCode为空!");
// 节点校验
List<FlowNode> allNodes = flowCombine.getAllNodes();
List<FlowSkip> allSkips = flowCombine.getAllSkips();
Map<String, List<FlowSkip>> skipMap = StreamUtils.groupByKey(allSkips, FlowSkip::getNowNodeCode);
allNodes.forEach(node -> {
node.setSkipList(skipMap.get(node.getNodeCode()));
skipMap.remove(node.getNodeCode());
});
AssertUtil.isNotEmpty(skipMap, "[" + flowName + "]" + ExceptionCons.FLOW_HAVE_USELESS_SKIP);
// 每一个流程的开始节点个数
Set<String> nodeCodeSet = new HashSet<>();
// 便利一个流程中的各个节点
int startNum = 0;
for (FlowNode node : allNodes) {
FlowConfigUtil.initNodeAndCondition(node, definition.getId(), definition.getVersion());
startNum = FlowConfigUtil.checkStartAndSame(node, startNum, flowName, nodeCodeSet);
}
AssertUtil.isTrue(startNum == 0, "[" + flowName + "]" + ExceptionCons.LOST_START_NODE);
// 校验跳转节点的合法性
FlowConfigUtil.checkSkipNode(allSkips);
// 校验所有目标节点是否都存在
FlowConfigUtil.validaIsExistDestNode(allSkips, nodeCodeSet);
}
@Override
public FlowDefinitionMapper getMapper() {
return SpringUtils.getBean(FlowDefinitionMapper.class);
}
}

View File

@ -0,0 +1,253 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.entity.*;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.dromara.common.core.utils.StreamUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.dto.FlowParams;
import org.dromara.warm.flow.entity.FlowHisTask;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowTask;
import org.dromara.warm.flow.entity.FlowUser;
import org.dromara.warm.flow.enums.CooperateType;
import org.dromara.warm.flow.enums.FlowStatus;
import org.dromara.warm.flow.enums.SkipType;
import org.dromara.warm.flow.service.WarmServiceImpl;
import org.dromara.warm.flow.utils.*;
import java.util.ArrayList;
import java.util.List;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.stereotype.Service;
import org.dromara.warm.flow.mapper.FlowHisTaskMapper;
/**
* 历史任务记录Service业务层处理
*
* @author warm
* @since 2023-03-29
*/
@Service
public class HisTaskService extends WarmServiceImpl<FlowHisTask> {
public List<FlowHisTask> listByTaskId(Long taskId) {
return list(new FlowHisTask().setTaskId(taskId));
}
public List<FlowHisTask> listByTaskIdAndCooperateTypes(Long taskId, Integer... cooperateTypes) {
if (ArrayUtil.isEmpty(cooperateTypes)) {
return listByTaskId(taskId);
}
if (cooperateTypes.length == 1) {
return list(new FlowHisTask().setTaskId(taskId).setCooperateType(cooperateTypes[0]));
}
return getMapper().listByTaskIdAndCooperateTypes(taskId, cooperateTypes);
}
public List<FlowHisTask> getByInsAndNodeCodes(Long instanceId, List<String> nodeCodes) {
return getMapper().getByInsAndNodeCodes(instanceId, nodeCodes);
}
public boolean deleteByInsIds(List<Long> instanceIds) {
return SqlHelper.retBool(getMapper().deleteByInsIds(instanceIds));
}
public FlowHisTask setSkipInsHis(FlowTask task, List<FlowNode> nextNodes, FlowParams flowParams) {
String flowStatus = getFlowStatus(flowParams);
return setSkipHis(task, nextNodes, flowParams, flowStatus);
}
public List<FlowHisTask> setSkipHisList(List<FlowTask> taskList, List<FlowNode> nextNodes, FlowParams flowParams) {
String flowStatus = getFlowStatus(flowParams);
List<FlowHisTask> hisTasks = new ArrayList<>();
for (FlowTask task : taskList) {
FlowHisTask hisTask = setSkipHis(task, nextNodes, flowParams, flowStatus);
hisTasks.add(hisTask);
}
return hisTasks;
}
public FlowHisTask setSkipHisTask(FlowTask task, FlowNode nextNode, FlowParams flowParams) {
String flowStatus = getFlowStatus(flowParams);
return setSkipHis(task, CollUtil.toList(nextNode), flowParams, flowStatus);
}
public FlowHisTask setCooperateHis(FlowTask task, FlowParams flowParams
, List<String> collaborators) {
String flowStatus = getFlowStatus(flowParams);
FlowHisTask hisTask = new FlowHisTask()
.setTaskId(task.getId())
.setInstanceId(task.getInstanceId())
.setCooperateType(ObjectUtil.defaultIfNull(flowParams.getCooperateType(), CooperateType.APPROVAL.getKey()))
.setCollaborator(StreamUtils.join(collaborators, c -> c))
.setNodeCode(task.getNodeCode())
.setNodeName(task.getNodeName())
.setNodeType(task.getNodeType())
.setDefinitionId(task.getDefinitionId())
.setTargetNodeCode(task.getNodeCode())
.setTargetNodeName(task.getNodeName())
.setApprover(flowParams.getHandler())
.setSkipType(flowParams.getSkipType())
.setFlowStatus(StrUtil.emptyToDefault(flowStatus, FlowStatus.APPROVAL.getKey()))
.setFormCustom(task.getFormCustom())
.setFormPath(task.getFormPath())
.setMessage(flowParams.getMessage())
.setVariable(flowParams.getVariableStr())
//业务详情添加至历史记录
.setExt(flowParams.getHisTaskExt())
.setCreateTime(task.getCreateTime());
FlowEngine.dataFillHandler().idFill(hisTask);
return hisTask;
}
public FlowHisTask notSkip(FlowTask task, FlowParams flowParams) {
String flowStatus = getFlowStatus(flowParams);
FlowHisTask hisTask = new FlowHisTask()
.setTaskId(task.getId())
.setInstanceId(task.getInstanceId())
.setCooperateType(ObjectUtil.defaultIfNull(flowParams.getCooperateType(), CooperateType.APPROVAL.getKey()))
.setNodeCode(task.getNodeCode())
.setNodeName(task.getNodeName())
.setNodeType(task.getNodeType())
.setDefinitionId(task.getDefinitionId())
.setTargetNodeCode(task.getNodeCode())
.setTargetNodeName(task.getNodeName())
.setApprover(flowParams.getHandler())
.setSkipType(SkipType.NONE.getKey())
.setFlowStatus(flowStatus)
.setFormCustom(task.getFormCustom())
.setFormPath(task.getFormPath())
.setMessage(flowParams.getMessage())
.setVariable(flowParams.getVariableStr())
//业务详情添加至历史记录
.setExt(flowParams.getHisTaskExt())
.setCreateTime(task.getCreateTime());
FlowEngine.dataFillHandler().idFill(hisTask);
return hisTask;
}
public FlowHisTask setDeputeHisTask(FlowTask task, FlowParams flowParams, FlowUser entrustedUser) {
String flowStatus = getFlowStatus(flowParams);
FlowHisTask hisTask = new FlowHisTask()
.setTaskId(task.getId())
.setInstanceId(task.getInstanceId())
.setCooperateType(CooperateType.DEPUTE.getKey())
.setNodeCode(task.getNodeCode())
.setNodeName(task.getNodeName())
.setNodeType(task.getNodeType())
.setDefinitionId(task.getDefinitionId())
.setTargetNodeCode(task.getNodeCode())
.setTargetNodeName(task.getNodeName())
.setApprover(flowParams.getHandler())
.setCollaborator(entrustedUser.getCreateBy())
.setSkipType(flowParams.getSkipType())
.setFlowStatus(StrUtil.isNotEmpty(flowStatus)
? flowStatus : SkipType.isReject(flowParams.getSkipType())
? FlowStatus.REJECT.getKey() : FlowStatus.PASS.getKey())
.setFormCustom(task.getFormCustom())
.setFormPath(task.getFormPath())
.setMessage(flowParams.getMessage())
.setVariable(flowParams.getVariableStr())
//业务详情添加至历史记录
.setExt(flowParams.getHisTaskExt())
.setCreateTime(task.getCreateTime());
FlowEngine.dataFillHandler().idFill(hisTask);
return hisTask;
}
public FlowHisTask setSignHisTask(FlowTask task, FlowParams flowParams, String nodeRatio, boolean isPass) {
String flowStatus = getFlowStatus(flowParams);
FlowHisTask hisTask = new FlowHisTask()
.setTaskId(task.getId())
.setInstanceId(task.getInstanceId())
.setCooperateType(CooperateType.isCountersign(nodeRatio)
? CooperateType.COUNTERSIGN.getKey() : CooperateType.VOTE.getKey())
.setNodeCode(task.getNodeCode())
.setNodeName(task.getNodeName())
.setNodeType(task.getNodeType())
.setDefinitionId(task.getDefinitionId())
.setApprover(flowParams.getHandler())
.setSkipType(isPass ? SkipType.PASS.getKey() : SkipType.REJECT.getKey())
.setFlowStatus(StrUtil.isNotEmpty(flowStatus)
? flowStatus : isPass
? FlowStatus.PASS.getKey() : FlowStatus.REJECT.getKey())
.setFormCustom(task.getFormCustom())
.setFormPath(task.getFormPath())
.setMessage(flowParams.getMessage())
.setVariable(flowParams.getVariableStr())
//业务详情添加至历史记录
.setExt(flowParams.getHisTaskExt())
.setCreateTime(task.getCreateTime());
FlowEngine.dataFillHandler().idFill(hisTask);
return hisTask;
}
public List<FlowHisTask> getByInsId(Long instanceId) {
return FlowEngine.hisTaskService().list(new FlowHisTask().setInstanceId(instanceId));
}
private FlowHisTask setSkipHis(FlowTask task, List<FlowNode> nextNodes, FlowParams flowParams, String flowStatus) {
FlowHisTask hisTask = new FlowHisTask()
.setTaskId(task.getId())
.setInstanceId(task.getInstanceId())
.setCooperateType(ObjectUtil.defaultIfNull(flowParams.getCooperateType(), CooperateType.APPROVAL.getKey()))
.setNodeCode(task.getNodeCode())
.setNodeName(task.getNodeName())
.setNodeType(task.getNodeType())
.setDefinitionId(task.getDefinitionId())
.setTargetNodeCode(StreamUtils.join(nextNodes, FlowNode::getNodeCode))
.setTargetNodeName(StreamUtils.join(nextNodes, FlowNode::getNodeName))
.setApprover(flowParams.getHandler())
.setSkipType(flowParams.getSkipType())
.setFlowStatus(StrUtil.isNotEmpty(flowStatus)
? flowStatus : SkipType.isReject(flowParams.getSkipType())
? FlowStatus.REJECT.getKey() : FlowStatus.PASS.getKey())
.setFormCustom(task.getFormCustom())
.setFormPath(task.getFormPath())
.setMessage(flowParams.getMessage())
.setVariable(flowParams.getVariableStr())
//业务详情添加至历史记录
.setExt(flowParams.getHisTaskExt())
.setCreateTime(task.getCreateTime());
FlowEngine.dataFillHandler().idFill(hisTask);
return hisTask;
}
private String getFlowStatus(FlowParams flowParams) {
return StrUtil.emptyToDefault(flowParams.getHisStatus(), flowParams.getFlowStatus());
}
@Override
public FlowHisTaskMapper getMapper() {
return SpringUtils.getBean(FlowHisTaskMapper.class);
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* Service接口
*
* @author warm
* @since 2023-03-17
*/
public interface IWarmService<T> {
/**
* 根据id查询
*
* @param id 主键
* @return 实体
*/
T getById(Serializable id);
/**
* 根据ids查询
*
* @param ids 主键
* @return 实体
*/
List<T> getByIds(Collection<? extends Serializable> ids);
/**
* 查询列表
*
* @param entity 查询实体
* @return 集合
*/
List<T> list(T entity);
/**
* 查询一条记录
*
* @param entity 查询实体
* @return 结果
*/
T getOne(T entity);
/**
* 判断是否存在
*
* @param entity 查询实体
* @return 结果
*/
Boolean exists(T entity);
/**
* 新增
*
* @param entity 实体
* @return 结果
*/
boolean save(T entity);
/**
* 根据id修改
*
* @param entity 实体
* @return 结果
*/
boolean updateById(T entity);
/**
* 根据id删除
*
* @param id 主键
* @return 结果
*/
boolean removeById(Serializable id);
/**
* 根据entity删除
*
* @param entity 实体
* @return 结果
*/
boolean remove(T entity);
/**
* 根据ids批量删除
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
boolean removeByIds(Collection<? extends Serializable> ids);
/**
* 批量新增
*
* @param list 实体集合
*/
void saveBatch(List<T> list);
}

View File

@ -0,0 +1,252 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.json.JsonUtil;
import org.dromara.warm.flow.entity.*;
import org.dromara.common.core.utils.StreamUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.constant.ExceptionCons;
import org.dromara.warm.flow.dto.FlowCombine;
import org.dromara.warm.flow.dto.FlowParams;
import org.dromara.warm.flow.dto.PathWayData;
import org.dromara.warm.flow.enums.ActivityStatus;
import org.dromara.warm.flow.enums.FlowStatus;
import org.dromara.warm.flow.enums.NodeType;
import org.dromara.warm.flow.enums.SkipType;
import org.dromara.warm.flow.listener.ListenerVariable;
import org.dromara.warm.flow.service.WarmServiceImpl;
import org.dromara.warm.flow.utils.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.dromara.warm.flow.entity.FlowInstance;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.stereotype.Service;
import org.dromara.warm.flow.mapper.FlowInstanceMapper;
/**
* 流程实例Service业务层处理
*
* @author warm
* @since 2023-03-29
*/
@Service
public class InsService extends WarmServiceImpl<FlowInstance> {
public FlowInstance start(String businessId, FlowParams flowParams) {
AssertUtil.isNull(flowParams.getFlowCode(), ExceptionCons.NULL_FLOW_CODE);
AssertUtil.isEmpty(businessId, ExceptionCons.NULL_BUSINESS_ID);
// 获取已发布的流程节点
FlowDefinition definition = FlowEngine.defService().getPublishByFlowCode(flowParams.getFlowCode());
AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF);
FlowCombine flowCombine = FlowEngine.defService().getFlowCombine(definition);
// 获取开始节点
FlowNode startNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), t -> NodeType.isStart(t.getNodeType()));
AssertUtil.isNull(startNode, ExceptionCons.LOST_START_NODE);
// 判断流程定义是否激活状态
AssertUtil.isTrue(definition.getActivityStatus().equals(ActivityStatus.SUSPENDED.getKey())
, ExceptionCons.NOT_DEFINITION_ACTIVITY);
flowParams.skipType(SkipType.PASS.getKey());
// 执行开始监听器
ListenerUtil.executeStart(new ListenerVariable(definition, null, startNode, flowParams.getVariable())
.setFlowParams(flowParams));
// 获取下一个节点如果是网关节点则重新获取后续节点
PathWayData pathWayData = new PathWayData().setDefId(startNode.getDefinitionId()).setSkipType(flowParams.getSkipType());
List<FlowNode> nextNodes = FlowEngine.nodeService().getNextNodeList(startNode, null, flowParams.getSkipType(),
flowParams.getVariable(), pathWayData, flowCombine);
// 设置流程实例对象
FlowInstance instance = setStartInstance(nextNodes.get(0), businessId, flowParams);
// 设置历史任务
FlowHisTask hisTask = setHisTask(nextNodes, flowParams, startNode, instance.getId());
List<FlowTask> addTasks = StreamUtils.toList(nextNodes, node -> FlowEngine.taskService()
.addTask(node, instance, definition, flowParams));
// 办理人变量替换
if (CollUtil.isNotEmpty(addTasks)) {
ExpressionUtil.evalVariable(addTasks, flowParams);
}
// 设置流程图元数据
pathWayData.getTargetNodes().addAll(nextNodes);
instance.setDefJson(FlowEngine.chartService().startMetadata(pathWayData));
// 执行分派监听器
ListenerUtil.executeAssignment(new ListenerVariable(definition, instance, startNode, flowParams.getVariable()
, null, nextNodes, addTasks).setFlowParams(flowParams));
// 开启流程保存流程信息
saveFlowInfo(instance, addTasks, hisTask, flowParams);
// 执行完成和创建监听器
ListenerUtil.endCreateListener(new ListenerVariable(definition, instance, startNode, flowParams.getVariable()
, null, nextNodes, addTasks).setFlowParams(flowParams));
return instance;
}
public List<FlowInstance> listByDefIds(List<Long> defIds) {
return getMapper().getByDefIds(defIds);
}
public boolean remove(List<Long> instanceIds) {
return toRemoveTask(instanceIds);
}
public List<FlowInstance> getByDefId(Long definitionId) {
return list(new FlowInstance().setDefinitionId(definitionId));
}
/**
* 设置历史任务
*
* @param nextNodes 下一节点集合
* @param flowParams 流程参数
* @param startNode 开始节点
* @param instanceId 流程实例id
*/
private FlowHisTask setHisTask(List<FlowNode> nextNodes, FlowParams flowParams, FlowNode startNode, Long instanceId) {
FlowTask startTask = new FlowTask()
.setInstanceId(instanceId)
.setDefinitionId(startNode.getDefinitionId())
.setNodeCode(startNode.getNodeCode())
.setNodeName(startNode.getNodeName())
.setNodeType(startNode.getNodeType());
FlowEngine.dataFillHandler().idFill(startTask);
// 开始任务转历史任务
return FlowEngine.hisTaskService().setSkipInsHis(startTask, nextNodes, flowParams);
}
/**
* 开启流程保存流程信息
*
* @param instance 流程实例
* @param addTasks 新增任务
* @param hisTask 历史任务
*/
private void saveFlowInfo(FlowInstance instance, List<FlowTask> addTasks, FlowHisTask hisTask, FlowParams flowParams) {
FlowEngine.taskService().setInsFinishInfo(instance, addTasks, flowParams);
FlowEngine.hisTaskService().save(hisTask);
// 待办任务设置处理人
if (CollUtil.isNotEmpty(addTasks)) {
List<FlowUser> users = FlowEngine.userService().taskAddUsers(addTasks);
FlowEngine.taskService().saveBatch(addTasks);
FlowEngine.userService().saveBatch(users);
}
save(instance);
}
/**
* 设置流程实例对象
*
* @param firstBetweenNode 第一个中间节点
* @param businessId 业务id
* @return FlowInstance
*/
private FlowInstance setStartInstance(FlowNode firstBetweenNode, String businessId
, FlowParams flowParams) {
FlowInstance instance = new FlowInstance();
Date now = new Date();
FlowEngine.dataFillHandler().idFill(instance);
// 关联业务id,其实后面可以不用到业务id,传业务id目前来看只是为了批量创建流程的时候能创建出有区别化的流程,也是为了后期需要用到businessId
instance.setDefinitionId(firstBetweenNode.getDefinitionId())
.setBusinessId(businessId)
.setNodeType(firstBetweenNode.getNodeType())
.setNodeCode(firstBetweenNode.getNodeCode())
.setNodeName(firstBetweenNode.getNodeName())
.setFlowStatus(StrUtil.emptyToDefault(flowParams.getFlowStatus(), FlowStatus.TOBESUBMIT.getKey()))
.setActivityStatus(ActivityStatus.ACTIVITY.getKey())
.setVariable(JsonUtil.objToStr(flowParams.getVariable()))
.setCreateTime(now)
.setUpdateTime(now)
.setCreateBy(flowParams.getHandler())
.setUpdateBy(flowParams.getHandler())
.setExt(flowParams.getExt());
return instance;
}
private boolean toRemoveTask(List<Long> instanceIds) {
AssertUtil.isEmpty(instanceIds, ExceptionCons.NULL_INSTANCE_ID);
List<Long> taskIds = new ArrayList<>();
instanceIds.forEach(instanceId -> taskIds.addAll(
FlowEngine.taskService()
.list(new FlowTask().setInstanceId(instanceId))
.stream()
.map(FlowTask::getId)
.collect(Collectors.toList())));
if (CollUtil.isNotEmpty(taskIds)) {
FlowEngine.userService().deleteByTaskIds(taskIds);
}
FlowEngine.taskService().deleteByInsIds(instanceIds);
FlowEngine.hisTaskService().deleteByInsIds(instanceIds);
return FlowEngine.insService().removeByIds(instanceIds);
}
public boolean active(Long id) {
FlowInstance instance = getById(id);
AssertUtil.isNull(instance, ExceptionCons.NOT_FOUNT_INSTANCE);
AssertUtil.isTrue(ActivityStatus.isActivity(instance.getActivityStatus()), ExceptionCons.INSTANCE_ALREADY_ACTIVITY);
instance.setActivityStatus(ActivityStatus.ACTIVITY.getKey());
return updateById(instance);
}
public boolean unActive(Long id) {
FlowInstance instance = getById(id);
AssertUtil.isNull(instance, ExceptionCons.NOT_FOUNT_INSTANCE);
AssertUtil.isTrue(ActivityStatus.isSuspended(instance.getActivityStatus()), ExceptionCons.INSTANCE_ALREADY_SUSPENDED);
instance.setActivityStatus(ActivityStatus.SUSPENDED.getKey());
return updateById(instance);
}
public void removeVariables(Long instanceId, String... keys) {
FlowInstance instance = FlowEngine.insService().getById(instanceId);
if (instance != null) {
Map<String, Object> variableMap = instance.getVariableMap();
for (String key : keys) {
variableMap.remove(key);
}
instance.setVariable(JsonUtil.objToStr(variableMap));
FlowEngine.insService().updateById(instance);
}
}
@Override
public FlowInstanceMapper getMapper() {
return SpringUtils.getBean(FlowInstanceMapper.class);
}
}

View File

@ -0,0 +1,366 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.json.JsonUtil;
import org.dromara.warm.flow.entity.*;
import org.dromara.common.core.utils.StreamUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.constant.ExceptionCons;
import org.dromara.warm.flow.constant.FlowCons;
import org.dromara.warm.flow.dto.FlowCombine;
import org.dromara.warm.flow.dto.PathWayData;
import org.dromara.warm.flow.entity.FlowDefinition;
import org.dromara.warm.flow.entity.FlowNode;
import org.dromara.warm.flow.entity.FlowSkip;
import org.dromara.warm.flow.enums.NodeType;
import org.dromara.warm.flow.enums.PublishStatus;
import org.dromara.warm.flow.enums.SkipType;
import org.dromara.warm.flow.exception.FlowException;
import org.dromara.warm.flow.service.WarmServiceImpl;
import org.dromara.warm.flow.utils.*;
import java.io.Serializable;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.stereotype.Service;
import org.dromara.warm.flow.mapper.FlowNodeMapper;
import java.util.Collection;
import java.util.List;
/**
* 流程节点Service业务层处理
*
* @author warm
* @since 2023-03-29
*/
@Slf4j
@Service
public class NodeService extends WarmServiceImpl<FlowNode> {
public List<FlowNode> getPublishByFlowCode(String flowCode) {
FlowDefinition definition = FlowEngine.defService().getOne(new FlowDefinition()
.setFlowCode(flowCode).setIsPublish(PublishStatus.PUBLISHED.getKey()));
if (ObjectUtil.isNotNull(definition)) {
return list(new FlowNode().setDefinitionId(definition.getId()));
}
return Collections.emptyList();
}
public List<FlowNode> getByNodeCodes(List<String> nodeCodes, Long definitionId) {
return getMapper().getByNodeCodes(nodeCodes, definitionId);
}
public List<FlowNode> previousNodeList(Long nodeId) {
FlowNode nowNode = getById(nodeId);
return previousNodeList(nowNode.getDefinitionId(), nowNode.getNodeCode());
}
public List<FlowNode> previousNodeList(Long definitionId, String nowNodeCode) {
return prefixOrSuffixNodes(definitionId, nowNodeCode, FlowCons.PREVIOUS);
}
public List<FlowNode> suffixNodeList(Long nodeId) {
FlowNode nowNode = getById(nodeId);
return suffixNodeList(nowNode.getDefinitionId(), nowNode.getNodeCode());
}
public List<FlowNode> suffixNodeList(Long definitionId, String nowNodeCode) {
return prefixOrSuffixNodes(definitionId, nowNodeCode, FlowCons.SUFFIX);
}
public List<FlowNode> suffixNodeList(String nowNodeCode, FlowCombine flowCombine) {
return prefixOrSuffixNodes(nowNodeCode, FlowCons.SUFFIX, flowCombine);
}
public List<FlowNode> getByDefId(Long definitionId) {
return list(new FlowNode().setDefinitionId(definitionId));
}
public FlowNode getByDefIdAndNodeCode(Long definitionId, String nodeCode) {
return getOne(new FlowNode().setDefinitionId(definitionId).setNodeCode(nodeCode));
}
public FlowNode getStartNode(Long definitionId) {
return getOne(new FlowNode().setDefinitionId(definitionId).setNodeType(NodeType.START.getKey()));
}
public List<FlowNode> getBetweenNode(Long definitionId) {
return list(new FlowNode().setDefinitionId(definitionId).setNodeType(NodeType.BETWEEN.getKey()));
}
public List<FlowNode> getFirstBetweenNode(Long definitionId, Map<String, Object> variable) {
FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(definitionId);
FlowNode startNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), t -> NodeType.isStart(t.getNodeType()));
return getNextNodeList(startNode, null, SkipType.PASS.getKey(),
variable, null, flowCombine);
}
public FlowNode getEndNode(Long definitionId) {
return getOne(new FlowNode().setDefinitionId(definitionId).setNodeType(NodeType.END.getKey()));
}
public List<FlowNode> prefixOrSuffixNodes(Long definitionId, String nowNodeCode, String type) {
FlowCombine flowCombine = new FlowCombine();
flowCombine.setAllNodes(FlowEngine.nodeService().getByDefId(definitionId));
flowCombine.setAllSkips(FlowEngine.skipService().getByDefId(definitionId));
return prefixOrSuffixNodes(nowNodeCode, type, flowCombine);
}
public List<FlowNode> prefixOrSuffixNodes(String nowNodeCode, String type, FlowCombine flowCombine) {
Map<String, FlowNode> nodeMap = StreamUtils.toMap(flowCombine.getAllNodes(), FlowNode::getNodeCode, node -> node);
Map<String, List<FlowSkip>> skipMap = flowCombine.getAllSkips().stream().filter(skip -> SkipType.isPass(skip.getSkipType()))
.collect(Collectors.groupingBy(FlowCons.PREVIOUS.equals(type) ? FlowSkip::getNextNodeCode : FlowSkip::getNowNodeCode
, LinkedHashMap::new, Collectors.toList()));
List<FlowNode> prefixOrSuffixNodes = new ArrayList<>();
List<String> prefixOrSuffixCode = prefixOrSuffixCodes(skipMap, nowNodeCode
, FlowCons.PREVIOUS.equals(type) ? FlowSkip::getNowNodeCode : FlowSkip::getNextNodeCode);
for (String nodeCode : prefixOrSuffixCode) {
FlowNode node = nodeMap.get(nodeCode);
if (!NodeType.isGateWay(node.getNodeType())) {
prefixOrSuffixNodes.add(node);
}
}
Collections.reverse(prefixOrSuffixNodes);
Set<String> sameCode = new HashSet<>();
prefixOrSuffixNodes.removeIf(node -> {
if (sameCode.contains(node.getNodeCode())) {
return true;
}
sameCode.add(node.getNodeCode());
return false;
});
Collections.reverse(prefixOrSuffixNodes);
return prefixOrSuffixNodes;
}
public List<FlowNode> getNextNodeList(Long definitionId, String nowNodeCode, String anyNodeCode, String skipType,
Map<String, Object> variable) {
AssertUtil.isEmpty(nowNodeCode, ExceptionCons.LOST_NODE_CODE);
// 查询当前节点
FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(definitionId);
FlowNode nowNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), t -> t.getNodeCode().equals(nowNodeCode));
// 如果是网关节点则根据条件判断
return getNextByCheckGateway(variable, getNextNode(nowNode, anyNodeCode, skipType, null, flowCombine),
null, flowCombine);
}
public FlowNode getNextNode(Long definitionId, String nowNodeCode, String anyNodeCode, String skipType) {
// 查询当前节点
FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(definitionId);
FlowNode nowNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), t -> t.getNodeCode().equals(nowNodeCode));
return getNextNode(nowNode, anyNodeCode, skipType, null, flowCombine);
}
public List<FlowNode> getNextNodeList(FlowNode nowNode, String anyNodeCode, String skipType, Map<String, Object> variable
, PathWayData pathWayData, FlowCombine flowCombine) {
// 如果是网关节点则根据条件判断
return getNextByCheckGateway(variable, getNextNode(nowNode, anyNodeCode, skipType
, pathWayData, flowCombine), pathWayData, flowCombine);
}
public FlowNode getNextNode(FlowNode nowNode, String anyNodeCode, String skipType, PathWayData pathWayData, FlowCombine flowCombine) {
// 查询当前节点
AssertUtil.isNull(nowNode, ExceptionCons.LOST_NODE_CODE);
AssertUtil.isNull(nowNode.getDefinitionId(), ExceptionCons.NOT_DEFINITION_ID);
AssertUtil.isEmpty(skipType, ExceptionCons.NULL_CONDITION_VALUE);
if (pathWayData != null) {
pathWayData.getPathWayNodes().add(nowNode);
}
FlowNode nextNode = null;
if (StrUtil.isNotEmpty(anyNodeCode)) {
// 如果指定了跳转节点直接获取节点
nextNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), node -> anyNodeCode.equals(node.getNodeCode()));
} else if (StrUtil.isNotEmpty(nowNode.getAnyNodeSkip()) && SkipType.isReject(skipType)) {
// 如果配置了任意跳转节点直接获取节点
nextNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), node -> nowNode.getAnyNodeSkip().equals(node.getNodeCode()));
}
if (ObjectUtil.isNotNull(nextNode)) {
AssertUtil.isTrue(NodeType.isGateWay(nextNode.getNodeType()), ExceptionCons.TAR_NOT_GATEWAY);
return nextNode;
}
// 获取跳转关系
List<FlowSkip> skips = StreamUtils.filter(flowCombine.getAllSkips(), skip -> nowNode.getNodeCode().equals(skip.getNowNodeCode()));
AssertUtil.isNull(skips, ExceptionCons.NULL_DEST_NODE);
FlowSkip nextSkip = getSkipByCheck(skips, skipType);
// 根据跳转查询出跳转到的那个节点
nextNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), node -> nextSkip != null && nextSkip.getNextNodeCode().equals(node.getNodeCode()));
AssertUtil.isNull(nextNode, ExceptionCons.NULL_NODE_CODE);
AssertUtil.isTrue(NodeType.isStart(nextNode.getNodeType()), ExceptionCons.FIRST_FORBID_BACK);
if (pathWayData != null) {
pathWayData.getPathWayNodes().add(nextNode);
pathWayData.getPathWaySkips().add(nextSkip);
}
return nextNode;
}
public List<FlowNode> getNextByCheckGateway(Map<String, Object> variable, FlowNode nextNode, PathWayData pathWayData
, FlowCombine flowCombine) {
// 网关节点处理
if (NodeType.isGateWay(nextNode.getNodeType())) {
List<FlowSkip> skipsGateway = StreamUtils.filter(flowCombine.getAllSkips()
, skip -> nextNode.getNodeCode().equals(skip.getNowNodeCode()));
if (CollUtil.isEmpty(skipsGateway)) {
return null;
}
//如果是互斥网关跳转条件匹配的则取任意第一条否则取跳转条件为空的任意一条
if (NodeType.isGateWaySerial(nextNode.getNodeType())) {
FlowSkip skipOne = null;
for (FlowSkip skip : skipsGateway) {
if (StrUtil.isNotEmpty(skip.getSkipCondition())) {
if (ExpressionUtil.evalCondition(skip.getSkipCondition(), variable)) {
skipOne = skip;
break;
}
} else {
skipOne = skip;
}
}
skipsGateway = skipOne == null ? null : CollUtil.toList(skipOne);
} else if (NodeType.isGateWayInclusive(nextNode.getNodeType())) {
//如果是包含网关有跳转条件的分支但是跳转条件不匹配的不执行没跳转条件为空的分支默认执行
skipsGateway.removeIf(skip -> StrUtil.isNotEmpty(skip.getSkipCondition())
&& !ExpressionUtil.evalCondition(skip.getSkipCondition(), variable));
}
AssertUtil.isEmpty(skipsGateway, ExceptionCons.NULL_CONDITION_VALUE_NODE);
List<String> nextNodeCodes = StreamUtils.toList(skipsGateway, FlowSkip::getNextNodeCode);
List<FlowNode> nextNodes = StreamUtils.filter(flowCombine.getAllNodes()
, node -> nextNodeCodes.contains(node.getNodeCode()));
AssertUtil.isEmpty(nextNodes, ExceptionCons.NOT_NODE_DATA);
if (pathWayData != null) {
pathWayData.getPathWayNodes().addAll(nextNodes);
pathWayData.getPathWaySkips().addAll(skipsGateway);
}
List<FlowNode> newNextNodes = new ArrayList<>();
for (FlowNode node : nextNodes) {
List<FlowNode> nodeList = getNextByCheckGateway(variable, node, pathWayData, flowCombine);
newNextNodes.addAll(nodeList);
}
return newNextNodes;
}
// 非网关节点直接返回
if (pathWayData != null) {
pathWayData.getPathWayNodes().remove(nextNode);
}
AssertUtil.isTrue(NodeType.isStart(nextNode.getNodeType()), ExceptionCons.START_NODE_NOT_ALLOW_JUMP);
return CollUtil.toList(nextNode);
}
public int deleteNodeByDefIds(Collection<? extends Serializable> defIds) {
return getMapper().deleteNodeByDefIds(defIds);
}
public Map<String, String> getExt(FlowNode node) {
Map<String, String> map = new HashMap<>();
String ext = node.getExt();
if (StrUtil.isNotEmpty(ext)) {
List<Map<String, Object>> extList = JsonUtil.strToList(ext);
if (CollUtil.isNotEmpty(extList)) {
for (Map<String, Object> extMap : extList) {
String code = ObjectUtil.defaultIfNull(extMap.get("code"), "").toString();
String value = ObjectUtil.defaultIfNull(extMap.get("value"), "").toString();
if (StrUtil.isAllNotEmpty(code, value)) {
map.put(code, value);
}
}
}
}
return map;
}
private List<String> prefixOrSuffixCodes(Map<String, List<FlowSkip>> skipMap, String nodeCode,
Function<FlowSkip, String> supplier) {
// 记录已访问节点防止循环
Set<String> visited = new HashSet<>();
List<String> result = new ArrayList<>();
prefixOrSuffixCodesRecursive(skipMap, nodeCode, supplier, visited, result);
return result;
}
private void prefixOrSuffixCodesRecursive(Map<String, List<FlowSkip>> skipMap, String nodeCode,
Function<FlowSkip, String> supplier, Set<String> visited, List<String> result) {
if (visited.contains(nodeCode)) {
// 防止循环访问
return;
}
visited.add(nodeCode);
List<FlowSkip> skipList = skipMap.get(nodeCode);
if (CollUtil.isNotEmpty(skipList)) {
for (FlowSkip skip : skipList) {
if (SkipType.isPass(skip.getSkipType())) {
String nextNodeCode = supplier.apply(skip);
// 避免重复添加
if (!result.contains(nextNodeCode)) {
result.add(nextNodeCode);
}
prefixOrSuffixCodesRecursive(skipMap, nextNodeCode, supplier, visited, result);
}
}
}
}
/**
* 通过校验跳转类型获取跳转集合
*
* @param skips 跳转集合
* @param skipType 跳转类型
* @return List<FlowSkip>
* @author xiarg
* @since 2024/8/21 11:32
*/
private FlowSkip getSkipByCheck(List<FlowSkip> skips, String skipType) {
return Optional.ofNullable(skips)
.orElse(Collections.emptyList())
.stream()
.filter(t -> StrUtil.isEmpty(t.getSkipType()) || skipType.equals(t.getSkipType()))
.findFirst()
.orElseThrow(() -> new FlowException(ExceptionCons.NULL_SKIP_TYPE));
}
@Override
public FlowNodeMapper getMapper() {
return SpringUtils.getBean(FlowNodeMapper.class);
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.entity.*;
import org.dromara.warm.flow.entity.FlowSkip;
import org.dromara.warm.flow.service.WarmServiceImpl;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.stereotype.Service;
import org.dromara.warm.flow.mapper.FlowSkipMapper;
/**
* 节点跳转关联Service业务层处理
*
* @author warm
* @since 2023-03-29
*/
@Service
public class SkipService extends WarmServiceImpl<FlowSkip> {
public int deleteSkipByDefIds(Collection<? extends Serializable> defIds) {
return getMapper().deleteSkipByDefIds(defIds);
}
public List<FlowSkip> getByDefId(Long definitionId) {
return list(new FlowSkip().setDefinitionId(definitionId));
}
public List<FlowSkip> getByDefIdAndNowNodeCode(Long definitionId, String nodeCode) {
return list(new FlowSkip().setDefinitionId(definitionId).setNowNodeCode(nodeCode));
}
@Override
public FlowSkipMapper getMapper() {
return SpringUtils.getBean(FlowSkipMapper.class);
}
}

View File

@ -0,0 +1,993 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.json.JsonUtil;
import org.dromara.warm.flow.entity.*;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.dromara.common.core.utils.StreamUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.constant.ExceptionCons;
import org.dromara.warm.flow.constant.FlowCons;
import org.dromara.warm.flow.dto.*;
import org.dromara.warm.flow.enums.*;
import org.dromara.warm.flow.listener.ListenerVariable;
import org.dromara.warm.flow.service.WarmServiceImpl;
import org.dromara.warm.flow.utils.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
import org.dromara.warm.flow.entity.FlowTask;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.stereotype.Service;
import org.dromara.warm.flow.mapper.FlowTaskMapper;
import java.util.List;
/**
* 待办任务Service业务层处理
*
* @author warm
* @since 2023-03-29
*/
@Service
public class TaskService extends WarmServiceImpl<FlowTask> {
public FlowInstance pass(Long taskId, String message, Map<String, Object> variable) {
return skip(taskId, new FlowParams(SkipType.PASS.getKey(), message, variable));
}
public FlowInstance passAtWill(Long taskId, String nodeCode, String message, Map<String, Object> variable) {
return skip(taskId, new FlowParams(nodeCode, SkipType.PASS.getKey(), message, variable));
}
public FlowInstance pass(Long taskId, String message, Map<String, Object> variable, String flowStatus, String hisStatus) {
return skip(taskId, new FlowParams(SkipType.PASS.getKey(), message, variable, flowStatus, hisStatus));
}
public FlowInstance passAtWill(Long taskId, String nodeCode, String message, Map<String, Object> variable
, String flowStatus, String hisStatus) {
return skip(taskId, new FlowParams(nodeCode, SkipType.PASS.getKey(), message, variable, flowStatus, hisStatus));
}
public FlowInstance reject(Long taskId, String message, Map<String, Object> variable) {
return skip(taskId, new FlowParams(SkipType.REJECT.getKey(), message, variable));
}
public FlowInstance rejectAtWill(Long taskId, String nodeCode, String message, Map<String, Object> variable) {
return skip(taskId, new FlowParams(nodeCode, SkipType.REJECT.getKey(), message, variable));
}
public FlowInstance reject(Long taskId, String message, Map<String, Object> variable, String flowStatus, String hisStatus) {
return skip(taskId, new FlowParams(SkipType.REJECT.getKey(), message, variable, flowStatus, hisStatus));
}
public FlowInstance rejectAtWill(Long taskId, String nodeCode, String message, Map<String, Object> variable
, String flowStatus, String hisStatus) {
return skip(taskId, new FlowParams(nodeCode, SkipType.REJECT.getKey(), message, variable, flowStatus, hisStatus));
}
public FlowInstance skip(Long taskId, FlowParams flowParams) {
// 获取待办任务
FlowTask task = getById(taskId);
return skip(flowParams, task);
}
public FlowInstance skipByInsId(Long instanceId, FlowParams flowParams) {
return skip(flowParams, getTask(instanceId));
}
public FlowInstance rejectLastByInsId(Long instanceId, FlowParams flowParams) {
return rejectLast(getTask(instanceId), flowParams);
}
public FlowInstance rejectLast(Long taskId, FlowParams flowParams) {
return rejectLast(getById(taskId), flowParams);
}
public FlowInstance rejectLast(FlowTask task, FlowParams flowParams) {
flowParams.skipType(SkipType.REJECT.getKey());
AssertUtil.isNull(task, ExceptionCons.NOT_FOUNT_TASK);
// 获取当前任务的前置任务
List<FlowHisTask> hisTaskList = FlowEngine.hisTaskService().getByInsId(task.getInstanceId());
// 获取hisTaskList中TargetNodeCod等于task.getNodeCode()并且id最大的
FlowHisTask lastHisTask = hisTaskList.stream()
.filter(hisTask -> StrUtil.isNotEmpty(hisTask.getTargetNodeCode()))
.filter(hisTask -> SkipType.isPass(hisTask.getSkipType()))
.filter(hisTask -> {
String targetCode = hisTask.getTargetNodeCode();
if (targetCode.contains(",")) {
return Arrays.asList(targetCode.split(",")).contains(task.getNodeCode());
} else {
return targetCode.equals(task.getNodeCode());
}
})
.max(Comparator.comparingLong(FlowHisTask::getId))
.orElse(null);
AssertUtil.isNull(lastHisTask, ExceptionCons.NOT_FOUNT_LAST_TASK);
flowParams.nodeCode(lastHisTask.getNodeCode());
return skip(flowParams, task);
}
public FlowInstance taskBackByInsId(Long instanceId, FlowParams flowParams) {
// 获取当前任务的前置任务
FlowHisTask lastHisTask = taskBack(flowParams, instanceId);
List<FlowNode> suffixNodeList = FlowEngine.nodeService().suffixNodeList(lastHisTask.getDefinitionId()
, lastHisTask.getNodeCode());
List<String> suffixNodeCodes = StreamUtils.toList(suffixNodeList, FlowNode::getNodeCode);
List<FlowTask> taskList = FlowEngine.taskService().getByInsIdAndNodeCodes(instanceId, suffixNodeCodes);
AssertUtil.isEmpty(taskList, ExceptionCons.NOT_FOUNT_HANDLED_TASK_HANDLER);
return skip(flowParams, taskList.get(0));
}
public FlowInstance taskBack(Long taskId, FlowParams flowParams) {
FlowTask task = getById(taskId);
AssertUtil.isNull(task, ExceptionCons.NOT_FOUNT_TASK);
taskBack(flowParams, task.getInstanceId());
return skip(flowParams, task);
}
public FlowInstance skip(FlowParams flowParams, FlowTask task) {
// TODO min 后续考虑并发问题待办任务和实例表不同步可给待办任务id加锁抽取所接口方便后续兼容分布式锁
// 流程开启前正确性校验
R r = getAndCheck(task);
flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable()));
// 非第一个记得跳转类型必传
if (!NodeType.isStart(task.getNodeType())) {
AssertUtil.isFalse(StrUtil.isNotEmpty(flowParams.getSkipType()), ExceptionCons.NULL_CONDITION_VALUE);
}
task.setUserList(FlowEngine.userService().listByAssociatedAndTypes(task.getId()));
FlowCombine flowCombine = FlowEngine.defService().getFlowCombineNoDef(r.definition.getId());
// 执行开始监听器
ListenerUtil.executeStart(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable()
, task).setFlowParams(flowParams));
// 如果是受托人在处理任务需要处理一条委派记录并且更新委托人回到计划审批人,然后直接返回流程实例
if (!flowParams.isIgnoreDepute() && handleDepute(task, flowParams)) {
return r.instance;
}
// 判断当前处理人是否有权限处理
checkAuth(task, flowParams);
//或签会签票签逻辑处理
if (!flowParams.isIgnoreCooperate() && cooperate(r.nowNode, task, flowParams)) {
return r.instance;
}
// 获取后续任务节点结合
PathWayData pathWayData = new PathWayData().setInsId(task.getInstanceId()).setSkipType(flowParams.getSkipType());
FlowNode nextNode = FlowEngine.nodeService().getNextNode(r.nowNode, flowParams.getNodeCode()
, flowParams.getSkipType(), pathWayData, flowCombine);
List<FlowNode> nextNodes = FlowEngine.nodeService().getNextByCheckGateway(flowParams.getVariable()
, nextNode, pathWayData, flowCombine);
// 判断并行网关和包容网关节点只剩一个前置代办任务才能生成新的代办任务
isGenerateNewTask(pathWayData, r.instance, nextNodes);
pathWayData.getTargetNodes().addAll(nextNodes);
// 设置流程图元数据
r.instance.setDefJson(FlowEngine.chartService().skipMetadata(pathWayData));
// 构建增待办任务和设置结束任务历史记录
List<FlowTask> addTasks = StreamUtils.toList(nextNodes, node -> addTask(node, r.instance, r.definition, flowParams));
// 办理人变量替换
ExpressionUtil.evalVariable(addTasks, flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable())));
// 执行分派监听器
ListenerUtil.executeAssignment(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable()
, task, nextNodes, addTasks).setFlowParams(flowParams));
// 更新流程信息
updateFlowInfo(task, r.instance, addTasks, flowParams, nextNodes);
// 一票否决谨慎使用如果退回退回指向节点后还存在其他正在执行的待办任务转历史任务状态都为失效,重走流程
if (CollUtil.isNotEmpty(nextNodes) && SkipType.isReject(flowParams.getSkipType())) {
oneVoteVeto(task, nextNodes.get(0).getNodeCode(), flowCombine);
}
// 处理未完成的任务当流程完成还存在待办任务未完成转历史任务状态完成
handUndoneTask(r.instance);
// 执行完成和创建监听器
ListenerUtil.endCreateListener(new ListenerVariable(r.definition, r.instance, r.nowNode
, flowParams.getVariable(), task, nextNodes, addTasks).setFlowParams(flowParams));
return r.instance;
}
public FlowInstance revoke(Long instanceId, FlowParams flowParams) {
flowParams.skipType(SkipType.REJECT.getKey());
// 删除待办任务保存历史删除所有代办任务的权限人
if (StrUtil.isEmpty(flowParams.getFlowStatus())) {
flowParams.flowStatus(FlowStatus.CANCEL.getKey());
}
FlowInstance instance = FlowEngine.insService().getById(instanceId);
AssertUtil.isNull(instance, ExceptionCons.NOT_FOUNT_INSTANCE);
FlowDefinition definition = FlowEngine.defService().getById(instance.getDefinitionId());
AssertUtil.isFalse(judgeActivityStatus(definition, instance), ExceptionCons.NOT_ACTIVITY);
AssertUtil.isTrue(NodeType.isEnd(instance.getNodeType()), ExceptionCons.FLOW_FINISH);
flowParams.variable(MapUtil.mergeAll(instance.getVariableMap(), flowParams.getVariable()));
List<FlowTask> taskList = getByInsId(instanceId);
FlowCombine flowCombine = FlowEngine.defService().getFlowCombine(definition);
Map<String, FlowNode> nodeMap = StreamUtils.toMap(flowCombine.getAllNodes(), FlowNode::getNodeCode, node -> node);
// 执行开始监听器
taskList.forEach(task -> ListenerUtil.executeStart(new ListenerVariable(definition, instance
, nodeMap.get(task.getNodeCode()), flowParams.getVariable(), task).setFlowParams(flowParams)));
// 验证权限是不是当前任务的发起人
if (!flowParams.isIgnore()) {
AssertUtil.isFalse(instance.getCreateBy().equals(flowParams.getHandler())
, ExceptionCons.NOT_DEF_PROMOTER_NOT_CANCEL);
}
// 获取开始节点
FlowNode startNode = StreamUtils.findFirstValue(flowCombine.getAllNodes(), node -> NodeType.isStart(node.getNodeType()));
// 获取下一个节点如果是网关节点则重新获取后续节点
PathWayData pathWayData = new PathWayData().setInsId(instanceId).setSkipType(flowParams.getSkipType());
FlowNode nextNode = FlowEngine.nodeService().getNextNode(startNode, null, SkipType.PASS.getKey()
, null, flowCombine);
List<FlowNode> nextNodes = FlowEngine.nodeService().getNextByCheckGateway(flowParams.getVariable(), nextNode
, pathWayData, flowCombine);
pathWayData.getTargetNodes().addAll(nextNodes);
// 设置流程图元数据
instance.setDefJson(FlowEngine.chartService().skipMetadata(pathWayData));
// 查询任务,如果前一个节点是并行网关可能任务表有多个任务,增加查询和判断
List<FlowTask> curTaskList = list(new FlowTask().setInstanceId(instance.getId()));
AssertUtil.isEmpty(curTaskList, ExceptionCons.NOT_FOUND_FLOW_TASK);
// 给回退到的那个节点赋权限-给当前处理人权限
List<FlowTask> addTasks = StreamUtils.toList(nextNodes, node -> addTask(node, instance, definition, flowParams));
// 办理人变量替换
ExpressionUtil.evalVariable(addTasks, flowParams.variable(MapUtil.mergeAll(instance.getVariableMap(), flowParams.getVariable())));
// 执行分派监听器
taskList.forEach(task -> ListenerUtil.executeAssignment(new ListenerVariable(definition, instance,
nodeMap.get(task.getNodeCode()), flowParams.getVariable(), task, nextNodes, addTasks)
.setFlowParams(flowParams)));
// 设置流程历史任务信息
List<FlowHisTask> insHisList = FlowEngine.hisTaskService().setSkipHisList(curTaskList, nextNodes, flowParams);
FlowEngine.hisTaskService().saveBatch(insHisList);
// 待办任务和处理人
removeAndUser(curTaskList);
List<FlowUser> users = FlowEngine.userService().taskAddUsers(addTasks);
// 设置任务完成后的实例相关信息
setInsFinishInfo(instance, addTasks, flowParams);
if (CollUtil.isNotEmpty(addTasks)) {
saveBatch(addTasks);
}
FlowEngine.insService().updateById(instance);
// 保存下一个待办任务的权限人
FlowEngine.userService().saveBatch(users);
// 执行完成和创建监听器
taskList.forEach(task -> ListenerUtil.endCreateListener(new ListenerVariable(definition, instance,
nodeMap.get(task.getNodeCode()), flowParams.getVariable(), task, nextNodes, addTasks).setFlowParams(flowParams)));
return instance;
}
public FlowInstance terminationByInsId(Long instanceId, FlowParams flowParams) {
AssertUtil.isNull(instanceId, ExceptionCons.NULL_INSTANCE_ID);
// 获取待办任务
List<FlowTask> taskList = FlowEngine.taskService().getByInsId(instanceId);
AssertUtil.isEmpty(taskList, ExceptionCons.NOT_FOUNT_TASK);
FlowTask task = taskList.get(0);
return termination(task, flowParams);
}
public FlowInstance termination(Long taskId, FlowParams flowParams) {
return termination(getById(taskId), flowParams);
}
public FlowInstance termination(FlowTask task, FlowParams flowParams) {
R r = getAndCheck(task);
flowParams.skipType(SkipType.PASS.getKey());
flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable()));
ListenerUtil.executeStart(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable()
, task).setFlowParams(flowParams));
// 判断当前处理人是否有权限处理
task.setUserList(FlowEngine.userService().listByAssociatedAndTypes(task.getId()));
checkAuth(task, flowParams);
// 所有待办转历史
FlowNode endNode = FlowEngine.nodeService().getEndNode(r.instance.getDefinitionId());
// 设置流程图元数据
PathWayData pathWayData = new PathWayData()
.setInsId(task.getInstanceId())
.setSkipType(flowParams.getSkipType())
.setPathWayNodes(Collections.singletonList(r.nowNode))
.setTargetNodes(Collections.singletonList(endNode));
r.instance.setDefJson(FlowEngine.chartService().skipMetadata(pathWayData));
// 流程实例完成
r.instance.setNodeType(endNode.getNodeType())
.setNodeCode(endNode.getNodeCode())
.setNodeName(endNode.getNodeName())
.setFlowStatus(StrUtil.emptyToDefault(flowParams.getFlowStatus(), FlowStatus.TERMINATE.getKey()));
// 待办任务转历史
flowParams.flowStatus(r.instance.getFlowStatus());
FlowHisTask insHis = FlowEngine.hisTaskService().setSkipInsHis(task, Collections.singletonList(endNode)
, flowParams);
FlowEngine.hisTaskService().save(insHis);
FlowEngine.insService().updateById(r.instance);
// 删除流程相关办理人
FlowEngine.userService().deleteByTaskIds(Collections.singletonList(task.getId()));
// 处理未完成的任务当流程完成还存在待办任务未完成转历史任务状态完成
handUndoneTask(r.instance);
// 最后判断是否存在节点监听器存在执行节点监听器
ListenerUtil.executeFinish(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable()
, task).setFlowParams(flowParams));
return r.instance;
}
public boolean deleteByInsIds(List<Long> instanceIds) {
List<FlowInstance> instanceList = FlowEngine.insService().getByIds(instanceIds);
FlowDefinition definition;
for (FlowInstance instance : instanceList) {
definition = FlowEngine.defService().getById(instance.getDefinitionId());
AssertUtil.isFalse(judgeActivityStatus(definition, instance), ExceptionCons.NOT_ACTIVITY);
}
return SqlHelper.retBool(getMapper().deleteByInsIds(instanceIds));
}
public boolean transfer(Long taskId, FlowParams flowParams) {
AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID);
AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY);
AssertUtil.isNull(flowParams.getAddHandlers(), ExceptionCons.NULL_TRANSFER_HANDLER);
List<FlowUser> users = FlowEngine.userService().getByProcessedBys(taskId, flowParams.getAddHandlers(), UserType.TRANSFER.getKey());
AssertUtil.isNotEmpty(users, ExceptionCons.IS_ALREADY_TRANSFER);
flowParams.cooperateType(CooperateType.TRANSFER.getKey())
.reductionHandlers(Collections.singletonList(flowParams.getHandler()));
return updateHandler(taskId, flowParams);
}
public boolean depute(Long taskId, FlowParams flowParams) {
AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID);
AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY);
AssertUtil.isNull(flowParams.getAddHandlers(), ExceptionCons.NULL_DEPUTE_HANDLER);
List<FlowUser> users = FlowEngine.userService().getByProcessedBys(taskId, flowParams.getAddHandlers(), UserType.DEPUTE.getKey());
AssertUtil.isNotEmpty(users, ExceptionCons.IS_ALREADY_DEPUTE);
flowParams.cooperateType(CooperateType.DEPUTE.getKey())
.reductionHandlers(Collections.singletonList(flowParams.getHandler()));
return updateHandler(taskId, flowParams);
}
public boolean addSignature(Long taskId, FlowParams flowParams) {
AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID);
AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY);
AssertUtil.isNull(flowParams.getAddHandlers(), ExceptionCons.NULL_ADD_SIGNATURE_HANDLER);
List<FlowUser> users = FlowEngine.userService().getByProcessedBys(taskId, flowParams.getAddHandlers(), UserType.APPROVAL.getKey());
AssertUtil.isNotEmpty(users, ExceptionCons.IS_ALREADY_SIGN);
flowParams.cooperateType(CooperateType.ADD_SIGNATURE.getKey());
return updateHandler(taskId, flowParams);
}
public boolean reductionSignature(Long taskId, FlowParams flowParams) {
AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID);
AssertUtil.isNull(flowParams.getHandler(), ExceptionCons.HANDLER_NOT_EMPTY);
AssertUtil.isNull(flowParams.getReductionHandlers(), ExceptionCons.NULL_REDUCTION_SIGNATURE_HANDLER);
List<FlowUser> users = FlowEngine.userService().listByAssociatedAndTypes(taskId
, UserType.APPROVAL.getKey(), UserType.TRANSFER.getKey());
AssertUtil.isTrue(CollUtil.isEmpty(users) || users.size() == 1, ExceptionCons.REDUCTION_SIGN_ONE_ERROR);
flowParams.cooperateType(CooperateType.REDUCTION_SIGNATURE.getKey());
return updateHandler(taskId, flowParams);
}
public boolean updateHandler(Long taskId, FlowParams flowParams) {
// 获取待办任务
R r = getAndCheck(taskId);
flowParams.variable(MapUtil.mergeAll(r.instance.getVariableMap(), flowParams.getVariable()));
// 执行开始监听器
ListenerUtil.executeStart(new ListenerVariable(r.definition, r.instance, r.nowNode, null, r.task));
// 获取给谁的权限
if (!flowParams.isIgnore()) {
// 判断当前处理人是否有权限获取当前办理人的权限
List<String> permissions = flowParams.getPermissionFlag();
// 获取任务权限人
List<String> taskPermissions = FlowEngine.userService().getPermission(taskId
, UserType.APPROVAL.getKey(), UserType.TRANSFER.getKey(), UserType.DEPUTE.getKey());
AssertUtil.isTrue(CollUtil.isNotEmpty(taskPermissions) && (CollUtil.isEmpty(permissions)
|| !CollUtil.containsAny(permissions, taskPermissions)), ExceptionCons.NOT_AUTHORITY);
}
// 留存历史记录
flowParams.skipType(SkipType.NONE.getKey());
FlowHisTask hisTask = null;
// 删除对应的操作人
if (CollUtil.isNotEmpty(flowParams.getReductionHandlers())) {
for (String reductionHandler : flowParams.getReductionHandlers()) {
FlowEngine.userService().remove(new FlowUser().setAssociated(taskId)
.setProcessedBy(reductionHandler));
}
hisTask = FlowEngine.hisTaskService().setCooperateHis(r.task, flowParams, flowParams.getReductionHandlers());
}
// 新增权限人
if (CollUtil.isNotEmpty(flowParams.getAddHandlers())) {
String type;
if (CooperateType.TRANSFER.getKey().equals(flowParams.getCooperateType())) {
type = UserType.TRANSFER.getKey();
} else if (CooperateType.DEPUTE.getKey().equals(flowParams.getCooperateType())) {
type = UserType.DEPUTE.getKey();
} else {
type = UserType.APPROVAL.getKey();
}
FlowEngine.userService().saveBatch(StreamUtils.toList(flowParams.getAddHandlers(), permission ->
FlowEngine.userService().structureUser(taskId, permission
, type, flowParams.getHandler())));
hisTask = FlowEngine.hisTaskService().setCooperateHis(r.task, flowParams, flowParams.getAddHandlers());
}
if (ObjectUtil.isNotNull(hisTask)) {
FlowEngine.hisTaskService().save(hisTask);
}
// 最后判断是否存在节点监听器存在执行节点监听器
ListenerUtil.executeFinish(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable()
, r.task));
return true;
}
public FlowInstance pendingByInsId(Long instanceId, FlowParams flowParams) {
return pending(getTask(instanceId), flowParams);
}
public FlowInstance pending(Long taskId, FlowParams flowParams) {
// 获取待办任务
FlowTask task = getById(taskId);
return pending(task, flowParams);
}
public FlowInstance pending(FlowTask task, FlowParams flowParams) {
// TODO min 后续考虑并发问题待办任务和实例表不同步可给待办任务id加锁抽取所接口方便后续兼容分布式锁
// 流程开启前正确性校验
R r = getAndCheck(task);
flowParams.flowStatus(StrUtil.emptyToDefault(flowParams.getFlowStatus(), FlowStatus.PENDING.getKey()));
// 执行开始监听器
ListenerUtil.executeStart(new ListenerVariable(r.definition, r.instance, r.nowNode, flowParams.getVariable()
, r.task).setFlowParams(flowParams));
// 判断当前处理人是否有权限处理
checkAuth(r.task, flowParams);
// 设置流程历史任务信息
FlowHisTask insHis = FlowEngine.hisTaskService().notSkip(r.task, flowParams);
FlowEngine.hisTaskService().save(insHis);
FlowEngine.insService().updateById(r.instance.setFlowStatus(flowParams.getFlowStatus()));
// 执行任务完成监听器
ListenerUtil.executeFinish(new ListenerVariable(r.definition, r.instance, r.nowNode
, flowParams.getVariable(), r.task));
return r.instance;
}
public FlowTask addTask(FlowNode node, FlowInstance instance, FlowDefinition definition, FlowParams flowParams) {
FlowTask addTask = new FlowTask();
Date now = new Date();
FlowEngine.dataFillHandler().idFill(addTask);
addTask.setDefinitionId(instance.getDefinitionId())
.setInstanceId(instance.getId())
.setNodeCode(node.getNodeCode())
.setNodeName(node.getNodeName())
.setNodeType(node.getNodeType())
.setFlowStatus(StrUtil.emptyToDefault(flowParams.getFlowStatus(),
setFlowStatus(node.getNodeType(), flowParams.getSkipType())))
.setCreateTime(now)
.setPermissionList(StrUtil.splitTrim(node.getPermissionFlag(), FlowCons.SPLIT_AT));
if (StrUtil.isNotEmpty(node.getFormCustom()) && StrUtil.isNotEmpty(node.getFormPath())) {
// 节点有自定义表单则使用
addTask.setFormCustom(node.getFormCustom()).setFormPath(node.getFormPath());
} else {
addTask.setFormCustom(definition.getFormCustom()).setFormPath(definition.getFormPath());
}
return addTask;
}
public List<FlowTask> getByInsId(Long instanceId) {
return list(new FlowTask().setInstanceId(instanceId));
}
public List<FlowTask> getByInsIdAndNodeCodes(Long instanceId, List<String> nodeCodes) {
return getMapper().getByInsIdAndNodeCodes(instanceId, nodeCodes);
}
public void setInsFinishInfo(FlowInstance instance, List<FlowTask> addTasks, FlowParams flowParams) {
instance.setUpdateTime(new Date());
// 合并流程变量到实例对象
mergeVariable(instance, flowParams.getVariable());
if (CollUtil.isNotEmpty(addTasks)) {
// 终结节点任务不算待办任务取其中最后一个作为实例最终信息
List<FlowTask> endTasks = addTasks.stream()
.filter(addTask -> NodeType.isEnd(addTask.getNodeType()))
.collect(Collectors.toList());
addTasks.removeAll(endTasks);
FlowTask finallyTask = CollUtil.getLast(endTasks);
if (finallyTask == null) {
finallyTask = getNextTask(addTasks);
}
instance.setNodeType(finallyTask.getNodeType())
.setNodeCode(finallyTask.getNodeCode())
.setNodeName(finallyTask.getNodeName())
.setFlowStatus(finallyTask.getFlowStatus());
}
}
public void mergeVariable(FlowInstance instance, Map<String, Object> variable) {
if (MapUtil.isNotEmpty(variable)) {
String variableStr = instance.getVariable();
Map<String, Object> deserialize = JsonUtil.strToMap(variableStr);
deserialize.putAll(variable);
instance.setVariable(JsonUtil.objToStr(deserialize));
}
}
/**
* 根据流程实例id获取操作人最近的已办历史任务
*
* @param flowParams 包含流程相关参数的对象
* @param instanceId 流程实例id
* @return 最近的已办历史任务
*/
private FlowHisTask taskBack(FlowParams flowParams, Long instanceId) {
flowParams.skipType(SkipType.REJECT.getKey())
.ignore(true)
.ignoreDepute(true)
.ignoreCooperate(true)
.flowStatus(StrUtil.emptyToDefault(flowParams.getFlowStatus(), FlowStatus.TASK_BACK.getKey()));
// 获取当前任务的前置任务
List<FlowHisTask> hisTaskList = FlowEngine.hisTaskService().getByInsId(instanceId);
// 获取hisTaskList中TargetNodeCod等于task.getNodeCode()并且id最大的
FlowHisTask lastHisTask = hisTaskList.stream()
.filter(hisTask -> StrUtil.isNotEmpty(hisTask.getApprover()))
.filter(hisTask -> SkipType.isPass(hisTask.getSkipType()))
.filter(hisTask -> hisTask.getApprover().equals(flowParams.getHandler()))
.max(Comparator.comparingLong(FlowHisTask::getId))
.orElse(null);
AssertUtil.isNull(lastHisTask, ExceptionCons.NOT_FOUNT_HANDLED_TASK);
flowParams.nodeCode(lastHisTask.getNodeCode());
return lastHisTask;
}
/**
* 获取待办任务
*
* @param instanceId 实例id
* @return 待办任务
*/
private FlowTask getTask(Long instanceId) {
List<FlowTask> taskList = getByInsId(instanceId);
AssertUtil.isEmpty(taskList, ExceptionCons.NOT_FOUNT_TASK);
AssertUtil.isTrue(taskList.size() > 1, ExceptionCons.TASK_NOT_ONE);
return taskList.get(0);
}
private String setFlowStatus(Integer nodeType, String skipType) {
// 根据审批动作确定流程状态
if (NodeType.isStart(nodeType)) {
return FlowStatus.TOBESUBMIT.getKey();
} else if (NodeType.isEnd(nodeType)) {
return FlowStatus.FINISHED.getKey();
} else if (SkipType.isReject(skipType)) {
return FlowStatus.REJECT.getKey();
} else {
return FlowStatus.APPROVAL.getKey();
}
}
private FlowTask getNextTask(List<FlowTask> tasks) {
if (tasks.size() == 1) {
return tasks.get(0);
}
return tasks.stream().max(Comparator.comparingLong(FlowTask::getId)).orElse(null);
}
private void removeAndUser(List<FlowTask> taskList) {
removeByIds(StreamUtils.toList(taskList, FlowTask::getId));
FlowEngine.userService().deleteByTaskIds(StreamUtils.toList(taskList, FlowTask::getId));
}
private R getAndCheck(Long taskId) {
AssertUtil.isNull(taskId, ExceptionCons.NULL_TASK_ID);
return getAndCheck(getById(taskId));
}
private R getAndCheck(FlowTask task) {
AssertUtil.isNull(task, ExceptionCons.NOT_FOUNT_TASK);
FlowInstance instance = FlowEngine.insService().getById(task.getInstanceId());
AssertUtil.isNull(instance, ExceptionCons.NOT_FOUNT_INSTANCE);
FlowDefinition definition = FlowEngine.defService().getById(instance.getDefinitionId());
AssertUtil.isFalse(judgeActivityStatus(definition, instance), ExceptionCons.NOT_ACTIVITY);
AssertUtil.isTrue(NodeType.isEnd(instance.getNodeType()), ExceptionCons.FLOW_FINISH);
FlowNode nowNode = FlowEngine.nodeService().getByDefIdAndNodeCode(task.getDefinitionId(), task.getNodeCode());
AssertUtil.isNull(nowNode, ExceptionCons.LOST_CUR_NODE);
return new R(instance, definition, nowNode, task);
}
private static class R {
public final FlowInstance instance;
public final FlowDefinition definition;
public final FlowNode nowNode;
public final FlowTask task;
public R(FlowInstance instance, FlowDefinition definition, FlowNode nowNode, FlowTask task) {
this.instance = instance;
this.definition = definition;
this.nowNode = nowNode;
this.task = task;
}
}
private boolean handleDepute(FlowTask task, FlowParams flowParams) {
// 获取受托人
List<FlowUser> entrustedUserList = StreamUtils.filter(task.getUserList(),
user -> UserType.DEPUTE.getKey().equals(user.getType())
&& Objects.equals(flowParams.getHandler(), user.getProcessedBy()));
if (CollUtil.isEmpty(entrustedUserList)) {
return false;
}
// 记录受托人处理任务记录
FlowUser entrustedUser = entrustedUserList.get(0);
FlowHisTask hisTask = FlowEngine.hisTaskService().setDeputeHisTask(task, flowParams, entrustedUser);
FlowEngine.hisTaskService().save(hisTask);
FlowEngine.userService().removeById(entrustedUser.getId());
// 查询委托人如果在flow_user不存在则给委托人新增待办记录
FlowUser deputeUser = FlowEngine.userService().getOne(new FlowUser().setAssociated(task.getId())
.setProcessedBy(entrustedUser.getCreateBy()).setType(UserType.APPROVAL.getKey()));
if (ObjectUtil.isNull(deputeUser)) {
FlowUser newUser = FlowEngine.userService().structureUser(entrustedUser.getAssociated()
, entrustedUser.getCreateBy()
, UserType.APPROVAL.getKey(), entrustedUser.getProcessedBy());
FlowEngine.userService().save(newUser);
}
return true;
}
/**
* 会签票签协作处理返回true或签或者会签票签结束返回false
*
* @param nowNode 当前节点
* @param task 任务
* @param flowParams 流程参数
* @return boolean
*/
private boolean cooperate(FlowNode nowNode, FlowTask task, FlowParams flowParams) {
if (flowParams.isIgnore()) {
return false;
}
String nodeRatio = nowNode.getNodeRatio();
// 或签直接返回
if (CooperateType.isOrSign(nodeRatio)) {
return false;
}
// 办理人和转办人列表
List<FlowUser> todoList = FlowEngine.userService().listByAssociatedAndTypes(task.getId()
, UserType.APPROVAL.getKey(), UserType.TRANSFER.getKey(), UserType.DEPUTE.getKey());
// 判断办理人是否有办理权限
AssertUtil.isEmpty(flowParams.getHandler(), ExceptionCons.SIGN_NULL_HANDLER);
FlowUser todoUser = CollUtil.getFirst(StreamUtils.filter(todoList, u -> Objects.equals(u.getProcessedBy(), flowParams.getHandler())));
AssertUtil.isNull(todoUser, ExceptionCons.NOT_AUTHORITY);
// 除当前办理人外剩余办理人列表
List<FlowUser> restList = StreamUtils.filter(todoList, u -> !Objects.equals(u.getProcessedBy(), flowParams.getHandler()));
// 会签并且当前人退回直接返回
if (CooperateType.isCountersign(nodeRatio) && SkipType.isReject(flowParams.getSkipType())) {
return removeRestList(restList);
}
// 查询会签票签已办列表
List<FlowHisTask> doneList = FlowEngine.hisTaskService().listByTaskId(task.getId());
doneList = CollUtil.isEmpty(doneList) ? new ArrayList<>() : doneList;
// 总人数
int allNum = todoList.size() + doneList.size();
// 通过历史记录
List<FlowHisTask> donePassList = StreamUtils.filter(doneList
, hisTask -> Objects.equals(hisTask.getSkipType(), SkipType.PASS.getKey()));
// 驳回历史记录
List<FlowHisTask> doneRejectList = StreamUtils.filter(doneList
, hisTask -> Objects.equals(hisTask.getSkipType(), SkipType.REJECT.getKey()));
boolean isPass = SkipType.isPass(flowParams.getSkipType());
// 如果是票签默认或者spel表达式策略则执行表达式
if (CooperateType.isVoteSignDefault(nodeRatio) || CooperateType.isVoteSignRejectSpel(nodeRatio)) {
Map<String, Object> variable = MapUtil.clone(flowParams.getVariable());
variable.put("skipType", flowParams.getSkipType());
variable.put("passNum", donePassList.size());
variable.put("rejectNum", doneRejectList.size());
variable.put("todoNum", todoList.size());
variable.put("allNum", allNum);
variable.put("passList", donePassList);
variable.put("rejectList", doneRejectList);
variable.put("todoList", todoList);
if (ExpressionUtil.evalVoteSign(nodeRatio, variable)) {
return removeRestList(restList);
}
} else {
// 计算通过率
BigDecimal passRatio = (isPass ? BigDecimal.ONE : BigDecimal.ZERO)
.add(BigDecimal.valueOf(donePassList.size()))
.divide(BigDecimal.valueOf(allNum), 4, RoundingMode.HALF_UP).multiply(MathUtil.ONE_HUNDRED);
// 计算驳回率
BigDecimal rejectRatio = (isPass ? BigDecimal.ZERO : BigDecimal.ONE)
.add(BigDecimal.valueOf(doneRejectList.size()))
.divide(BigDecimal.valueOf(allNum), 4, RoundingMode.HALF_UP).multiply(MathUtil.ONE_HUNDRED);
// 判断是否是票签中的固定通过人数如果是则判断是否达到该人数
if (CooperateType.isVoteSignPassCount(nodeRatio)) {
String passCount = StrUtil.subSuf(nodeRatio, nodeRatio.indexOf("=") + 1);
if ((isPass && donePassList.size() + 1 >= Integer.parseInt(passCount))
|| (!isPass && doneRejectList.size() + 1 > allNum - Integer.parseInt(passCount))) {
return removeRestList(restList);
}
} else if (CooperateType.isVoteSignRejectCount(nodeRatio)) {
// 判断是否是票签中的固定驳回人数如果是则判断是否达到该人数
String rejectCount = StrUtil.subSuf(nodeRatio, nodeRatio.indexOf("=") + 1);
if ((!isPass && doneRejectList.size() + 1 >= Integer.parseInt(rejectCount))
|| (isPass && donePassList.size() + 1 > allNum - Integer.parseInt(rejectCount))) {
return removeRestList(restList);
}
} else if ((!isPass && rejectRatio.compareTo(MathUtil.ONE_HUNDRED.subtract(new BigDecimal(nodeRatio))) > 0)
|| (isPass && passRatio.compareTo(new BigDecimal(nodeRatio)) >= 0)) {
// 提前不满足通过率或者满足通过率删除剩余办理人流程正常流程流转
return removeRestList(restList);
}
}
// 当只剩一位待办用户时由当前用户决定走向
if (todoList.size() == 1) {
return false;
}
// 添加历史任务
FlowHisTask hisTask = FlowEngine.hisTaskService().setSignHisTask(task, flowParams, nodeRatio, isPass);
FlowEngine.hisTaskService().save(hisTask);
// 删掉待办用户
FlowEngine.userService().removeById(todoUser.getId());
return true;
}
/**
* 删除剩余办理人
* @param restList 待办用户列表
* @return boolean
*/
private static boolean removeRestList(List<FlowUser> restList) {
if (CollUtil.isNotEmpty(restList)) {
FlowEngine.userService().removeByIds(StreamUtils.toList(restList, FlowUser::getId));
}
return false;
}
/**
* 判断并行网关和包容网关节点只剩一个前置代办任务才能生成新的代办任务
*
* @param pathWayData 办理过程中途径数据
* @param instance 实例
*/
private void isGenerateNewTask(PathWayData pathWayData, FlowInstance instance, List<FlowNode> nextNodes) {
if (SkipType.isReject(pathWayData.getSkipType())) {
return;
}
DefJson defJson = JsonUtil.strToBean(instance.getDefJson(), DefJson.class);
Map<String, NodeJson> nodeJsonMap = StreamUtils.toMap(defJson.getNodeList(), NodeJson::getNodeCode, node -> node);
// 途径节点中的并行/包容网关只剩一个前置待办任务时才能生成新的代办任务
List<FlowNode> parallelOrInclusiveList = pathWayData.getPathWayNodes().stream()
.filter(t -> NodeType.isGateWayParallel(t.getNodeType()) || NodeType.isGateWayInclusive(t.getNodeType()))
.collect(Collectors.toList());
if (CollUtil.isEmpty(parallelOrInclusiveList)) {
return;
}
List<FlowNode> previousNodeList = FlowEngine.nodeService().previousNodeList(instance.getDefinitionId()
, parallelOrInclusiveList.get(parallelOrInclusiveList.size() - 1).getNodeCode());
// 前置节点中处于待办状态的数量
long toDoCount = previousNodeList.stream()
.map(FlowNode::getNodeCode)
.map(nodeJsonMap::get)
.filter(Objects::nonNull)
.filter(nodeJson -> ChartStatus.isToDo(nodeJson.getStatus()))
.count();
// 并行网关和包容网关还有多个前置待办任务不可生成新的代办任务
if (toDoCount <= 1) {
return;
}
nextNodes.clear();
String gatewayCode = parallelOrInclusiveList.get(0).getNodeCode();
// 途径节点保留首个网关自身其后的全部移除
List<FlowNode> pathWayNodes = pathWayData.getPathWayNodes();
for (int i = 0; i < pathWayNodes.size(); i++) {
if (pathWayNodes.get(i).getNodeCode().equals(gatewayCode)) {
pathWayNodes.subList(i + 1, pathWayNodes.size()).clear();
break;
}
}
// 途径连线从首个网关出发的全部移除
List<FlowSkip> pathWaySkips = pathWayData.getPathWaySkips();
for (int i = 0; i < pathWaySkips.size(); i++) {
if (pathWaySkips.get(i).getNowNodeCode().equals(gatewayCode)) {
pathWaySkips.subList(i, pathWaySkips.size()).clear();
break;
}
}
}
/**
* 判断当前处理人是否有权限处理
*
* @param task 当前任务任务id
* @param flowParams:包含流程相关参数的对象
*/
private void checkAuth(FlowTask task, FlowParams flowParams) {
if (flowParams.isIgnore()) {
return;
}
// 查询审批人和转办人
List<String> permissions = StreamUtils.toList(task.getUserList(), FlowUser::getProcessedBy);
// 当前办理人拥有的权限和设计时候填的权限集合是否有交集有说明有权限办理
AssertUtil.isTrue(CollUtil.isNotEmpty(permissions) && (CollUtil.isEmpty(flowParams.getPermissionFlag())
|| !CollUtil.containsAny(flowParams.getPermissionFlag(), permissions)), ExceptionCons.NULL_ROLE_NODE);
}
/**
* 一票否决谨慎使用如果退回退回指向节点后还存在其他正在执行的待办任务转历史任务状态都为退回,重走流程
*
* @param task 当前任务
* @param nextNodeCode 下一个节点编码
* @param flowCombine 流程数据集合
*/
private void oneVoteVeto(FlowTask task, String nextNodeCode, FlowCombine flowCombine) {
// 一票否决谨慎使用如果退回退回指向节点后还存在其他正在执行的待办任务转历史任务状态失效,重走流程
List<FlowTask> tasks = list(new FlowTask().setInstanceId(task.getInstanceId()));
// 属于退回指向节点的后置未完成的任务
List<FlowTask> noDoneTasks = new ArrayList<>();
List<FlowNode> suffixNodeList = FlowEngine.nodeService().suffixNodeList(nextNodeCode, flowCombine);
List<String> suffixCodes = StreamUtils.toList(suffixNodeList, FlowNode::getNodeCode);
for (FlowTask flowTask : tasks) {
if (suffixCodes.contains(flowTask.getNodeCode())) {
noDoneTasks.add(flowTask);
}
}
if (CollUtil.isNotEmpty(noDoneTasks)) {
removeAndUser(noDoneTasks);
}
}
/**
* 处理未完成的任务当流程完成还存在待办任务未完成转历史任务状态完成
*
* @param instance 流程实例
*/
private void handUndoneTask(FlowInstance instance) {
if (NodeType.isEnd(instance.getNodeType())) {
List<FlowTask> taskList = list(new FlowTask().setInstanceId(instance.getId()));
if (CollUtil.isNotEmpty(taskList)) {
removeAndUser(taskList);
}
}
}
/**
* 更新流程信息
*
* @param task 当前任务
* @param instance 流程实例
* @param addTasks 新增待办任务
* @param flowParams 包含流程相关参数的对象
* @param nextNodes 下一个节点集合
*/
private void updateFlowInfo(FlowTask task, FlowInstance instance, List<FlowTask> addTasks, FlowParams flowParams
, List<FlowNode> nextNodes) {
// 设置流程历史任务信息
FlowHisTask insHis = FlowEngine.hisTaskService().setSkipInsHis(task, nextNodes, flowParams);
FlowEngine.hisTaskService().save(insHis);
removeAndUser(Collections.singletonList(task));
// 待办任务设置处理人
List<FlowUser> users = FlowEngine.userService().taskAddUsers(addTasks);
// 设置任务完成后的实例相关信息
setInsFinishInfo(instance, addTasks, flowParams);
if (CollUtil.isNotEmpty(addTasks)) {
saveBatch(addTasks);
}
FlowEngine.insService().updateById(instance);
// 保存下一个待办任务的权限人
FlowEngine.userService().saveBatch(users);
}
private boolean judgeActivityStatus(FlowDefinition definition, FlowInstance instance) {
return ActivityStatus.isActivity(definition.getActivityStatus())
&& ActivityStatus.isActivity(instance.getActivityStatus());
}
public FlowDto load(Long taskId, FlowParams flowParams) {
R r = getAndCheck(taskId);
FlowDto flowDto = new FlowDto();
flowDto.setData(r.instance.getVariableMap().get(FlowCons.FORM_DATA));
return flowDto;
}
public FlowDto hisLoad(Long hisTaskId, FlowParams flowParams) {
FlowHisTask hisTask = FlowEngine.hisTaskService().getById(hisTaskId);
AssertUtil.isNull(hisTask, ExceptionCons.NOT_FOUND_FLOW_TASK);
FlowDefinition definition = FlowEngine.defService().getById(hisTask.getDefinitionId());
AssertUtil.isNull(definition, ExceptionCons.NOT_FOUNT_DEF);
FlowNode nowNode = CollUtil.getFirst(FlowEngine.nodeService()
.getMapper().getByNodeCodes(Collections.singletonList(hisTask.getNodeCode()), hisTask.getDefinitionId()));
AssertUtil.isNull(nowNode, ExceptionCons.LOST_CUR_NODE);
FlowDto flowDto = new FlowDto();
flowDto.setData(hisTask.getVariableMap().get(FlowCons.FORM_DATA));
return flowDto;
}
@Override
public FlowTaskMapper getMapper() {
return SpringUtils.getBean(FlowTaskMapper.class);
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import org.dromara.warm.flow.entity.*;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.entity.FlowTask;
import org.dromara.warm.flow.entity.FlowUser;
import org.dromara.warm.flow.enums.UserType;
import org.dromara.warm.flow.service.WarmServiceImpl;
import org.dromara.common.core.utils.StreamUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.dromara.common.core.utils.SpringUtils;
import org.springframework.stereotype.Service;
import org.dromara.warm.flow.mapper.FlowUserMapper;
/**
* 流程用户Service业务层处理
*
* @author xiarg
* @since 2024/5/10 13:57
*/
@Service
public class UserService extends WarmServiceImpl<FlowUser> {
public List<FlowUser> taskAddUsers(List<FlowTask> addTasks) {
List<FlowUser> taskUserList = new ArrayList<>();
if (CollUtil.isNotEmpty(addTasks)) {
StreamUtils.toList(addTasks, task -> taskUserList.addAll(taskAddUser(task)));
}
return taskUserList;
}
public List<FlowUser> taskAddUser(FlowTask task) {
// 遍历权限集合生成流程节点的权限
List<FlowUser> userList = StreamUtils.toList(task.getPermissionList()
, permission -> structureUser(task.getId(), permission, UserType.APPROVAL.getKey()));
task.setUserList(userList);
return userList;
}
public void deleteByTaskIds(List<Long> ids) {
getMapper().deleteByTaskIds(ids);
}
public List<String> getPermission(Long associated, String... types) {
if (ArrayUtil.isEmpty(types)) {
return StreamUtils.toList(list(new FlowUser().setAssociated(associated)), FlowUser::getProcessedBy);
}
if (types.length == 1) {
return StreamUtils.toList(list(new FlowUser().setAssociated(associated).setType(types[0]))
, FlowUser::getProcessedBy);
}
return StreamUtils.toList(getMapper().listByAssociatedAndTypes(Collections.singletonList(associated), types)
, FlowUser::getProcessedBy);
}
public List<FlowUser> listByAssociatedAndTypes(Long associated, String... types) {
if (ArrayUtil.isEmpty(types)) {
return list(new FlowUser().setAssociated(associated));
}
if (types.length == 1) {
return list(new FlowUser().setAssociated(associated).setType(types[0]));
}
return getMapper().listByAssociatedAndTypes(Collections.singletonList(associated), types);
}
public List<FlowUser> getByAssociateds(List<Long> associateds, String... types) {
if (CollUtil.isNotEmpty(associateds) && associateds.size() == 1) {
return listByAssociatedAndTypes(associateds.get(0), types);
}
return getMapper().listByAssociatedAndTypes(associateds, types);
}
public List<FlowUser> listByProcessedBys(Long associated, String processedBy, String... types) {
if (ArrayUtil.isEmpty(types)) {
return list(new FlowUser().setAssociated(associated).setProcessedBy(processedBy));
}
if (types.length == 1) {
return list(new FlowUser().setAssociated(associated).setProcessedBy(processedBy).setType(types[0]));
}
return getMapper().listByProcessedBys(associated, Collections.singletonList(processedBy), types);
}
public List<FlowUser> getByProcessedBys(Long associated, List<String> processedBys, String... types) {
if (CollUtil.isNotEmpty(processedBys) && processedBys.size() == 1) {
return listByProcessedBys(associated, processedBys.get(0), types);
}
return getMapper().listByProcessedBys(associated, processedBys, types);
}
public boolean updatePermission(Long associated, List<String> permissions, String type, boolean clear,
String handler) {
// 判断是否clear如果是true则先删除当前关联id用户数据
if (clear) {
getMapper().delete(new FlowUser().setAssociated(associated).setCreateBy(handler));
}
// 再新增权限人
saveBatch(StreamUtils.toList(permissions, permission -> structureUser(associated, permission, type, handler)));
return true;
}
public List<FlowUser> structureUser(Long associated, List<String> permissionList, String type) {
return StreamUtils.toList(permissionList, permission -> structureUser(associated, permission, type, null));
}
public FlowUser structureUser(Long associated, String permission, String type) {
return structureUser(associated, permission, type, null);
}
public List<FlowUser> structureUser(Long associated, List<String> permissionList, String type, String handler) {
return StreamUtils.toList(permissionList, permission -> structureUser(associated, permission, type, handler));
}
public FlowUser structureUser(Long associated, String permission, String type, String handler) {
Date now = new Date();
FlowUser user = new FlowUser()
.setType(type)
.setProcessedBy(permission)
.setAssociated(associated)
.setCreateBy(handler);
FlowEngine.dataFillHandler().idFill(user);
return user;
}
@Override
public FlowUserMapper getMapper() {
return SpringUtils.getBean(FlowUserMapper.class);
}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.service;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.entity.RootEntity;
import org.dromara.warm.flow.handler.DataFillHandler;
import org.dromara.warm.flow.mapper.WarmMapper;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* BaseService层处理直接持有 Mapper无独立 Dao
*
* @author warm
* @since 2023-03-17
*/
public abstract class WarmServiceImpl<T extends RootEntity> implements IWarmService<T> {
/**
* 获取Mapper由子类提供Mapper MyBatis 扫描 mapper 包注册
*/
public abstract WarmMapper<T> getMapper();
@Override
public T getById(Serializable id) {
return getMapper().selectById(id);
}
@Override
public List<T> getByIds(Collection<? extends Serializable> ids) {
return getMapper().selectBatchIds(ids);
}
@Override
public List<T> list(T entity) {
return getMapper().selectList(entity);
}
@Override
public T getOne(T entity) {
List<T> list = getMapper().selectList(entity);
return CollUtil.getFirst(list);
}
@Override
public Boolean exists(T entity) {
return getMapper().selectCount(entity) > 0;
}
@Override
public boolean save(T entity) {
insertFill(entity);
return SqlHelper.retBool(getMapper().insert(entity));
}
@Override
public boolean updateById(T entity) {
updateFill(entity);
return SqlHelper.retBool(getMapper().update(entity));
}
@Override
public boolean removeById(Serializable id) {
return SqlHelper.retBool(getMapper().deleteById(id));
}
@Override
public boolean remove(T entity) {
return SqlHelper.retBool(getMapper().delete(entity));
}
@Override
public boolean removeByIds(Collection<? extends Serializable> ids) {
return SqlHelper.retBool(getMapper().deleteBatchIds(ids));
}
@Override
public void saveBatch(List<T> list) {
if (CollUtil.isEmpty(list)) {
return;
}
list.forEach(this::save);
}
public void insertFill(T entity) {
DataFillHandler dataFillHandler = FlowEngine.dataFillHandler();
if (dataFillHandler == null) {
return;
}
dataFillHandler.idFill(entity);
dataFillHandler.insertFill(entity);
}
public void updateFill(T entity) {
DataFillHandler dataFillHandler = FlowEngine.dataFillHandler();
if (dataFillHandler == null) {
return;
}
dataFillHandler.updateFill(entity);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.strategy;
import org.dromara.warm.flow.constant.FlowCons;
import java.util.ArrayList;
import java.util.List;
/**
* 条件表达式接口
*
* @author warm
*/
public interface ConditionStrategy extends ExpressionStrategy<Boolean> {
/**
* 条件表达式策略实现类集合
*/
List<ExpressionStrategy<Boolean>> EXPRESSION_STRATEGY_LIST = new ArrayList<>();
@Override
default void setExpression(ExpressionStrategy<Boolean> expressionStrategy) {
EXPRESSION_STRATEGY_LIST.add(expressionStrategy);
}
@Override
default String interceptStr() {
return FlowCons.SPLIT_AT;
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.strategy;
import java.util.Map;
/**
* 表达式策略类接口
*
* @author warm
*/
public interface ExpressionStrategy<T> {
/**
* 获取策略类型
*
* @return 类型
*/
String getType();
/**
* 当选择截取并且希望拼接上某些字符串在进行截取
*
* @return 类型
*/
default String interceptStr() {
return "";
}
/**
* 执行表达式
*
* @param expression 表达式
* @param variable 流程变量
* @return 执行结果
*/
T eval(String expression, Map<String, Object> variable);
/**
* 设置表达式
*
* @param expressionStrategy 表达式
*/
void setExpression(ExpressionStrategy<T> expressionStrategy);
}

View File

@ -0,0 +1,62 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.strategy;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.common.core.utils.StreamUtils;
import java.util.*;
import java.util.stream.Collectors;
/**
* 办理人表达式策略接口
*
* @author warm,battcn
*/
public interface HandlerStrategy extends ExpressionStrategy<List<String>> {
/**
* 办理人表达式策略实现类集合
*/
List<ExpressionStrategy<List<String>>> EXPRESSION_STRATEGY_LIST = new ArrayList<>();
@Override
default void setExpression(ExpressionStrategy<List<String>> expressionStrategy) {
EXPRESSION_STRATEGY_LIST.add(expressionStrategy);
}
Object preEval(String expression, Map<String, Object> variable);
@Override
default List<String> eval(String expression, Map<String, Object> variable) {
return afterEval(preEval(expression, variable));
}
default List<String> afterEval(Object o) {
if (ObjectUtil.isNull(o)) {
return null;
}
if (o instanceof List) {
return StreamUtils.toList((List<?>) o, Object::toString);
}
if (o instanceof Object[]) {
return Arrays.stream((Object[]) o).map(Object::toString).collect(Collectors.toList());
}
return Collections.singletonList(o.toString());
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.strategy;
import java.util.ArrayList;
import java.util.List;
/**
* 监听器表达式策略接口
*
* @author warm,battcn
*/
public interface ListenerStrategy extends ExpressionStrategy<Boolean> {
/**
* 监听器表达式策略实现类集合
*/
List<ExpressionStrategy<Boolean>> EXPRESSION_STRATEGY_LIST = new ArrayList<>();
@Override
default void setExpression(ExpressionStrategy<Boolean> expressionStrategy) {
EXPRESSION_STRATEGY_LIST.add(expressionStrategy);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.strategy;
import org.dromara.warm.flow.constant.FlowCons;
import java.util.ArrayList;
import java.util.List;
/**
* 票签表达式接口
*
* @author warm
*/
public interface VoteSignStrategy extends ExpressionStrategy<Boolean> {
/**
* 票签表达式策略实现类集合
*/
List<ExpressionStrategy<Boolean>> EXPRESSION_STRATEGY_LIST = new ArrayList<>();
@Override
default void setExpression(ExpressionStrategy<Boolean> expressionStrategy) {
EXPRESSION_STRATEGY_LIST.add(expressionStrategy);
}
@Override
default String interceptStr() {
return FlowCons.SPLIT_AT;
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.config;
import org.dromara.warm.flow.ui.controller.WarmFlowController;
import org.dromara.warm.flow.ui.controller.WarmFlowUiController;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 工作流设计器配置类
*
* @author ruoyi
*/
@Configuration
@ConditionalOnProperty(value = "warm-flow.ui", havingValue = "true", matchIfMissing = true)
@Import({WarmFlowUiController.class
, WarmFlowController.class})
public class WarmFlowUiConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/** warm-flow配置 */
registry.addResourceHandler("/warm-flow-ui/**")
.addResourceLocations("classpath:/META-INF/resources/warm-flow-ui/", "classpath:/warm-flow-ui/");
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.controller;
import org.dromara.warm.flow.dto.ApiResult;
import org.dromara.warm.flow.dto.DefJson;
import org.dromara.warm.flow.dto.FlowDto;
import org.dromara.warm.flow.entity.FlowInstance;
import org.dromara.warm.flow.ui.dto.HandlerFeedBackDto;
import org.dromara.warm.flow.ui.dto.HandlerQuery;
import org.dromara.warm.flow.ui.service.WarmFlowService;
import org.dromara.warm.flow.ui.vo.*;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 设计器Controller 可选择是否放行放行可与业务系统共享权限主要是用来访问业务系统数据
*
* @author warm
*/
@RestController
@RequestMapping("/warm-flow")
public class WarmFlowController {
/**
* 保存流程json字符串
*
* @param defJson 流程数据集合
* @return ApiResult<Void>
* @throws Exception 异常
* @author xiarg
* @since 2024/10/29 16:31
*/
@PostMapping("/save-json")
@Transactional(rollbackFor = Exception.class)
public ApiResult<Void> saveJson(@RequestBody DefJson defJson, @RequestHeader("onlyNodeSkip") boolean onlyNodeSkip) throws Exception {
return WarmFlowService.saveJson(defJson, onlyNodeSkip);
}
/**
* 获取流程定义数据(包含节点和跳转)
*
* @param id 流程定义id
* @return ApiResult<DefVo>
* @author xiarg
* @since 2024/10/29 16:31
*/
@GetMapping(value = {"/query-def", "/query-def/{id}"})
public ApiResult<DefJson> queryDef(@PathVariable(value = "id", required = false) Long id) {
return WarmFlowService.queryDef(id);
}
/**
* 获取流程图
*
* @param id 流程实例id
* @return ApiResult<DefJson>
*/
@GetMapping("/query-flow-chart/{id}")
public ApiResult<DefJson> queryFlowChart(@PathVariable("id") Long id) {
return WarmFlowService.queryFlowChart(id);
}
/**
* 办理人权限设置列表tabs页签
*
* @return List<String>
*/
@GetMapping("/handler-type")
public ApiResult<List<String>> handlerType() {
return WarmFlowService.handlerType();
}
/**
* 办理人权限设置列表结果
*
* @return HandlerSelectVo
*/
@GetMapping("/handler-result")
public ApiResult<HandlerSelectVo> handlerResult(HandlerQuery query) {
return WarmFlowService.handlerResult(query);
}
/**
* 办理人权限名称回显
*
* @return HandlerSelectVo
*/
@GetMapping("/handler-feedback")
public ApiResult<List<HandlerFeedBackVo>> handlerFeedback(HandlerFeedBackDto handlerFeedBackDto) {
return WarmFlowService.handlerFeedback(handlerFeedBackDto);
}
/**
* 办理人选择项
*
* @return List<Dict>
*/
@GetMapping("/handler-dict")
public ApiResult<List<Dict>> handlerDict() {
return WarmFlowService.handlerDict();
}
/**
* 根据任务id获取待办任务表单及数据
*
* @param taskId 当前任务id
* @return {@link ApiResult< FlowDto >}
* @author liangli
* @date 2024/8/21 17:08
**/
@GetMapping(value = "/execute/load/{taskId}")
public ApiResult<FlowDto> load(@PathVariable("taskId") Long taskId) {
return WarmFlowService.load(taskId);
}
/**
* 根据任务id获取已办任务表单及数据
*
* @param hisTaskId
* @return
*/
@GetMapping(value = "/execute/hisLoad/{taskId}")
public ApiResult<FlowDto> hisLoad(@PathVariable("taskId") Long hisTaskId) {
return WarmFlowService.hisLoad(hisTaskId);
}
/**
* 通用表单流程审批接口
*
* @param formData
* @param taskId
* @param skipType
* @param message
* @param nodeCode
* @return
*/
@Transactional(rollbackFor = Exception.class)
@PostMapping(value = "/execute/handle")
public ApiResult<FlowInstance> handle(@RequestBody Map<String, Object> formData, @RequestParam("taskId") Long taskId
, @RequestParam("skipType") String skipType, @RequestParam("message") String message
, @RequestParam(value = "nodeCode", required = false) String nodeCode) {
return WarmFlowService.handle(formData, taskId, skipType, message, nodeCode);
}
/**
* 获取节点扩展属性
*
* @return List<NodeExt>
*/
@GetMapping("/node-ext")
public ApiResult<List<NodeExt>> nodeExt() {
return WarmFlowService.nodeExt();
}
/**
* 获取节点扩展属性
*
* @return List<NodeExt>
*/
@GetMapping("/listener-list")
public ApiResult<List<ListenerVo>> listenerList() {
return WarmFlowService.listenerList();
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.controller;
import org.dromara.warm.flow.dto.ApiResult;
import org.dromara.warm.flow.ui.service.WarmFlowService;
import org.dromara.warm.flow.ui.vo.WarmFlowVo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 设计器Controller 匿名访问
*
* @author warm
*/
@RestController
@RequestMapping("/warm-flow-ui")
public class WarmFlowUiController {
/**
* 返回流程定义的配置
*
* @return ApiResult<WarmFlowVo>
*/
@GetMapping("/config")
public ApiResult<WarmFlowVo> config() {
return WarmFlowService.config();
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 流程设计器-办理人选择
*
* @author warm
*/
@Getter
@Setter
@Accessors(chain = true)
public class HandlerFeedBackDto {
/**
* 入库主键集合比如怕角色和用户id重复可拼接为role:id
*/
private List<String> storageIds;
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.function.Function;
/**
* 办理人权限设置列表Function
*
* @author warm
*/
@Getter
@Setter
@Accessors(chain = true)
public class HandlerFunDto<T> {
/**
* 权限列表
*/
private List<T> list;
/**
* 权限列表list总数
*/
private long total;
/**
* 获取入库主键集合Function
*/
private Function<T, String> storageId;
/**
* 获取权限编码Function
*/
private Function<T, String> handlerCode;
/**
* 获取权限名称Function
*/
private Function<T, String> handlerName;
/**
* 获取权限分组名称Function
*/
private Function<T, String> groupName;
/**
* 获取创建时间Function
*/
private Function<T, String> createTime;
public HandlerFunDto(List<T> list, long total) {
this.list = list;
this.total = total;
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.dto;
import lombok.Getter;
import lombok.Setter;
/**
* 流程设计器-办理人权限设置列表查询参数
* 办理人权限列表选择框可能存在多个比如部门角色用户的情况
*
* @author warm
*/
@Getter
@Setter
public class HandlerQuery {
/**
* 权限编码zhangroleAdmindeptAdmin等编码
*/
private String handlerCode;
/**
* 权限名称管理员角色管理员部门管理员等名称
*/
private String handlerName;
/**
* 办理权限类型比如用户/角色/部门等
*/
private String handlerType;
/**
* 页面左侧树权限分组主键角色部门等主键
*/
private String groupId;
/**
* 当前页码
*/
private Integer pageNum;
/**
* 每页显示条数
*/
private Integer pageSize;
/**
* 开始时间
*/
private String beginTime;
/**
* 结束时间
*/
private String endTime;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
import java.util.function.Function;
/**
* 页面左侧树列表Function
*
* @author warm
*/
@Getter
@Setter
@Accessors(chain = true)
public class TreeFunDto<T> {
/**
* 左侧树列表
*/
private List<T> list;
/**
* 获取左侧树ID Function
*/
private Function<T, String> id;
/**
* 获取左侧树名称 Function
*/
private Function<T, String> name;
/**
* 获取左侧树父级ID Function
*/
private Function<T, String> parentId;
public TreeFunDto(List<T> list) {
this.list = list;
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.service;
import org.dromara.warm.flow.dto.Tree;
import java.util.List;
/**
* 分类接口
*
* @author warm
* @since 2025/6/24
*/
public interface CategoryService {
/**
* 查询分类
*
* @return 分类
*/
List<Tree> queryCategory();
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.service;
import org.dromara.warm.flow.dto.DefJson;
import org.dromara.warm.flow.dto.PromptContent;
import org.dromara.warm.flow.enums.NodeType;
import org.dromara.warm.flow.utils.MapUtil;
import java.util.ArrayList;
import java.util.List;
/**
* 流程图提示信息
*
* @author warm
*/
public interface ChartExtService {
/**
* 设置流程图提示信息
*
* @param defJson 流程定义json对象
*/
void execute(DefJson defJson);
/**
* 初始化流程图提示信息
*
* @param defJson 流程定义json对象
*/
default void initPromptContent(DefJson defJson) {
defJson.setTopText("流程名称: " + defJson.getFlowName());
defJson.getNodeList().forEach(nodeJson -> {
// 提示信息主对象
PromptContent promptContent = new PromptContent();
if (NodeType.isGateWay(nodeJson.getNodeType())) {
return;
}
// 设置 dialogStyle 样式
promptContent.setDialogStyle(MapUtil.mergeAll(
"position", "absolute",
"backgroundColor", "#fff",
"border", "1px solid #ccc",
"borderRadius", "4px",
"boxShadow", "0 2px 8px rgba(0, 0, 0, 0.15)",
"padding", "8px 12px",
"fontSize", "14px",
"zIndex", 1000,
"maxWidth", "500px",
"color", "#333"
));
// 创建 info 列表
List<PromptContent.InfoItem> infoList = new ArrayList<>();
// 添加第一个条目: 任务名称
PromptContent.InfoItem item = new PromptContent.InfoItem()
.setPrefix("任务名称: ")
.setContent(nodeJson.getNodeName())
.setContentStyle(MapUtil.mergeAll("border", "1px solid #d1e9ff",
"backgroundColor", "#e8f4ff",
"padding", "4px 8px",
"borderRadius", "4px"
))
.setRowStyle(MapUtil.mergeAll("fontWeight", "bold",
"margin", "0 0 6px 0",
"padding", "0 0 8px 0",
"borderBottom", "1px solid #ccc"
));
infoList.add(item);
promptContent.setInfo(infoList);
nodeJson.setPromptContent(promptContent);
});
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.service;
import org.dromara.warm.flow.ui.vo.Dict;
import java.util.List;
/**
* 流程设计器-获取办理人选择项
*
* @author warm
*/
public interface HandlerDictService {
/**
* 获取办理人选择项
*
* @return 结果
*/
List<Dict> getHandlerDict();
}

View File

@ -0,0 +1,134 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.service;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.utils.StreamUtils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import org.dromara.warm.flow.dto.FlowPage;
import org.dromara.warm.flow.dto.Tree;
import org.dromara.warm.flow.utils.*;
import org.dromara.warm.flow.ui.dto.HandlerFunDto;
import org.dromara.warm.flow.ui.dto.HandlerQuery;
import org.dromara.warm.flow.ui.dto.TreeFunDto;
import org.dromara.warm.flow.ui.utils.TreeUtil;
import org.dromara.warm.flow.ui.vo.HandlerAuth;
import org.dromara.warm.flow.ui.vo.HandlerFeedBackVo;
import org.dromara.warm.flow.ui.vo.HandlerSelectVo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 流程设计器-获取办理人权限设置列表接口
*
* @author warm
*/
public interface HandlerSelectService {
/**
* 获取办理人权限设置列表tabs页签用户角色和部门等可以返回其中一种或者多种按业务需求决定
*
* @return tabs页签
*/
List<String> getHandlerType();
/**
* 获取用户列表角色列表部门列表等可以返回其中一种或者多种按业务需求决定
*
* @param query 查询参数
* @return 结果
*/
HandlerSelectVo getHandlerSelect(HandlerQuery query);
/**
* 办理人权限名称回显兼容老项目新项目重写提高性能
*
* @param storageIds 入库主键集合
* @return 结果
*/
default List<HandlerFeedBackVo> handlerFeedback(List<String> storageIds) {
List<HandlerFeedBackVo> handlerFeedBackVos = new ArrayList<>();
if (CollUtil.isEmpty(storageIds)) {
return handlerFeedBackVos;
}
Map<String, String> authMap = new HashMap<>();
List<String> handlerTypes = getHandlerType();
if (CollUtil.isEmpty(handlerTypes)) {
return handlerFeedBackVos;
}
for (String handlerType : handlerTypes) {
HandlerQuery handlerQuery = new HandlerQuery();
handlerQuery.setHandlerType(handlerType);
HandlerSelectVo handlerSelectVo = getHandlerSelect(handlerQuery);
if (ObjectUtil.isNotNull(handlerSelectVo)) {
FlowPage<HandlerAuth> handlerAuths = handlerSelectVo.getHandlerAuths();
List<HandlerAuth> rows = handlerAuths.getRows();
if (CollUtil.isNotEmpty(rows)) {
authMap.putAll(StreamUtils.toMap(rows, HandlerAuth::getStorageId, HandlerAuth::getHandlerName));
}
}
}
// 遍历storageIds按照原本的顺序回显名称
for (String storageId : storageIds) {
handlerFeedBackVos.add(new HandlerFeedBackVo(storageId
, MapUtil.isEmpty(authMap) ? "" : authMap.get(storageId)));
}
return handlerFeedBackVos;
}
default <T> HandlerSelectVo getHandlerSelectVo(HandlerFunDto<T> handlerFunDto) {
List<HandlerAuth> handlerAuths = new ArrayList<>();
// 遍历角色数据封装为组件可识别的数据
for (T obj : handlerFunDto.getList()) {
handlerAuths.add(new HandlerAuth()
.setStorageId(handlerFunDto.getStorageId() == null ? null : handlerFunDto.getStorageId().apply(obj))
.setHandlerCode(handlerFunDto.getHandlerCode() == null ? null : handlerFunDto.getHandlerCode().apply(obj))
.setHandlerName(handlerFunDto.getHandlerName() == null ? null : handlerFunDto.getHandlerName().apply(obj))
.setCreateTime(handlerFunDto.getCreateTime() == null ? null : handlerFunDto.getCreateTime().apply(obj))
.setGroupName(handlerFunDto.getGroupName() == null ? null : handlerFunDto.getGroupName().apply(obj)));
}
return getResult(handlerAuths, handlerFunDto.getTotal());
}
default <T, R> HandlerSelectVo getHandlerSelectVo(HandlerFunDto<T> handlerFunDto, TreeFunDto<R> treeFunDto) {
HandlerSelectVo handlerSelectVo = getHandlerSelectVo(handlerFunDto);
List<Tree> treeList = StreamUtils.toList(treeFunDto.getList(), org ->
new Tree().setId(treeFunDto.getId() == null ? null : treeFunDto.getId().apply(org))
.setName(treeFunDto.getName() == null ? null : treeFunDto.getName().apply(org))
.setParentId(treeFunDto.getParentId() == null ? null : treeFunDto.getParentId().apply(org)));
// 通过递归构建树状结构
return handlerSelectVo.setTreeSelections(TreeUtil.buildTree(treeList));
}
default HandlerSelectVo getResult(List<HandlerAuth> handlerAuths, long total) {
return new HandlerSelectVo().setHandlerAuths(new FlowPage<HandlerAuth>()
.setCode(HttpStatus.SUCCESS)
.setMsg("查询成功")
.setRows(handlerAuths)
.setTotal(total));
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.service;
import org.dromara.warm.flow.ui.vo.ListenerVo;
import java.util.List;
/**
* 流程设计器-获取监听器列表
*
* @author warm
*/
public interface ListenerListService {
/**
* 获取监听器列表
*
* @return 结果
*/
List<ListenerVo> listenerList();
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.service;
import org.dromara.warm.flow.ui.vo.NodeExt;
import java.util.List;
/**
* 流程设计器-节点扩展属性
*
* @author warm
*/
public interface NodeExtService {
/**
* 获取节点扩展属性
*
* @return 结果
*/
List<NodeExt> getNodeExt();
}

View File

@ -0,0 +1,330 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.service;
import org.dromara.warm.flow.json.JsonUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.warm.flow.FlowEngine;
import org.dromara.warm.flow.config.WarmFlowProperties;
import org.dromara.warm.flow.dto.*;
import org.dromara.warm.flow.entity.FlowInstance;
import org.dromara.warm.flow.enums.FormCustomEnum;
import org.dromara.warm.flow.enums.ModelEnum;
import org.dromara.warm.flow.exception.FlowException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.warm.flow.ui.dto.HandlerFeedBackDto;
import org.dromara.warm.flow.ui.dto.HandlerQuery;
import org.dromara.warm.flow.ui.utils.TreeUtil;
import org.dromara.warm.flow.ui.vo.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* 设计器Controller 可选择是否放行放行可与业务系统共享权限主要是用来访问业务系统数据
*
* @author warm
*/
@Slf4j
public class WarmFlowService {
/**
* 返回流程定义的配置
*
* @return ApiResult<WarmFlowVo>
*/
public static ApiResult<WarmFlowVo> config() {
WarmFlowVo warmFlowVo = new WarmFlowVo();
WarmFlowProperties warmFlow = FlowEngine.getFlowConfig();
warmFlowVo.setFramework(warmFlow.getFramework().name());
// 获取tokenName
String tokenName = warmFlow.getTokenName();
if (StrUtil.isEmpty(tokenName)) {
return ApiResult.fail("未配置tokenName");
}
String[] tokenNames = tokenName.split(",");
List<String> tokenNameList = Arrays.stream(tokenNames).filter(StrUtil::isNotEmpty)
.map(String::trim).collect(Collectors.toList());
warmFlowVo.setTokenNameList(tokenNameList);
return ApiResult.ok(warmFlowVo);
}
/**
* 保存流程json字符串
*
* @param defJson 流程数据集合
* @param onlyNodeSkip 是否只保存节点和跳转
* @return ApiResult<Void>
* @throws Exception 异常
* @author xiarg
* @since 2024/10/29 16:31
*/
public static ApiResult<Void> saveJson(DefJson defJson, boolean onlyNodeSkip) throws Exception {
FlowEngine.defService().saveDef(defJson, onlyNodeSkip);
return ApiResult.ok();
}
/**
* 获取流程定义数据(包含节点和跳转)
*
* @param id 流程定义id
* @return ApiResult<DefVo>
* @author xiarg
* @since 2024/10/29 16:31
*/
public static ApiResult<DefJson> queryDef(Long id) {
try {
DefJson defJson;
if (id == null) {
defJson = new DefJson()
.setModelValue(ModelEnum.CLASSICS.name())
.setFormCustom(FormCustomEnum.N.name());
} else {
defJson = FlowEngine.defService().queryDesign(id);
}
CategoryService categoryService = SpringUtils.getBeanOrNull(CategoryService.class);
if (categoryService != null) {
List<Tree> treeList = categoryService.queryCategory();
defJson.setCategoryList(TreeUtil.buildTree(treeList));
}
return ApiResult.ok(defJson);
} catch (Exception e) {
log.error("获取流程json字符串", e);
throw new FlowException("获取流程json字符串失败", e);
}
}
/**
* 获取流程图
*
* @param id 流程实例id
* @return ApiResult<DefJson>
*/
public static ApiResult<DefJson> queryFlowChart(Long id) {
try {
FlowInstance instance = FlowEngine.insService().getById(id);
String defJsonStr = instance.getDefJson();
DefJson defJson = JsonUtil.strToBean(defJsonStr, DefJson.class);
defJson.setInstance(instance);
// 获取流程图三原色
defJson.setChartStatusColor(FlowEngine.chartService().getChartRgb(defJson.getModelValue()));
// 是否显示流程图顶部文字
defJson.setTopTextShow(FlowEngine.getFlowConfig().isTopTextShow());
// 需要业务系统实现该接口
ChartExtService chartExtService = SpringUtils.getBeanOrNull(ChartExtService.class);
if (chartExtService != null) {
chartExtService.initPromptContent(defJson);
chartExtService.execute(defJson);
}
return ApiResult.ok(defJson);
} catch (Exception e) {
log.error("获取流程图", e);
throw new FlowException("获取流程图失败", e);
}
}
/**
* 办理人权限设置列表tabs页签
*
* @return List<String>
*/
public static ApiResult<List<String>> handlerType() {
try {
// 需要业务系统实现该接口
HandlerSelectService handlerSelectService = SpringUtils.getBeanOrNull(HandlerSelectService.class);
if (handlerSelectService == null) {
return ApiResult.ok(Collections.emptyList());
}
List<String> handlerType = handlerSelectService.getHandlerType();
return ApiResult.ok(handlerType);
} catch (Exception e) {
log.error("办理人权限设置列表tabs页签异常", e);
throw new FlowException("办理人权限设置列表tabs页签失败", e);
}
}
/**
* 办理人权限设置列表结果
*
* @return HandlerSelectVo
*/
public static ApiResult<HandlerSelectVo> handlerResult(HandlerQuery query) {
try {
// 需要业务系统实现该接口
HandlerSelectService handlerSelectService = SpringUtils.getBeanOrNull(HandlerSelectService.class);
if (handlerSelectService == null) {
return ApiResult.ok(new HandlerSelectVo());
}
HandlerSelectVo handlerSelectVo = handlerSelectService.getHandlerSelect(query);
return ApiResult.ok(handlerSelectVo);
} catch (Exception e) {
log.error("办理人权限设置列表结果异常", e);
throw new FlowException("办理人权限设置列表结果失败", e);
}
}
/**
* 办理人权限名称回显
*
* @return HandlerSelectVo
*/
public static ApiResult<List<HandlerFeedBackVo>> handlerFeedback(HandlerFeedBackDto handlerFeedBackDto) {
try {
// 需要业务系统实现该接口
HandlerSelectService handlerSelectService = SpringUtils.getBeanOrNull(HandlerSelectService.class);
if (handlerSelectService == null) {
List<HandlerFeedBackVo> handlerFeedBackVos = StreamUtils.toList(handlerFeedBackDto.getStorageIds(),
storageId -> new HandlerFeedBackVo(storageId, null));
return ApiResult.ok(handlerFeedBackVos);
}
List<HandlerFeedBackVo> handlerFeedBackVos = handlerSelectService.handlerFeedback(handlerFeedBackDto.getStorageIds());
return ApiResult.ok(handlerFeedBackVos);
} catch (Exception e) {
log.error("办理人权限名称回显", e);
throw new FlowException("办理人权限名称回显", e);
}
}
/**
* 办理人选择项
*
* @return List<Dict>
*/
public static ApiResult<List<Dict>> handlerDict() {
try {
// 需要业务系统实现该接口
HandlerDictService handlerDictService = SpringUtils.getBeanOrNull(HandlerDictService.class);
if (handlerDictService == null) {
List<Dict> dictList = new ArrayList<>();
Dict dict = new Dict();
dict.setLabel("默认表达式");
dict.setValue("${handler}");
Dict dict1 = new Dict();
dict1.setLabel("spel表达式");
dict1.setValue("#{@user.evalVar(#handler)}");
Dict dict2 = new Dict();
dict2.setLabel("其他");
dict2.setValue("");
dictList.add(dict);
dictList.add(dict1);
dictList.add(dict2);
return ApiResult.ok(dictList);
}
return ApiResult.ok(handlerDictService.getHandlerDict());
} catch (Exception e) {
log.error("办理人权限设置列表结果异常", e);
throw new FlowException("办理人权限设置列表结果失败", e);
}
}
/**
* 根据任务id获取待办任务表单及数据
*
* @param taskId 当前任务id
* @return {@link ApiResult<FlowDto>}
* @author liangli
* @date 2024/8/21 17:08
**/
public static ApiResult<FlowDto> load(Long taskId) {
FlowParams flowParams = FlowParams.build();
return ApiResult.ok(FlowEngine.taskService().load(taskId, flowParams));
}
/**
* 根据任务id获取已办任务表单及数据
*
* @param hisTaskId
* @return
*/
public static ApiResult<FlowDto> hisLoad(Long hisTaskId) {
FlowParams flowParams = FlowParams.build();
return ApiResult.ok(FlowEngine.taskService().hisLoad(hisTaskId, flowParams));
}
/**
* 通用表单流程审批接口
*
* @param formData
* @param taskId
* @param skipType
* @param message
* @param nodeCode
* @return
*/
public static ApiResult<FlowInstance> handle(Map<String, Object> formData, Long taskId, String skipType
, String message, String nodeCode) {
FlowParams flowParams = FlowParams.build()
.skipType(skipType)
.nodeCode(nodeCode)
.message(message);
flowParams.formData(formData);
return ApiResult.ok(FlowEngine.taskService().skip(taskId, flowParams));
}
/**
* 获取节点扩展属性
*
* @return List<NodeExt>
*/
public static ApiResult<List<NodeExt>> nodeExt() {
try {
// 需要业务系统实现该接口
NodeExtService nodeExtService = SpringUtils.getBeanOrNull(NodeExtService.class);
if (nodeExtService == null) {
return ApiResult.ok(Collections.emptyList());
}
List<NodeExt> nodeExts = nodeExtService.getNodeExt();
return ApiResult.ok(nodeExts);
} catch (Exception e) {
log.error("获取节点扩展属性", e);
throw new FlowException("获取节点扩展属性失败", e);
}
}
/**
* 获取监听器列表
*
* @return List<NodeExt>
*/
public static ApiResult<List<ListenerVo>> listenerList() {
try {
// 需要业务系统实现该接口
ListenerListService listenerListService = SpringUtils.getBeanOrNull(ListenerListService.class);
if (listenerListService == null) {
return ApiResult.ok(Collections.emptyList());
}
List<ListenerVo> listenerList = listenerListService.listenerList();
return ApiResult.ok(listenerList);
} catch (Exception e) {
log.error("获取监听器列表", e);
throw new FlowException("获取监听器列表失败", e);
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.utils;
import cn.hutool.core.util.StrUtil;
import org.dromara.warm.flow.dto.Tree;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class TreeUtil {
private TreeUtil() {
}
/**
* 构建所需要树结构
*
* @param trees 部门列表
* @return 树结构列表
*/
public static List<Tree> buildTree(List<Tree> trees) {
List<Tree> returnList = new ArrayList<>();
List<String> tempList = trees.stream().map(Tree::getId).collect(Collectors.toList());
for (Tree dept : trees) {
// 如果是顶级节点, 遍历该父节点的所有子节点
if (!tempList.contains(dept.getParentId())) {
recursionFn(trees, dept);
returnList.add(dept);
}
}
if (returnList.isEmpty()) {
returnList = trees;
}
return returnList;
}
/**
* 递归列表
*/
private static void recursionFn(List<Tree> list, Tree t) {
// 得到子节点列表
List<Tree> childList = getChildList(list, t);
t.setChildren(childList);
for (Tree tChild : childList) {
if (hasChild(list, tChild)) {
recursionFn(list, tChild);
}
}
}
/**
* 判断是否有子节点
*/
private static boolean hasChild(List<Tree> list, Tree t) {
return !getChildList(list, t).isEmpty();
}
/**
* 得到子节点列表
*/
private static List<Tree> getChildList(List<Tree> list, Tree t) {
List<Tree> tlist = new ArrayList<>();
for (Tree n : list) {
if (StrUtil.isNotEmpty(n.getParentId()) && n.getParentId().equals(t.getId())) {
tlist.add(n);
}
}
return tlist;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.vo;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
/**
* 字典
*
* @author warm
*/
@Getter
@Setter
@NoArgsConstructor
public class Dict implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 字典标签
*/
private String label;
/**
* 字典值
*/
private String value;
private List<Dict> childList;
public Dict(String label, String value) {
this.label = label;
this.value = value;
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.vo;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 流程设计器-办理人权限设置列表
* 办理人权限列表选择框可能存在多个比如部门角色用户的情况
*
* @author warm
*/
@Getter
@Setter
@Accessors(chain = true)
public class HandlerAuth {
/**
* 入库主键比如怕角色和用户id重复可拼接为role:id
*/
private String storageId;
/**
* 权限编码zhangroleAdmindeptAdmin等编码
*/
private String handlerCode;
/**
* 权限名称管理员角色管理员部门管理员等名称
*/
private String handlerName;
/**
* 权限分组名称角色部门等名称
*/
private String groupName;
/**
* 创建时间
*/
private String createTime;
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.vo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 流程设计器-办理人选择
*
* @author warm
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class HandlerFeedBackVo {
/**
* 入库主键比如怕角色和用户id重复可拼接为role:id
*/
private String storageId;
/**
* 权限名称管理员角色管理员部门管理员等名称
*/
private String handlerName;
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.vo;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.dromara.warm.flow.dto.FlowPage;
import org.dromara.warm.flow.dto.Tree;
import java.util.List;
/**
* 流程设计器-办理人选择
*
* @author warm
*/
@Getter
@Setter
@Accessors(chain = true)
public class HandlerSelectVo {
/**
* 办理人选择分页列表有具体办理对象 比如部门角色和用户等情况
*/
private FlowPage<HandlerAuth> handlerAuths;
/**
* 左侧树状选择配合{@link #handlerAuths}使用比如用户先选择部门然后选择用户
*/
private List<Tree> treeSelections;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.vo;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
/**
* 监听器列表-设计器页面监听器列表下拉选使用
*
* @author warm
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ListenerVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 监听器类型startassignmentfinishcreate如果未设置选择监听器后监听器类型不联动
*/
private String type;
/**
* 监听器全限定类路径
*/
private String path;
/**
* 监听器描述
*/
private String description;
}

View File

@ -0,0 +1,80 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.vo;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
/**
* 节点扩展属性
*
* @author warm
* @since 2025/2/18
*/
@Getter
@Setter
public class NodeExt implements Serializable {
private static final long serialVersionUID = 1L;
private String code;
private String name;
private String desc;
private int type;
private List<ChildNode> childs;
@Getter
@Setter
public static class ChildNode {
private String code;
private String desc;
private String label;
private int type;
private boolean must;
private boolean multiple;
private int precision;
private String step;
private String min;
private String dateType;
private String dateFormat;
private List<DictItem> dict;
}
@Getter
@Setter
public static class DictItem {
private String label;
private String value;
private boolean selected;
public DictItem() {
}
public DictItem(String label, String value) {
this.label = label;
this.value = value;
}
public DictItem(String label, String value, boolean selected) {
this.label = label;
this.value = value;
this.selected = selected;
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.ui.vo;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 流程配置vo
*
* @author warm
*/
@Getter
@Setter
@Accessors(chain = true)
public class WarmFlowVo {
/**
* 如果需要工作流共享业务系统权限默认Authorization
*/
private List<String> tokenNameList;
/**
* 框架类型: springbootsolon
*/
private String framework;
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2024-2025, Warm-Flow (290631660@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.warm.flow.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import org.dromara.warm.flow.exception.FlowException;
import java.util.Collection;
import java.util.Map;
/**
* Assert类
*
* @author warm
* @since 2023/3/30 14:05
*/
public class AssertUtil {
private AssertUtil() {
}
public static void isNull(Object obj, String errorMsg) {
if (obj == null) {
throw new FlowException(errorMsg);
}
}
public static void isNotNull(Object obj, String errorMsg) {
if (obj != null) {
throw new FlowException(errorMsg);
}
}
/**
* 为true不抛异常
*
* @param obj
* @param errorMsg
*/
public static void isFalse(boolean obj, String errorMsg) {
if (!obj) {
throw new FlowException(errorMsg);
}
}
/**
* 为false不抛异常
*
* @param obj
* @param errorMsg
*/
public static void isTrue(boolean obj, String errorMsg) {
if (obj) {
throw new FlowException(errorMsg);
}
}
public static void isNotEmpty(Object obj, String errorMsg) {
if (obj != null) {
if (obj instanceof String) {
AssertUtil.isTrue(StrUtil.isNotEmpty((String) obj), errorMsg);
} else if (obj instanceof Collection) {
AssertUtil.isTrue(CollUtil.isNotEmpty((Collection<?>) obj), errorMsg);
} else if (obj instanceof Map) {
AssertUtil.isTrue(MapUtil.isNotEmpty((Map<?, ?>) obj), errorMsg);
} else {
throw new FlowException("Unsupported type: " + obj.getClass().getName());
}
}
}
public static void isEmpty(Object obj, String errorMsg) {
if (obj == null) {
throw new FlowException(errorMsg);
} else if (obj instanceof String) {
AssertUtil.isTrue(StrUtil.isEmpty((String) obj), errorMsg);
} else if (obj instanceof Collection) {
AssertUtil.isTrue(CollUtil.isEmpty((Collection<?>) obj), errorMsg);
} else if (obj instanceof Map) {
AssertUtil.isTrue(MapUtil.isEmpty((Map<?, ?>) obj), errorMsg);
} else {
throw new FlowException("Unsupported type: " + obj.getClass().getName());
}
}
public static <T> void contains(Collection<T> a, T b, String errorMsg) {
if (CollUtil.isNotEmpty(a) && a.contains(b)) {
throw new FlowException(errorMsg);
}
}
public static <T> void notContains(Collection<T> a, T b, String errorMsg) {
if (CollUtil.isEmpty(a) || !a.contains(b)) {
throw new FlowException(errorMsg);
}
}
}

Some files were not shown because too many files have changed in this diff Show More