初始化项目提交,并添加 flowable 7 的 dm 兼容

This commit is contained in:
陈博文
2025-06-10 15:04:49 +08:00
parent af2fd603b5
commit ebfa59ad99
1986 changed files with 1774358 additions and 59 deletions

27
yudao-module-bpm/pom.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>yudao-module-bpm-api</module>
<module>yudao-module-bpm-server</module>
</modules>
<artifactId>yudao-module-bpm</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
bpm 包下业务流程管理Business Process Management我们放工作流的功能。
例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
bpm 解释https://baike.baidu.com/item/BPM/1933
工作流基于 Flowable 6 实现,分成流程定义、流程表单、流程实例、流程任务等功能模块。
</description>
</project>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-bpm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
bpm 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springdoc</groupId> <!-- 接口文档:使用最新版本的 Swagger 模型 -->
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.bpm.api.event;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
/**
* 流程实例的状态(结果)发生变化的 Event
*
* @author 芋道源码
*/
@SuppressWarnings("ALL")
@Data
public class BpmProcessInstanceStatusEvent extends ApplicationEvent {
/**
* 流程实例的编号
*/
@NotNull(message = "流程实例的编号不能为空")
private String id;
/**
* 流程实例的 key
*/
@NotNull(message = "流程实例的 key 不能为空")
private String processDefinitionKey;
/**
* 流程实例的结果
*/
@NotNull(message = "流程实例的状态不能为空")
private Integer status;
/**
* 流程实例对应的业务标识
* 例如说,请假
*/
private String businessKey;
public BpmProcessInstanceStatusEvent(Object source) {
super(source);
}
}

View File

@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.bpm.api.event;
import cn.hutool.core.util.StrUtil;
import org.springframework.context.ApplicationListener;
/**
* {@link BpmProcessInstanceStatusEvent} 的监听器
*
* @author 芋道源码
*/
public abstract class BpmProcessInstanceStatusEventListener
implements ApplicationListener<BpmProcessInstanceStatusEvent> {
@Override
public final void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
if (!StrUtil.equals(event.getProcessDefinitionKey(), getProcessDefinitionKey())) {
return;
}
onEvent(event);
}
/**
* @return 返回监听的流程定义 Key
*/
protected abstract String getProcessDefinitionKey();
/**
* 处理事件
*
* @param event 事件
*/
protected abstract void onEvent(BpmProcessInstanceStatusEvent event);
}

View File

@@ -0,0 +1,4 @@
/**
* bpm API 包,定义暴露给其它模块的 API
*/
package cn.iocoder.yudao.module.bpm.api;

View File

@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.bpm.api.task;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import jakarta.validation.Valid;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - 流程实例")
public interface BpmProcessInstanceApi {
String PREFIX = ApiConstants.PREFIX + "/process-instance";
@PostMapping(PREFIX + "/create")
@Operation(summary = "创建流程实例(提供给内部),返回实例编号")
@Parameter(name = "userId", description = "用户编号", required = true, example = "1")
CommonResult<String> createProcessInstance(@RequestParam("userId") Long userId,
@Valid @RequestBody BpmProcessInstanceCreateReqDTO reqDTO);
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.bpm.api.task.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Map;
@Schema(description = "RPC 服务 - 流程实例的创建 Request DTO")
@Data
public class BpmProcessInstanceCreateReqDTO {
@Schema(description = "流程定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "leave")
@NotEmpty(message = "流程定义的标识不能为空")
private String processDefinitionKey;
@Schema(description = "变量实例", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, Object> variables;
@Schema(description = "业务的唯一标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "业务的唯一标识不能为空")
private String businessKey; // 例如说,请假申请的编号。通过它,可以查询到对应的实例
/**
* 发起人自选审批人 Map
*
* keytaskKey 任务编码
* value审批人的数组
* 例如:{ taskKey1 :[1, 2] },则表示 taskKey1 这个任务,提前设定了,由 userId 为 1,2 的用户进行审批
*/
@Schema(description = "发起人自选审批人 Map")
private Map<String, List<Long>> startUserSelectAssignees;
}

View File

@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.bpm.enums;
import cn.iocoder.yudao.framework.common.enums.RpcConstants;
/**
* API 相关的枚举
*
* @author 芋道源码
*/
public class ApiConstants {
/**
* 服务名
*
* 注意,需要保证和 spring.application.name 保持一致
*/
public static final String NAME = "bpm-server";
public static final String PREFIX = RpcConstants.RPC_API_PREFIX + "/bpm";
public static final String VERSION = "1.0.0";
}

View File

@@ -0,0 +1,10 @@
package cn.iocoder.yudao.module.bpm.enums;
/**
* BPM 字典类型的枚举类
*
* @author 芋道源码
*/
public interface DictTypeConstants {
}

View File

@@ -0,0 +1,87 @@
package cn.iocoder.yudao.module.bpm.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* Bpm 错误码枚举类
* <p>
* bpm 系统,使用 1-009-000-000 段
*/
public interface ErrorCodeConstants {
// ========== 通用流程处理 模块 1-009-000-000 ==========
// ========== OA 流程模块 1-009-001-000 ==========
ErrorCode OA_LEAVE_NOT_EXISTS = new ErrorCode(1_009_001_001, "请假申请不存在");
// ========== 流程模型 1-009-002-000 ==========
ErrorCode MODEL_KEY_EXISTS = new ErrorCode(1_009_002_000, "已经存在流程标识为【{}】的流程");
ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_009_002_001, "流程模型不存在");
ErrorCode MODEL_KEY_VALID = new ErrorCode(1_009_002_002, "流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!");
ErrorCode MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG = new ErrorCode(1_009_002_003, "部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置");
ErrorCode MODEL_DEPLOY_FAIL_TASK_CANDIDATE_NOT_CONFIG = new ErrorCode(1_009_002_004, "部署流程失败," +
"原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败原因BPMN 流程图中,没有开始事件");
ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败原因BPMN 流程图中,用户任务({})的名字不存在");
ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
ErrorCode MODEL_DEPLOY_FAIL_FIRST_USER_TASK_CANDIDATE_STRATEGY_ERROR = new ErrorCode(1_009_002_008, "部署流程失败,原因:首个任务({})的审批人不能是【审批人自选】");
// ========== 流程定义 1-009-003-000 ==========
ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
ErrorCode PROCESS_DEFINITION_NAME_NOT_MATCH = new ErrorCode(1_009_003_001, "流程定义的名字期望是({}),当前是({}),请修改 BPMN 流程图");
ErrorCode PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_009_003_002, "流程定义不存在");
ErrorCode PROCESS_DEFINITION_IS_SUSPENDED = new ErrorCode(1_009_003_003, "流程定义处于挂起状态");
// ========== 流程实例 1-009-004-000 ==========
ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置");
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
// ========== 流程任务 1-009-005-000 ==========
ErrorCode TASK_OPERATE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1_009_005_001, "操作失败,原因:该任务的审批人不是你");
ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在");
ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退回任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");
ErrorCode TASK_SIGN_CREATE_TYPE_ERROR = new ErrorCode(1_009_005_010, "任务加签:当前任务已经{},不能{}");
ErrorCode TASK_SIGN_CREATE_USER_REPEAT = new ErrorCode(1_009_005_011, "任务加签失败,加签人与现有审批人[{}]重复");
ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
// ========== 动态表单模块 1-009-010-000 ==========
ErrorCode FORM_NOT_EXISTS = new ErrorCode(1_009_010_000, "动态表单不存在");
ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1_009_010_001, "表单项({}) 和 ({}) 使用了相同的字段名({})");
// ========== 用户组模块 1-009-011-000 ==========
ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1_009_011_000, "用户分组不存在");
ErrorCode USER_GROUP_IS_DISABLE = new ErrorCode(1_009_011_001, "名字为【{}】的用户分组已被禁用");
// ========== 用户组模块 1-009-012-000 ==========
ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_009_012_000, "流程分类不存在");
ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_009_012_001, "流程分类名字【{}】重复");
ErrorCode CATEGORY_CODE_DUPLICATE = new ErrorCode(1_009_012_002, "流程分类编码【{}】重复");
// ========== BPM 流程监听器 1-009-013-000 ==========
ErrorCode PROCESS_LISTENER_NOT_EXISTS = new ErrorCode(1_009_013_000, "流程监听器不存在");
ErrorCode PROCESS_LISTENER_CLASS_NOT_FOUND = new ErrorCode(1_009_013_001, "流程监听器类({})不存在");
ErrorCode PROCESS_LISTENER_CLASS_IMPLEMENTS_ERROR = new ErrorCode(1_009_013_002, "流程监听器类({})没有实现接口({})");
ErrorCode PROCESS_LISTENER_EXPRESSION_INVALID = new ErrorCode(1_009_013_003, "流程监听器表达式({})不合法");
// ========== BPM 流程表达式 1-009-014-000 ==========
ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在");
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 自动去重的类型的枚举
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmAutoApproveTypeEnum implements ArrayValuable<Integer> {
NONE(0, "不自动通过"),
APPROVE_ALL(1, "仅审批一次,后续重复的审批节点均自动通过"),
APPROVE_SEQUENT(2, "仅针对连续审批的节点自动通过");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmAutoApproveTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 边界事件 (boundary event) 自定义类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmBoundaryEventTypeEnum {
USER_TASK_TIMEOUT(1, "用户任务超时"),
DELAY_TIMER_TIMEOUT(2, "延迟器超时"),
CHILD_PROCESS_TIMEOUT(3, "子流程超时");
private final Integer type;
private final String name;
public static BpmBoundaryEventTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(eventType -> eventType.getType().equals(type), values());
}
}

View File

@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 子流程多实例来源类型枚举
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmChildProcessMultiInstanceSourceTypeEnum implements ArrayValuable<Integer> {
FIXED_QUANTITY(1, "固定数量"),
NUMBER_FORM(2, "数字表单"),
MULTIPLE_FORM(3, "多选表单");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessMultiInstanceSourceTypeEnum::getType).toArray(Integer[]::new);
public static BpmChildProcessMultiInstanceSourceTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 当子流程发起人为空时类型枚举
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmChildProcessStartUserEmptyTypeEnum implements ArrayValuable<Integer> {
MAIN_PROCESS_START_USER(1, "同主流程发起人"),
CHILD_PROCESS_ADMIN(2, "子流程管理员"),
MAIN_PROCESS_ADMIN(3, "主流程管理员");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserEmptyTypeEnum::getType).toArray(Integer[]::new);
public static BpmChildProcessStartUserEmptyTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 子流程发起人类型枚举
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmChildProcessStartUserTypeEnum implements ArrayValuable<Integer> {
MAIN_PROCESS_START_USER(1, "同主流程发起人"),
FROM_FORM(2, "表单");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmChildProcessStartUserTypeEnum::getType).toArray(Integer[]::new);
public static BpmChildProcessStartUserTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 延迟器类型枚举
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmDelayTimerTypeEnum implements ArrayValuable<Integer> {
FIXED_TIME_DURATION(1, "固定时长"),
FIXED_DATE_TIME(2, "固定日期");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmDelayTimerTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 表单权限的枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmFieldPermissionEnum {
READ(1, "只读"),
WRITE(2, "可编辑"),
NONE(3, "隐藏");
/**
* 权限
*/
private final Integer permission;
/**
* 名字
*/
private final String name;
public static BpmFieldPermissionEnum valueOf(Integer permission) {
return ArrayUtil.firstMatch(item -> item.getPermission().equals(permission), values());
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM HTTP 请求参数设置类型。用于 Simple 设计器任务监听器和触发器配置。
*
* @author Lesan
*/
@Getter
@AllArgsConstructor
public enum BpmHttpRequestParamTypeEnum implements ArrayValuable<Integer> {
FIXED_VALUE(1, "固定值"),
FROM_FORM(2, "表单");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmHttpRequestParamTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 模型的表单类型的枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmModelFormTypeEnum implements ArrayValuable<Integer> {
NORMAL(10, "流程表单"), // 对应 BpmFormDO
CUSTOM(20, "业务表单") // 业务自己定义的表单,自己进行数据的存储
;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmModelFormTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 模型的类型的枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmModelTypeEnum implements ArrayValuable<Integer> {
BPMN(10, "BPMN 设计器"), // https://bpmn.io/toolkit/bpmn-js/
SIMPLE(20, "SIMPLE 设计器"); // 参考钉钉、飞书工作流的设计器
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmModelTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 流程监听器的类型
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmProcessListenerTypeEnum {
EXECUTION("execution", "执行监听器"),
TASK("task", "任务执行器");
private final String type;
private final String name;
}

View File

@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 流程监听器的值类型
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmProcessListenerValueTypeEnum {
CLASS("class", "Java 类"),
DELEGATE_EXPRESSION("delegateExpression", "代理表达式"),
EXPRESSION("expression", "表达式");
private final String type;
private final String name;
}

View File

@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 仿钉钉的流程器设计器条件节点的条件类型
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmSimpleModeConditionTypeEnum implements ArrayValuable<Integer> {
EXPRESSION(1, "条件表达式"),
RULE(2, "条件规则");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmSimpleModeConditionTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
private final String name;
public static BpmSimpleModeConditionTypeEnum valueOf(Integer type) {
return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
* 仿钉钉的流程器设计器的模型节点类型
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable<Integer> {
// 0 ~ 1 开始和结束
START_NODE(0, "开始", "startEvent"),
END_NODE(1, "结束", "endEvent"),
// 10 ~ 49 各种节点
START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点Id 固定
APPROVE_NODE(11, "审批人", "userTask"),
COPY_NODE(12, "抄送人", "serviceTask"),
TRANSACTOR_NODE(13, "办理人", "userTask"),
DELAY_TIMER_NODE(14, "延迟器", "receiveTask"),
TRIGGER_NODE(15, "触发器", "serviceTask"),
CHILD_PROCESS(20, "子流程", "callActivity"),
// 50 ~ 条件分支
CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"),
INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
ROUTER_BRANCH_NODE(54, "路由分支", "exclusiveGateway")
;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmSimpleModelNodeTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
private final String name;
private final String bpmnType;
/**
* 判断是否为分支节点
*
* @param type 节点类型
*/
public static boolean isBranchNode(Integer type) {
return Objects.equals(CONDITION_BRANCH_NODE.getType(), type)
|| Objects.equals(PARALLEL_BRANCH_NODE.getType(), type)
|| Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type)
|| Objects.equals(ROUTER_BRANCH_NODE.getType(), type);
}
public static BpmSimpleModelNodeTypeEnum valueOf(Integer type) {
return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM Simple 触发器类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmTriggerTypeEnum implements ArrayValuable<Integer> {
HTTP_REQUEST(1, "发起 HTTP 请求"), // BPM => 业务,流程继续执行,无需等待业务
HTTP_CALLBACK(2, "接收 HTTP 回调"), // BPM => 业务 => BPM流程卡主等待业务回调
FORM_UPDATE(10, "更新流程表单数据"),
FORM_DELETE(11, "删除流程表单数据"),
;
/**
* 触发器执行动作类型
*/
private final Integer type;
/**
* 触发器执行动作描述
*/
private final String desc;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmTriggerTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
public static BpmTriggerTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
}

View File

@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 多人审批方式的枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskApproveMethodEnum implements ArrayValuable<Integer> {
RANDOM(1, "随机挑选一人审批", null),
RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例)
ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人)
SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批
/**
* 审批方式
*/
private final Integer method;
/**
* 名字
*/
private final String name;
/**
* 完成表达式
*/
private final String completionCondition;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskApproveMethodEnum::getMethod).toArray(Integer[]::new);
public static BpmUserTaskApproveMethodEnum valueOf(Integer method) {
return ArrayUtil.firstMatch(item -> item.getMethod().equals(method), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户任务的审批类型枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskApproveTypeEnum implements ArrayValuable<Integer> {
USER(1), // 人工审批
AUTO_APPROVE(2), // 自动通过
AUTO_REJECT(3); // 自动拒绝
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskApproveTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* BPM 用户任务的审批人为空时,处理类型枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum BpmUserTaskAssignEmptyHandlerTypeEnum implements ArrayValuable<Integer> {
APPROVE(1), // 自动通过
REJECT(2), // 自动拒绝
ASSIGN_USER(3), // 指定人员审批
ASSIGN_ADMIN(4), // 转交给流程管理员
;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskAssignEmptyHandlerTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* BPM 用户任务的审批人与发起人相同时,处理类型枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum BpmUserTaskAssignStartUserHandlerTypeEnum implements ArrayValuable<Integer> {
START_USER_AUDIT(1), // 由发起人对自己审批
SKIP(2), // 自动跳过【参考飞书】1如果当前节点还有其他审批人则交由其他审批人进行审批2如果当前节点没有其他审批人则该节点自动通过
TRANSFER_DEPT_LEADER(3); // 转交给部门负责人审批【参考飞书】:若部门负责人为空,则自动通过
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskAssignStartUserHandlerTypeEnum::getType).toArray(Integer[]::new);
private final Integer type;
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* BPM 用户任务拒绝处理类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskRejectHandlerTypeEnum implements ArrayValuable<Integer> {
FINISH_PROCESS_INSTANCE(1, "终止流程"),
RETURN_USER_TASK(2, "驳回到指定任务节点");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskRejectHandlerTypeEnum::getType).toArray(Integer[]::new);
public static BpmUserTaskRejectHandlerTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.bpm.enums.definition;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 用户任务超时处理类型枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmUserTaskTimeoutHandlerTypeEnum implements ArrayValuable<Integer> {
REMINDER(1,"自动提醒"),
APPROVE(2, "自动同意"),
REJECT(3, "自动拒绝");
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmUserTaskTimeoutHandlerTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
}

View File

@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.bpm.enums.message;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Bpm 消息的枚举
*
* @author 芋道源码
*/
@AllArgsConstructor
@Getter
public enum BpmMessageEnum {
PROCESS_INSTANCE_APPROVE("bpm_process_instance_approve"), // 流程任务被审批通过时,发送给申请人
PROCESS_INSTANCE_REJECT("bpm_process_instance_reject"), // 流程任务被审批不通过时,发送给申请人
TASK_ASSIGNED("bpm_task_assigned"), // 任务被分配时,发送给审批人
TASK_TIMEOUT("bpm_task_timeout"); // 任务审批超时时,发送给审批人
/**
* 短信模板的标识
*
* 关联 SmsTemplateDO 的 code 属性
*/
private final String smsTemplateCode;
}

View File

@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程任务的 Comment 评论类型枚举
*
* @author kehaiyou
*/
@Getter
@AllArgsConstructor
public enum BpmCommentTypeEnum {
APPROVE("1", "审批通过", "审批通过,原因是:{}"),
REJECT("2", "不通过", "审批不通过:原因是:{}"),
CANCEL("3", "已取消", "系统自动取消,原因是:{}"),
RETURN("4", "退回", "任务被退回,原因是:{}"),
DELEGATE_START("5", "委派发起", "[{}]将任务委派给[{}],委派理由为:{}"),
DELEGATE_END("6", "委派完成", "[{}]完成委派任务,任务重新回到[{}]手中,审批建议为:{}"),
TRANSFER("7", "转派", "[{}]将任务转派给[{}],转派理由为:{}"),
ADD_SIGN("8", "加签", "[{}]{}给了[{}],理由为:{}"),
SUB_SIGN("9", "减签", "[{}]操作了【减签】,审批人[{}]的任务被取消"),
;
/**
* 操作类型
*
* 由于 BPM Comment 类型为 String所以这里就不使用 Integer
*/
private final String type;
/**
* 操作名字
*/
private final String name;
/**
* 操作描述
*/
private final String comment;
public String formatComment(Object... params) {
return StrUtil.format(comment, params);
}
}

View File

@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 流程实例 ProcessInstance 的状态
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmProcessInstanceStatusEnum implements ArrayValuable<Integer> {
NOT_START(-1, "未开始"),
RUNNING(1, "审批中"),
APPROVE(2, "审批通过"),
REJECT(3, "审批不通过"),
CANCEL(4, "已取消");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BpmProcessInstanceStatusEnum::getStatus).toArray(Integer[]::new);
/**
* 状态
*/
private final Integer status;
/**
* 描述
*/
private final String desc;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isRejectStatus(Integer status) {
return REJECT.getStatus().equals(status);
}
public static boolean isProcessEndStatus(Integer status) {
return ObjectUtils.equalsAny(status,
APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus());
}
}

View File

@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程实例/任务的的处理原因枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmReasonEnum {
// ========== 流程实例的独有原因 ==========
REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法
CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程
CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程
CANCEL_CHILD_PROCESS_INSTANCE_BY_MAIN_PROCESS("子流程自动取消,原因:主流程已取消"),
// ========== 流程任务的独有原因 ==========
CANCEL_BY_SYSTEM("系统自动取消"), // 场景非常多比如说1多任务审批已经满足条件无需审批该任务2流程实例被取消无需审批该任务等等
TIMEOUT_APPROVE("审批超时,系统自动通过"),
TIMEOUT_REJECT("审批超时,系统自动不通过"),
ASSIGN_START_USER_APPROVE("审批人与提交人为同一人时,自动通过"),
ASSIGN_START_USER_APPROVE_WHEN_SKIP("审批人与提交人为同一人时,自动通过"),
ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE("发起人节点首次自动通过"), // 目前仅“子流程”使用
ASSIGN_START_USER_APPROVE_WHEN_DEPT_LEADER_NOT_FOUND("审批人与提交人为同一人时,找不到部门负责人,自动通过"),
ASSIGN_START_USER_TRANSFER_DEPT_LEADER("审批人与提交人为同一人时,转交给部门负责人审批"),
ASSIGN_EMPTY_APPROVE("审批人为空,自动通过"),
ASSIGN_EMPTY_REJECT("审批人为空,自动不通过"),
APPROVE_TYPE_AUTO_APPROVE("非人工审核,自动通过"),
APPROVE_TYPE_AUTO_REJECT("非人工审核,自动不通过"),
CANCEL_BY_PROCESS_CLEAN("进程清理自动取消"),
;
private final String reason;
/**
* 格式化理由
*
* @param args 参数
* @return 理由
*/
public String format(Object... args) {
return StrUtil.format(reason, args);
}
}

View File

@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程任务的加签类型枚举
*
* @author kehaiyou
*/
@Getter
@AllArgsConstructor
public enum BpmTaskSignTypeEnum {
/**
* 向前加签,需要前置任务审批完成,才回到原审批人
*/
BEFORE("before", "向前加签"),
/**
* 向后加签,需要后置任务全部审批完,才会通过原审批人节点
*/
AFTER("after", "向后加签");
/**
* 类型
*/
private final String type;
/**
* 名字
*/
private final String name;
public static String nameOfType(String type) {
for (BpmTaskSignTypeEnum value : values()) {
if (value.type.equals(type)) {
return value.name;
}
}
return null;
}
public static BpmTaskSignTypeEnum of(String type) {
return ArrayUtil.firstMatch(value -> value.getType().equals(type), values());
}
}

View File

@@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程任务 Task 的状态枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmTaskStatusEnum {
NOT_START(-1, "未开始"),
RUNNING(1, "审批中"),
APPROVE(2, "审批通过"),
REJECT(3, "审批不通过"),
CANCEL(4, "已取消"),
RETURN(5, "已退回"),
/**
* 使用场景:
* 1. 任务被向后【加签】时,它在审批通过后,会变成 APPROVING 这个状态,然后等到【加签】出来的任务都被审批后,才会变成 APPROVE 审批通过
*/
APPROVING(7, "审批通过中"),
/**
* 使用场景:
* 1. 任务被向前【加签】时,它会变成 WAIT 状态,需要等待【加签】出来的任务被审批后,它才能继续变为 RUNNING 继续审批
* 2. 任务被向后【加签】时,【加签】出来的任务处于 WAIT 状态,它们需要等待该任务被审批后,它们才能继续变为 RUNNING 继续审批
*/
WAIT(0, "待审批");
/**
* 状态
* <p>
* 如果新增时,注意 {@link #isEndStatus(Integer)} 是否需要变更
*/
private final Integer status;
/**
* 名字
*/
private final String name;
public static boolean isRejectStatus(Integer status) {
return REJECT.getStatus().equals(status);
}
/**
* 判断该状态是否已经处于 End 最终状态
* <p>
* 主要用于一些状态更新的逻辑,如果已经是最终状态,就不再进行更新
*
* @param status 状态
* @return 是否
*/
public static boolean isEndStatus(Integer status) {
return ObjectUtils.equalsAny(status,
APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus(),
RETURN.getStatus(), APPROVING.getStatus());
}
public static boolean isCancelStatus(Integer status) {
return ObjUtil.equal(status, CANCEL.getStatus());
}
}

View File

@@ -0,0 +1,19 @@
## AdoptOpenJDK 停止发布 OpenJDK 二进制,而 Eclipse Temurin 是它的延伸,提供更好的稳定性
## 感谢复旦核博士的建议!灰子哥,牛皮!
FROM eclipse-temurin:21-jre
## 创建目录,并使用它作为工作目录
RUN mkdir -p /yudao-module-bpm-server
WORKDIR /yudao-module-bpm-server
## 将后端项目的 Jar 文件,复制到镜像中
COPY ./target/yudao-module-bpm-server.jar app.jar
## 设置 TZ 时区
## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖
ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx512m"
## 暴露后端项目的 48080 端口
EXPOSE 48083
## 启动后端项目
CMD java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar app.jar

View File

@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-bpm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm-server</artifactId>
<name>${project.artifactId}</name>
<description>
bpm 包下业务流程管理Business Process Management我们放工作流的功能基于 Flowable 6 版本实现。
例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等 </description>
<dependencies>
<!-- Spring Cloud 基础 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-env</artifactId>
</dependency>
<!-- 依赖服务 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-bpm-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-rpc</artifactId>
</dependency>
<!-- Registry 注册中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Config 配置中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 服务保障相关 TODO 芋艿:暂时去掉 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-spring-boot-starter-protection</artifactId>-->
<!-- </dependency>-->
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
<!-- 监控相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<!-- Flowable 工作流相关 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,781 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.alibaba.druid.pool;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.druid.VERSION;
import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import com.alibaba.druid.util.JdbcUtils;
import com.alibaba.druid.util.MySqlUtils;
import java.net.SocketTimeoutException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class DruidPooledStatement extends PoolableWrapper implements Statement {
private static final Log LOG = LogFactory.getLog(DruidPooledStatement.class);
private final Statement stmt;
protected DruidPooledConnection conn;
protected List<ResultSet> resultSetTrace;
protected boolean closed;
protected int fetchRowPeak = -1;
protected int exceptionCount;
public DruidPooledStatement(DruidPooledConnection conn, Statement stmt) {
super(stmt);
this.conn = conn;
this.stmt = stmt;
}
protected void addResultSetTrace(ResultSet resultSet) {
if (this.resultSetTrace == null) {
this.resultSetTrace = new ArrayList(1);
} else if (this.resultSetTrace.size() > 0) {
int lastIndex = this.resultSetTrace.size() - 1;
ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(lastIndex);
try {
if (lastResultSet.isClosed()) {
this.resultSetTrace.set(lastIndex, resultSet);
return;
}
} catch (SQLException var5) {
}
}
this.resultSetTrace.add(resultSet);
}
protected void recordFetchRowCount(int fetchRowCount) {
if (this.fetchRowPeak < fetchRowCount) {
this.fetchRowPeak = fetchRowCount;
}
}
public int getFetchRowPeak() {
return this.fetchRowPeak;
}
protected SQLException checkException(Throwable error) throws SQLException {
String sql = null;
if (this instanceof DruidPooledPreparedStatement) {
sql = ((DruidPooledPreparedStatement)this).getSql();
}
this.handleSocketTimeout(error);
++this.exceptionCount;
return this.conn.handleException(error, sql);
}
protected SQLException checkException(Throwable error, String sql) throws SQLException {
this.handleSocketTimeout(error);
++this.exceptionCount;
return this.conn.handleException(error, sql);
}
protected void handleSocketTimeout(Throwable error) throws SQLException {
if (this.conn != null && this.conn.transactionInfo == null && this.conn.holder != null) {
DruidDataSource dataSource = null;
DruidConnectionHolder holder = this.conn.holder;
if (holder.dataSource instanceof DruidDataSource) {
dataSource = (DruidDataSource)holder.dataSource;
}
if (dataSource != null) {
if (dataSource.killWhenSocketReadTimeout) {
SQLException sqlException = null;
if (error instanceof SQLException) {
sqlException = (SQLException)error;
}
if (sqlException != null) {
Throwable cause = error.getCause();
boolean socketReadTimeout = cause instanceof SocketTimeoutException && "Read timed out".equals(cause.getMessage());
if (socketReadTimeout) {
if (JdbcUtils.isMysqlDbType(dataSource.dbTypeName)) {
String killQuery = MySqlUtils.buildKillQuerySql(this.conn.getConnection(), (SQLException)error);
if (killQuery != null) {
DruidPooledConnection killQueryConn = null;
Statement killQueryStmt = null;
try {
killQueryConn = dataSource.getConnection(1000L);
if (killQueryConn != null) {
killQueryStmt = killQueryConn.createStatement();
killQueryStmt.execute(killQuery);
if (LOG.isDebugEnabled()) {
LOG.debug(killQuery + " success.");
}
return;
}
} catch (Exception ex) {
LOG.warn(killQuery + " error.", ex);
return;
} finally {
JdbcUtils.close(killQueryStmt);
JdbcUtils.close(killQueryConn);
}
}
}
}
}
}
}
}
}
public DruidPooledConnection getPoolableConnection() {
return this.conn;
}
public Statement getStatement() {
return this.stmt;
}
protected void checkOpen() throws SQLException {
if (this.closed) {
Throwable disableError = null;
if (this.conn != null) {
disableError = this.conn.getDisableError();
}
if (disableError != null) {
throw new SQLException("statement is closed", disableError);
} else {
throw new SQLException("statement is closed");
}
}
}
protected void clearResultSet() {
if (this.resultSetTrace != null) {
for(ResultSet rs : this.resultSetTrace) {
try {
if (!rs.isClosed()) {
rs.close();
}
} catch (SQLException ex) {
LOG.error("clearResultSet error", ex);
}
}
this.resultSetTrace.clear();
}
}
public void incrementExecuteCount() {
DruidPooledConnection conn = this.getPoolableConnection();
if (conn != null) {
DruidConnectionHolder holder = conn.getConnectionHolder();
if (holder != null) {
DruidAbstractDataSource dataSource = holder.getDataSource();
if (dataSource != null) {
dataSource.incrementExecuteCount();
}
}
}
}
public void incrementExecuteBatchCount() {
DruidPooledConnection conn = this.getPoolableConnection();
if (conn != null) {
DruidConnectionHolder holder = conn.getConnectionHolder();
if (holder != null) {
if (holder.getDataSource() != null) {
DruidAbstractDataSource dataSource = holder.getDataSource();
if (dataSource != null) {
dataSource.incrementExecuteBatchCount();
}
}
}
}
}
public void incrementExecuteUpdateCount() {
DruidPooledConnection conn = this.getPoolableConnection();
if (conn != null) {
DruidConnectionHolder holder = conn.getConnectionHolder();
if (holder != null) {
DruidAbstractDataSource dataSource = holder.getDataSource();
if (dataSource != null) {
dataSource.incrementExecuteUpdateCount();
}
}
}
}
public void incrementExecuteQueryCount() {
DruidPooledConnection conn = this.conn;
if (conn != null) {
DruidConnectionHolder holder = conn.holder;
if (holder != null) {
DruidAbstractDataSource dataSource = holder.dataSource;
if (dataSource != null) {
++dataSource.executeQueryCount;
}
}
}
}
protected void transactionRecord(String sql) throws SQLException {
this.conn.transactionRecord(sql);
}
public final ResultSet executeQuery(String sql) throws SQLException {
this.checkOpen();
this.incrementExecuteQueryCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
ResultSet var3;
try {
ResultSet rs = this.stmt.executeQuery(sql);
if (rs != null) {
DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);
this.addResultSetTrace(poolableResultSet);
DruidPooledResultSet var4 = poolableResultSet;
return var4;
}
var3 = rs;
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var3;
}
public final int executeUpdate(String sql) throws SQLException {
this.checkOpen();
this.incrementExecuteUpdateCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
int var2;
try {
var2 = this.stmt.executeUpdate(sql);
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var2;
}
protected final void errorCheck(Throwable t) {
String errorClassName = t.getClass().getName();
if (errorClassName.endsWith(".CommunicationsException") && this.conn.holder != null && this.conn.holder.dataSource.testWhileIdle) {
DruidConnectionHolder holder = this.conn.holder;
DruidAbstractDataSource dataSource = holder.dataSource;
long currentTimeMillis = System.currentTimeMillis();
long lastActiveTimeMillis = holder.lastActiveTimeMillis;
if (lastActiveTimeMillis < holder.lastKeepTimeMillis) {
lastActiveTimeMillis = holder.lastKeepTimeMillis;
}
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long lastValidIdleMillis = currentTimeMillis - holder.lastActiveTimeMillis;
String errorMsg = "CommunicationsException, druid version " + VERSION.getVersionNumber() + ", jdbcUrl : " + dataSource.jdbcUrl + ", testWhileIdle " + dataSource.testWhileIdle + ", idle millis " + idleMillis + ", minIdle " + dataSource.minIdle + ", poolingCount " + dataSource.getPoolingCount() + ", timeBetweenEvictionRunsMillis " + dataSource.timeBetweenEvictionRunsMillis + ", lastValidIdleMillis " + lastValidIdleMillis + ", driver " + dataSource.driver.getClass().getName();
if (dataSource.exceptionSorter != null) {
errorMsg = errorMsg + ", exceptionSorter " + dataSource.exceptionSorter.getClass().getName();
}
LOG.error(errorMsg);
}
}
public final int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
this.checkOpen();
this.incrementExecuteUpdateCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
int var3;
try {
var3 = this.stmt.executeUpdate(sql, autoGeneratedKeys);
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var3;
}
public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
this.checkOpen();
this.incrementExecuteUpdateCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
int var3;
try {
var3 = this.stmt.executeUpdate(sql, columnIndexes);
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var3;
}
public final int executeUpdate(String sql, String[] columnNames) throws SQLException {
this.checkOpen();
this.incrementExecuteUpdateCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
int var3;
try {
var3 = this.stmt.executeUpdate(sql, columnNames);
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var3;
}
public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
this.checkOpen();
this.incrementExecuteCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
boolean var3;
try {
var3 = this.stmt.execute(sql, autoGeneratedKeys);
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var3;
}
public final boolean execute(String sql, int[] columnIndexes) throws SQLException {
this.checkOpen();
this.incrementExecuteCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
boolean var3;
try {
var3 = this.stmt.execute(sql, columnIndexes);
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var3;
}
public final boolean execute(String sql, String[] columnNames) throws SQLException {
this.checkOpen();
this.incrementExecuteCount();
this.transactionRecord(sql);
this.conn.beforeExecute();
boolean var3;
try {
var3 = this.stmt.execute(sql, columnNames);
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t, sql);
} finally {
this.conn.afterExecute();
}
return var3;
}
public int getMaxFieldSize() throws SQLException {
this.checkOpen();
try {
return this.stmt.getMaxFieldSize();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public void close() throws SQLException {
if (!this.closed) {
this.clearResultSet();
if (this.stmt != null) {
this.stmt.close();
}
this.closed = true;
DruidConnectionHolder connHolder = this.conn.getConnectionHolder();
if (connHolder != null) {
connHolder.removeTrace(this);
}
}
}
public void setMaxFieldSize(int max) throws SQLException {
this.checkOpen();
try {
this.stmt.setMaxFieldSize(max);
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getMaxRows() throws SQLException {
this.checkOpen();
try {
return this.stmt.getMaxRows();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public void setMaxRows(int max) throws SQLException {
this.checkOpen();
try {
this.stmt.setMaxRows(max);
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final void setEscapeProcessing(boolean enable) throws SQLException {
this.checkOpen();
try {
this.stmt.setEscapeProcessing(enable);
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getQueryTimeout() throws SQLException {
this.checkOpen();
try {
return this.stmt.getQueryTimeout();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public void setQueryTimeout(int seconds) throws SQLException {
this.checkOpen();
try {
this.stmt.setQueryTimeout(seconds);
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final void cancel() throws SQLException {
this.checkOpen();
try {
this.stmt.cancel();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final SQLWarning getWarnings() throws SQLException {
this.checkOpen();
try {
return this.stmt.getWarnings();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final void clearWarnings() throws SQLException {
this.checkOpen();
try {
this.stmt.clearWarnings();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final void setCursorName(String name) throws SQLException {
this.checkOpen();
try {
this.stmt.setCursorName(name);
} catch (Throwable t) {
throw this.checkException(t);
}
}
@Override
public final boolean execute(String sql) throws SQLException {
checkOpen();
incrementExecuteCount();
transactionRecord(sql);
try {
if (StringUtils.isNotEmpty(sql)){
sql = sql.replace("TRUE", "1");
sql = sql.replace("FALSE", "0");
}
return stmt.execute(sql);
} catch (Throwable t) {
errorCheck(t);
throw checkException(t, sql);
}
}
public final ResultSet getResultSet() throws SQLException {
this.checkOpen();
try {
ResultSet rs = this.stmt.getResultSet();
if (rs == null) {
return null;
} else {
DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);
this.addResultSetTrace(poolableResultSet);
return poolableResultSet;
}
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getUpdateCount() throws SQLException {
this.checkOpen();
try {
return this.stmt.getUpdateCount();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final boolean getMoreResults() throws SQLException {
this.checkOpen();
try {
boolean moreResults = this.stmt.getMoreResults();
if (this.resultSetTrace != null && this.resultSetTrace.size() > 0) {
ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(this.resultSetTrace.size() - 1);
if (lastResultSet instanceof DruidPooledResultSet) {
DruidPooledResultSet pooledResultSet = (DruidPooledResultSet)lastResultSet;
pooledResultSet.closed = true;
}
}
return moreResults;
} catch (Throwable t) {
throw this.checkException(t);
}
}
public void setFetchDirection(int direction) throws SQLException {
this.checkOpen();
try {
this.stmt.setFetchDirection(direction);
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getFetchDirection() throws SQLException {
this.checkOpen();
try {
return this.stmt.getFetchDirection();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public void setFetchSize(int rows) throws SQLException {
this.checkOpen();
try {
this.stmt.setFetchSize(rows);
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getFetchSize() throws SQLException {
this.checkOpen();
try {
return this.stmt.getFetchSize();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getResultSetConcurrency() throws SQLException {
this.checkOpen();
try {
return this.stmt.getResultSetConcurrency();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getResultSetType() throws SQLException {
this.checkOpen();
try {
return this.stmt.getResultSetType();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final void addBatch(String sql) throws SQLException {
this.checkOpen();
this.transactionRecord(sql);
try {
this.stmt.addBatch(sql);
} catch (Throwable t) {
throw this.checkException(t, sql);
}
}
public final void clearBatch() throws SQLException {
if (!this.closed) {
try {
this.stmt.clearBatch();
} catch (Throwable t) {
throw this.checkException(t);
}
}
}
public int[] executeBatch() throws SQLException {
this.checkOpen();
this.incrementExecuteBatchCount();
this.conn.beforeExecute();
int[] var1;
try {
var1 = this.stmt.executeBatch();
} catch (Throwable t) {
this.errorCheck(t);
throw this.checkException(t);
} finally {
this.conn.afterExecute();
}
return var1;
}
public final Connection getConnection() throws SQLException {
this.checkOpen();
return this.conn;
}
public final boolean getMoreResults(int current) throws SQLException {
this.checkOpen();
try {
boolean results = this.stmt.getMoreResults(current);
if (this.resultSetTrace != null && this.resultSetTrace.size() > 0) {
ResultSet lastResultSet = (ResultSet)this.resultSetTrace.get(this.resultSetTrace.size() - 1);
if (lastResultSet instanceof DruidPooledResultSet) {
DruidPooledResultSet pooledResultSet = (DruidPooledResultSet)lastResultSet;
pooledResultSet.closed = true;
}
}
return results;
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final ResultSet getGeneratedKeys() throws SQLException {
this.checkOpen();
try {
ResultSet rs = this.stmt.getGeneratedKeys();
DruidPooledResultSet poolableResultSet = new DruidPooledResultSet(this, rs);
this.addResultSetTrace(poolableResultSet);
return poolableResultSet;
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final int getResultSetHoldability() throws SQLException {
this.checkOpen();
try {
return this.stmt.getResultSetHoldability();
} catch (Throwable t) {
throw this.checkException(t);
}
}
public final boolean isClosed() throws SQLException {
return this.closed;
}
public final void setPoolable(boolean poolable) throws SQLException {
if (!poolable) {
throw new SQLException("not support");
}
}
public final boolean isPoolable() throws SQLException {
return false;
}
public String toString() {
return this.stmt.toString();
}
public void closeOnCompletion() throws SQLException {
this.stmt.closeOnCompletion();
}
public boolean isCloseOnCompletion() throws SQLException {
return this.stmt.isCloseOnCompletion();
}
}

View File

@@ -0,0 +1,546 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package liquibase.database.core;
import java.lang.reflect.Method;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import liquibase.CatalogAndSchema;
import liquibase.GlobalConfiguration;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.executor.ExecutorService;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.statement.UniqueConstraint;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawParameterizedSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Column;
import liquibase.structure.core.Index;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Schema;
import liquibase.util.JdbcUtil;
import liquibase.util.StringUtil;
import org.apache.commons.lang3.StringUtils;
public class DmDatabase extends AbstractJdbcDatabase {
private static final String PROXY_USER_REGEX = ".*(?:thin|oci)\\:(.+)/@.*";
public static final Pattern PROXY_USER_PATTERN = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
private static final String VERSION_REGEX = "(\\d+)\\.(\\d+)\\..*";
private static final Pattern VERSION_PATTERN = Pattern.compile("(\\d+)\\.(\\d+)\\..*");
public static final String PRODUCT_NAME = "DM DBMS";
private static final ResourceBundle coreBundle = ResourceBundle.getBundle("liquibase/i18n/liquibase-core");
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
public static final int ORACLE_12C_MAJOR_VERSION = 12;
public static final int ORACLE_23C_MAJOR_VERSION = 23;
private final Set<String> reservedWords = new HashSet();
private Set<String> userDefinedTypes;
private Map<String, String> savedSessionNlsSettings;
private Boolean canAccessDbaRecycleBin;
private Integer databaseMajorVersion;
private Integer databaseMinorVersion;
public DmDatabase() {
super.unquotedObjectsAreUppercased = true;
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
this.dateFunctions.add(new DatabaseFunction("SYSDATE"));
this.dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
this.dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
super.sequenceNextValueFunction = "%s.nextval";
super.sequenceCurrentValueFunction = "%s.currval";
}
public int getPriority() {
return 1;
}
private void tryProxySession(String url, Connection con) {
Matcher m = PROXY_USER_PATTERN.matcher(url);
if (m.matches()) {
Properties props = new Properties();
props.put("PROXY_USER_NAME", m.group(1));
try {
Method method = con.getClass().getMethod("openProxySession", Integer.TYPE, Properties.class);
method.setAccessible(true);
method.invoke(con, 1, props);
} catch (Exception e) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
return;
}
try {
Method method = con.getClass().getMethod("isProxySession");
method.setAccessible(true);
boolean b = (Boolean)method.invoke(con);
if (!b) {
Scope.getCurrentScope().getLog(this.getClass()).info("Proxy session not established on OracleDatabase: ");
}
} catch (Exception e) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
}
}
}
public void setConnection(DatabaseConnection conn) {
this.reservedWords.addAll(Arrays.asList("GROUP", "USER", "SESSION", "PASSWORD", "RESOURCE", "START", "SIZE", "UID", "DESC", "ORDER"));
Connection sqlConn = null;
if (!(conn instanceof OfflineConnection)) {
try {
if (conn instanceof JdbcConnection) {
sqlConn = ((JdbcConnection)conn).getWrappedConnection();
}
} catch (Exception e) {
throw new UnexpectedLiquibaseException(e);
}
if (sqlConn != null) {
this.tryProxySession(conn.getURL(), sqlConn);
try {
this.reservedWords.addAll(Arrays.asList(sqlConn.getMetaData().getSQLKeywords().toUpperCase().split(",\\s*")));
} catch (SQLException e) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could get sql keywords on OracleDatabase: " + e.getMessage());
}
try {
Method method = sqlConn.getClass().getMethod("setRemarksReporting", Boolean.TYPE);
method.setAccessible(true);
method.invoke(sqlConn, true);
} catch (Exception e) {
Scope.getCurrentScope().getLog(this.getClass()).info("Could not set remarks reporting on OracleDatabase: " + e.getMessage());
}
CallableStatement statement = null;
try {
statement = sqlConn.prepareCall("{call DBMS_UTILITY.DB_VERSION(?,?)}");
statement.registerOutParameter(1, 12);
statement.registerOutParameter(2, 12);
statement.execute();
String compatibleVersion = statement.getString(2);
if (compatibleVersion != null) {
Matcher majorVersionMatcher = VERSION_PATTERN.matcher(compatibleVersion);
if (majorVersionMatcher.matches()) {
this.databaseMajorVersion = Integer.valueOf(majorVersionMatcher.group(1));
this.databaseMinorVersion = Integer.valueOf(majorVersionMatcher.group(2));
}
}
} catch (SQLException e) {
String message = "Cannot read from DBMS_UTILITY.DB_VERSION: " + e.getMessage();
Scope.getCurrentScope().getLog(this.getClass()).info("Could not set check compatibility mode on OracleDatabase, assuming not running in any sort of compatibility mode: " + message);
} finally {
JdbcUtil.closeStatement(statement);
}
if (GlobalConfiguration.DDL_LOCK_TIMEOUT.getCurrentValue() != null) {
int timeoutValue = (Integer)GlobalConfiguration.DDL_LOCK_TIMEOUT.getCurrentValue();
Scope.getCurrentScope().getLog(this.getClass()).fine("Setting DDL_LOCK_TIMEOUT value to " + timeoutValue);
String sql = "ALTER SESSION SET DDL_LOCK_TIMEOUT=" + timeoutValue;
PreparedStatement ddlLockTimeoutStatement = null;
try {
ddlLockTimeoutStatement = sqlConn.prepareStatement(sql);
ddlLockTimeoutStatement.execute();
} catch (SQLException sqle) {
Scope.getCurrentScope().getUI().sendErrorMessage("Unable to set the DDL_LOCK_TIMEOUT_VALUE: " + sqle.getMessage(), sqle);
Scope.getCurrentScope().getLog(this.getClass()).warning("Unable to set the DDL_LOCK_TIMEOUT_VALUE: " + sqle.getMessage(), sqle);
} finally {
JdbcUtil.closeStatement(ddlLockTimeoutStatement);
}
}
}
}
super.setConnection(conn);
}
public String getShortName() {
return "dm";
}
protected String getDefaultDatabaseProductName() {
return PRODUCT_NAME;
}
public int getDatabaseMajorVersion() throws DatabaseException {
return this.databaseMajorVersion == null ? super.getDatabaseMajorVersion() : this.databaseMajorVersion;
}
public int getDatabaseMinorVersion() throws DatabaseException {
return this.databaseMinorVersion == null ? super.getDatabaseMinorVersion() : this.databaseMinorVersion;
}
public Integer getDefaultPort() {
return 5236;
}
public String getJdbcCatalogName(CatalogAndSchema schema) {
return null;
}
public String getJdbcSchemaName(CatalogAndSchema schema) {
return this.correctObjectName(schema.getCatalogName() == null ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
}
protected String getAutoIncrementClause(String generationType, Boolean defaultOnNull) {
if (StringUtil.isEmpty(generationType)) {
return super.getAutoIncrementClause();
} else {
String autoIncrementClause = "GENERATED %s AS IDENTITY";
String generationStrategy = generationType;
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
generationStrategy = generationType + " ON NULL";
}
return String.format(autoIncrementClause, generationStrategy);
}
}
public String generatePrimaryKeyName(String tableName) {
return tableName.length() > 27 ? "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27) : "PK_" + tableName.toUpperCase(Locale.US);
}
public boolean supportsInitiallyDeferrableColumns() {
return true;
}
public boolean isReservedWord(String objectName) {
return this.reservedWords.contains(objectName.toUpperCase());
}
public boolean supportsSequences() {
return true;
}
public boolean supports(Class<? extends DatabaseObject> object) {
return Schema.class.isAssignableFrom(object) ? false : super.supports(object);
}
public boolean supportsSchemas() {
return false;
}
protected String getConnectionCatalogName() throws DatabaseException {
if (this.getConnection() instanceof OfflineConnection) {
return this.getConnection().getCatalog();
} else if (!(this.getConnection() instanceof JdbcConnection)) {
return this.defaultCatalogName;
} else {
try {
return (String)((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
} catch (Exception e) {
Scope.getCurrentScope().getLog(this.getClass()).info("Error getting default schema", e);
return null;
}
}
}
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return "oracle".equalsIgnoreCase(conn.getDatabaseProductName());
}
public String getDefaultDriver(String url) {
return url.startsWith("jdbc:dm") ? "dm.jdbc.driver.DmDriver" : null;
}
public String getDefaultCatalogName() {
String defaultCatalogName = super.getDefaultCatalogName();
if (Boolean.TRUE.equals(GlobalConfiguration.PRESERVE_SCHEMA_CASE.getCurrentValue())) {
return defaultCatalogName;
} else {
return defaultCatalogName == null ? null : defaultCatalogName.toUpperCase(Locale.US);
}
}
public String getDateLiteral(String isoDate) {
String normalLiteral = super.getDateLiteral(isoDate);
if (this.isDateOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
} else if (this.isTimeOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
} else if (this.isTimestamp(isoDate)) {
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
} else if (this.isDateTime(isoDate)) {
int seppos = normalLiteral.lastIndexOf(46);
if (seppos != -1) {
normalLiteral = normalLiteral.substring(0, seppos) + "'";
}
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
} else {
return "UNSUPPORTED:" + isoDate;
}
}
public boolean isSystemObject(DatabaseObject example) {
if (example == null) {
return false;
} else if (this.isLiquibaseObject(example)) {
return false;
} else {
if (example instanceof Schema) {
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
return true;
}
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
return true;
}
} else if (this.isSystemObject(example.getSchema())) {
return true;
}
if (example instanceof Catalog) {
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
return true;
}
} else if (example.getName() != null) {
if (example.getName().startsWith("BIN$")) {
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
if (!filteredInOriginalQuery) {
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
}
if (!filteredInOriginalQuery) {
return true;
}
return !(example instanceof PrimaryKey) && !(example instanceof Index) && !(example instanceof UniqueConstraint);
}
if (example.getName().startsWith("AQ$")) {
return true;
}
if (example.getName().startsWith("DR$")) {
return true;
}
if (example.getName().startsWith("SYS_IOT_OVER")) {
return true;
}
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
return true;
}
if (example.getName().startsWith("MLOG$_")) {
return true;
}
if (example.getName().startsWith("RUPD$_")) {
return true;
}
if (example.getName().startsWith("WM$_")) {
return true;
}
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) {
return true;
}
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) {
return true;
}
if (example.getName().startsWith("ISEQ$$_")) {
return true;
}
if (example.getName().startsWith("USLOG$")) {
return true;
}
if (example.getName().startsWith("SYS_FBA")) {
return true;
}
}
return super.isSystemObject(example);
}
}
public boolean supportsTablespaces() {
return true;
}
public boolean supportsAutoIncrement() {
boolean isAutoIncrementSupported = false;
try {
if (this.getDatabaseMajorVersion() >= 12) {
isAutoIncrementSupported = true;
}
} catch (DatabaseException var3) {
isAutoIncrementSupported = false;
}
return isAutoIncrementSupported;
}
public boolean supportsRestrictForeignKeys() {
return false;
}
public int getDataTypeMaxParameters(String dataTypeName) {
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
return 0;
} else {
return "BINARY_DOUBLE".equals(dataTypeName.toUpperCase()) ? 0 : super.getDataTypeMaxParameters(dataTypeName);
}
}
public String getSystemTableWhereClause(String tableNameColumn) {
List<String> clauses = new ArrayList(Arrays.asList("BIN$", "AQ$", "DR$", "SYS_IOT_OVER", "MLOG$_", "RUPD$_", "WM$_", "ISEQ$$_", "USLOG$", "SYS_FBA"));
clauses.replaceAll((s) -> tableNameColumn + " NOT LIKE '" + s + "%'");
return "(" + StringUtil.join(clauses, " AND ") + ")";
}
public boolean jdbcCallsCatalogsSchemas() {
return true;
}
public Set<String> getUserDefinedTypes() {
if (this.userDefinedTypes == null) {
this.userDefinedTypes = new HashSet();
if (this.getConnection() != null && !(this.getConnection() instanceof OfflineConnection)) {
try {
try {
this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawParameterizedSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
} catch (DatabaseException var2) {
this.userDefinedTypes.addAll(((ExecutorService)Scope.getCurrentScope().getSingleton(ExecutorService.class)).getExecutor("jdbc", this).queryForList(new RawParameterizedSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
}
} catch (DatabaseException var3) {
}
}
}
return this.userDefinedTypes;
}
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
if (databaseFunction != null && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
return databaseFunction.toString();
} else if (!(databaseFunction instanceof SequenceNextValueFunction) && !(databaseFunction instanceof SequenceCurrentValueFunction)) {
return super.generateDatabaseFunctionValue(databaseFunction);
} else {
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
return quotedSeq.replaceFirst("\"([^.\"]+)\\.([^.\"]+)\"", "\"$1\".\"$2\"");
}
}
public ValidationErrors validate() {
ValidationErrors errors = super.validate();
DatabaseConnection connection = this.getConnection();
if (connection != null && !(connection instanceof OfflineConnection)) {
if (!this.canAccessDbaRecycleBin()) {
errors.addWarning(this.getDbaRecycleBinWarning());
}
return errors;
} else {
Scope.getCurrentScope().getLog(this.getClass()).info("Cannot validate offline database");
return errors;
}
}
public String getDbaRecycleBinWarning() {
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where constraints are deleted and restored. Since Oracle doesn't properly restore the original table names referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this issue.\n\nThe user you used to connect to the database (" + this.getConnection().getConnectionUserName() + ") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. Please run the following SQL to set the appropriate permissions, and try running the command again.\n\n GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + this.getConnection().getConnectionUserName() + ";";
}
public boolean canAccessDbaRecycleBin() {
if (this.canAccessDbaRecycleBin == null) {
DatabaseConnection connection = this.getConnection();
if (connection == null || connection instanceof OfflineConnection) {
return false;
}
Statement statement = null;
try {
statement = ((JdbcConnection)connection).createStatement();
ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
resultSet.close();
this.canAccessDbaRecycleBin = true;
} catch (Exception var7) {
if (var7 instanceof SQLException && var7.getMessage().startsWith("ORA-00942")) {
this.canAccessDbaRecycleBin = false;
} else {
Scope.getCurrentScope().getLog(this.getClass()).warning("Cannot check dba_recyclebin access", var7);
this.canAccessDbaRecycleBin = false;
}
} finally {
JdbcUtil.close((ResultSet)null, statement);
}
}
return this.canAccessDbaRecycleBin;
}
public boolean supportsNotNullConstraintNames() {
return true;
}
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
if (identifier != null && identifier.length() >= 1) {
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$")) {
return false;
} else {
return identifier.length() <= 128;
}
} else {
return false;
}
}
public int getIdentifierMaximumLength() {
try {
if (this.getDatabaseMajorVersion() < 12) {
return 30;
} else {
return this.getDatabaseMajorVersion() == 12 && this.getDatabaseMinorVersion() <= 1 ? 30 : 128;
}
} catch (DatabaseException ex) {
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
}
}
public boolean supportsDatabaseChangeLogHistory() {
return true;
}
public String correctObjectName(String objectName, Class<? extends DatabaseObject> objectType) {
return objectType.equals(Column.class) && StringUtils.startsWithIgnoreCase(objectName, "int") ? "NUMBER(*, 0)" : super.correctObjectName(objectName, objectType);
}
}

View File

@@ -0,0 +1,149 @@
package liquibase.datatype.core;
import liquibase.change.core.LoadDataChange;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
import java.util.Locale;
import java.util.regex.Pattern;
@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
public class BooleanType extends LiquibaseDataType {
@Override
public DatabaseDataType toDatabaseDataType(Database database) {
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
// if ((database instanceof Firebird3Database)) {
// return new DatabaseDataType("BOOLEAN");
// }
if ((database instanceof AbstractDb2Database) || (database instanceof FirebirdDatabase)) {
return new DatabaseDataType("SMALLINT");
} else if (database instanceof MSSQLDatabase) {
return new DatabaseDataType(database.escapeDataTypeName("bit"));
} else if (database instanceof MySQLDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
return new DatabaseDataType("BIT", 1);
} else if (database instanceof OracleDatabase) {
return new DatabaseDataType("NUMBER", 1);
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
return new DatabaseDataType("BIT");
} else if (database instanceof DerbyDatabase) {
if (((DerbyDatabase) database).supportsBooleanDataType()) {
return new DatabaseDataType("BOOLEAN");
} else {
return new DatabaseDataType("SMALLINT");
}
} else if (database.getClass().isAssignableFrom(DB2Database.class)) {
if (((DB2Database) database).supportsBooleanDataType())
return new DatabaseDataType("BOOLEAN");
else
return new DatabaseDataType("SMALLINT");
} else if (database instanceof HsqlDatabase) {
return new DatabaseDataType("BOOLEAN");
} else if (database instanceof PostgresDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
} else if(database instanceof DmDatabase) {
return new DatabaseDataType("bit");
}
return super.toDatabaseDataType(database);
}
@Override
public String objectToSql(Object value, Database database) {
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
return null;
}
String returnValue;
if (value instanceof String) {
value = ((String) value).replaceAll("'", "");
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getTrueBooleanValue(database);
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getFalseBooleanValue(database);
} else {
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
}
} else if (value instanceof Long) {
if (Long.valueOf(1).equals(value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof Number) {
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof DatabaseFunction) {
return value.toString();
} else if (value instanceof Boolean) {
if (((Boolean) value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else {
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
}
return returnValue;
}
protected boolean isNumericBoolean(Database database) {
if (database instanceof DerbyDatabase) {
return !((DerbyDatabase) database).supportsBooleanDataType();
} else if (database.getClass().isAssignableFrom(DB2Database.class)) {
return !((DB2Database) database).supportsBooleanDataType();
}
return (database instanceof Db2zDatabase) || (database instanceof DB2Database) || (database instanceof FirebirdDatabase) || (database instanceof
MSSQLDatabase) || (database instanceof MySQLDatabase) || (database instanceof OracleDatabase) ||
(database instanceof SQLiteDatabase) || (database instanceof SybaseASADatabase) || (database instanceof
SybaseDatabase) || (database instanceof DmDatabase);
}
/**
* The database-specific value to use for "false" "boolean" columns.
*/
public String getFalseBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "0";
}
if (database instanceof InformixDatabase) {
return "'f'";
}
return "FALSE";
}
/**
* The database-specific value to use for "true" "boolean" columns.
*/
public String getTrueBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "1";
}
if (database instanceof InformixDatabase) {
return "'t'";
}
return "TRUE";
}
@Override
public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
}
}

View File

@@ -0,0 +1 @@
防止IDEA将`.``/`混为一谈

View File

@@ -0,0 +1,21 @@
liquibase.database.core.CockroachDatabase
liquibase.database.core.DB2Database
liquibase.database.core.Db2zDatabase
liquibase.database.core.DerbyDatabase
#liquibase.database.core.Firebird3Database
liquibase.database.core.FirebirdDatabase
liquibase.database.core.H2Database
liquibase.database.core.HsqlDatabase
liquibase.database.core.InformixDatabase
liquibase.database.core.Ingres9Database
liquibase.database.core.MSSQLDatabase
liquibase.database.core.MariaDBDatabase
liquibase.database.core.MockDatabase
liquibase.database.core.MySQLDatabase
liquibase.database.core.OracleDatabase
liquibase.database.core.PostgresDatabase
liquibase.database.core.SQLiteDatabase
liquibase.database.core.SybaseASADatabase
liquibase.database.core.SybaseDatabase
liquibase.database.core.DmDatabase
liquibase.database.core.UnsupportedDatabase

View File

@@ -0,0 +1,113 @@
--- #################### 注册中心 + 配置中心相关配置 ####################
spring:
cloud:
nacos:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
username: # Nacos 账号
password: # Nacos 密码
discovery: # 【配置中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
autoconfigure:
exclude:
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/*
login-username: # 控制台管理用户名和密码
login-password:
filter:
stat:
enabled: true
log-slow-sql: true # 慢 SQL 记录
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 5 # 初始连接数
min-idle: 10 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: root
password: 123456
slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: root
password: 123456
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
host: 400-infra.server.iocoder.cn # 地址
port: 6379 # 端口
database: 1 # 数据库索引
# password: 123456 # 密码,建议生产环境开启
--- #################### MQ 消息队列相关配置 ####################
--- #################### 定时任务相关配置 ####################
xxl:
job:
admin:
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项
lock4j:
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
instance:
service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
demo: true # 开启演示模式

View File

@@ -0,0 +1,137 @@
--- #################### 注册中心 + 配置中心相关配置 ####################
spring:
cloud:
nacos:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
username: # Nacos 账号
password: # Nacos 密码
discovery: # 【配置中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
autoconfigure:
exclude:
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/*
login-username: # 控制台管理用户名和密码
login-password:
filter:
stat:
enabled: true
log-slow-sql: true # 慢 SQL 记录
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 1 # 初始连接数
min-idle: 1 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro # SQLServer 连接的示例
# url: jdbc:dm://172.16.46.247:1050?schema=RUOYI-VUE-PRO # DM 连接的示例
username: root
password: 123456
# username: sa # SQL Server 连接的示例
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W # SQL Server 连接的示例
# username: SYSDBA # DM 连接的示例
# password: pgbsci6ddJ6Sqj@e # DM 连接的示例
slave: # 模拟从库,可根据自己需要修改
lazy: true # 开启懒加载,保证启动速度
url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # DM 连接的示例
username: SYSDBA # DM 连接的示例
password: pgbsci6ddJ6Sqj@e # DM 连接的示例
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
data:
redis:
host: 127.0.0.1 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
# password: 123456 # 密码,建议生产环境开启
--- #################### MQ 消息队列相关配置 ####################
--- #################### 定时任务相关配置 ####################
xxl:
job:
enabled: false # 是否开启调度中心,默认为 true 开启
admin:
addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项
lock4j:
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
instance:
service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
# 日志文件配置
logging:
level:
# 配置自己写的 MyBatis Mapper 打印日志
cn.iocoder.yudao.module.bpm.dal.mysql: debug
org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿先禁用Spring Boot 3.X 存在部分错误的 WARN 提示
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
env: # 多环境的配置项
tag: ${HOSTNAME}
security:
mock-enable: true
access-log: # 访问日志的配置项
enable: false

View File

@@ -0,0 +1,136 @@
spring:
application:
name: bpm-server
profiles:
active: local
main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
config:
import:
- optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
# Cache 配置项
cache:
type: REDIS
redis:
time-to-live: 1h # 设置过期时间为 1 小时
server:
port: 48083
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
--- #################### 接口文档配置 ####################
springdoc:
api-docs:
enabled: true # 1. 是否开启 Swagger 接文档的元数据
path: /v3/api-docs
swagger-ui:
enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
path: /swagger-ui
default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
knife4j:
enable: false # TODO 芋艿需要关闭增强具体原因见https://github.com/xiaoymin/knife4j/issues/874
setting:
language: zh_cn
# 工作流 Flowable 配置
flowable:
# 1. false: 默认值Flowable 启动时,对比数据库表中保存的版本,如果不匹配。将抛出异常
# 2. true: 启动时会对数据库中所有表进行更新操作,如果表存在,不做处理,反之,自动创建表
# 3. create_drop: 启动时自动创建表,关闭时自动删除表
# 4. drop_create: 启动时,删除旧表,再创建新表
database-schema-update: false # 设置为 false可通过 https://github.com/flowable/flowable-sql 初始化
db-history-used: true # flowable6 默认 true 生成信息表,无需手动设置
check-process-definitions: false # 设置为 false禁用 /resources/processes 自动部署 BPMN XML 流程
history-level: audit # full保存历史数据的最高级别可保存全部流程相关细节包括流程流转各节点参数
# MyBatis Plus 的配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
# id-type: AUTO # 自增 ID适合 MySQL 等直接自增的数据库
# id-type: INPUT # 用户输入 ID适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
# id-type: ASSIGN_ID # 分配 ID默认使用雪花算法。注意Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
banner: false # 关闭控制台的 Banner 打印
type-aliases-package: ${yudao.info.base-package}.dal.dataobject
encryptor:
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
mybatis-plus-join:
banner: false # 关闭控制台的 Banner 打印
# Spring Data Redis 配置
spring:
data:
redis:
repositories:
enabled: false # 项目未使用到 Spring Data Redis 的 Repository所以直接禁用保证启动速度
# VO 转换(数据翻译)相关
easy-trans:
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
--- #################### RPC 远程调用相关配置 ####################
--- #################### MQ 消息队列相关配置 ####################
--- #################### 定时任务相关配置 ####################
xxl:
job:
executor:
appname: ${spring.application.name} # 执行器 AppName
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
accessToken: default_token # 执行器通讯TOKEN
--- #################### 芋道相关配置 ####################
yudao:
info:
version: 1.0.0
base-package: cn.iocoder.yudao.module.bpm
web:
admin-ui:
url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
xss:
enable: false
exclude-urls: # 如下 url仅仅是为了演示去掉配置也没关系
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
swagger:
title: 管理后台
description: 提供管理员管理的所有功能
version: ${yudao.info.version}
tenant: # 多租户相关配置项
enable: true
debug: false

View File

@@ -0,0 +1,76 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 变量 yudao.info.base-package基础业务包 -->
<springProperty scope="context" name="yudao.info.base-package" source="yudao.info.base-package"/>
<!-- 格式化输出:%d 表示日期,%X{tid} SkWalking 链路追踪编号,%thread 表示线程名,%-5level级别从左显示 5 个字符宽度,%msg日志消息%n是换行符 -->
<property name="PATTERN_DEFAULT" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} | %highlight(${LOG_LEVEL_PATTERN:-%5p} ${PID:- }) | %boldYellow(%thread [%tid]) %boldGreen(%-40.40logger{39}) | %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- 控制台 Appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 文件 Appender -->
<!-- 参考 Spring Boot 的 file-appender.xml 编写 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
<!-- 日志文件名 -->
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 滚动后的日志文件名 -->
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<!-- 日志文件,到达多少容量,进行滚动 -->
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<!-- 日志文件的总大小0 表示不限制 -->
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<!-- 日志文件的保留天数 -->
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>
<!-- 异步写入日志,提升性能 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志。默认的,如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能。默认值为 256 -->
<queueSize>256</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!-- SkyWalking GRPC 日志收集实现日志中心。注意SkyWalking 8.4.0 版本开始支持 -->
<appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 本地环境 -->
<springProfile name="local">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
</root>
</springProfile>
<!-- 其它环境 -->
<springProfile name="dev,test,stage,prod,default">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC"/>
<appender-ref ref="GRPC"/>
</root>
</springProfile>
</configuration>

View File

@@ -0,0 +1,274 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other.BpmTaskCandidateAssignEmptyStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Spy;
import org.mockito.internal.util.collections.Sets;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link BpmTaskCandidateInvoker} 的单元测试
*
* @author 芋道源码
*/
public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest {
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Mock
private AdminUserApi adminUserApi;
@Mock
private BpmProcessInstanceService processInstanceService;
@Spy
private BpmTaskCandidateStrategy userStrategy;
@Mock
private BpmTaskCandidateAssignEmptyStrategy emptyStrategy;
@Spy
private List<BpmTaskCandidateStrategy> strategyList;
@BeforeEach
public void setUp() {
userStrategy = new BpmTaskCandidateUserStrategy(); // 创建 strategy 实例
when(emptyStrategy.getStrategy()).thenReturn(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY);
strategyList = List.of(userStrategy, emptyStrategy); // 创建 strategyList
taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi);
}
/**
* 场景:成功计算到候选人,但是移除了发起人的用户
*/
@Test
public void testCalculateUsersByTask_some() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
// 准备参数
String param = "1,2";
DelegateExecution execution = mock(DelegateExecution.class);
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
String processInstanceId = randomString();
when(execution.getProcessInstanceId()).thenReturn(processInstanceId);
when(execution.getCurrentFlowElement()).thenReturn(userTask);
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
.thenReturn(param);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 移除发起人的用户
springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class))
.thenReturn(processInstanceService);
ProcessInstance processInstance = mock(ProcessInstance.class);
when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn("1");
mockFlowElementExtensionElement(userTask, BpmnModelConstants.USER_TASK_ASSIGN_START_USER_HANDLER_TYPE,
String.valueOf(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType()));
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution);
// 断言
assertEquals(asSet(2L), results);
}
}
/**
* 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配
*/
@Test
public void testCalculateUsersByTask_none() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
// 准备参数
String param = "1,2";
DelegateExecution execution = mock(DelegateExecution.class);
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
String processInstanceId = randomString();
when(execution.getProcessInstanceId()).thenReturn(processInstanceId);
when(execution.getCurrentFlowElement()).thenReturn(userTask);
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
.thenReturn(param);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 方法empty
when(emptyStrategy.calculateUsersByTask(same(execution), same(param)))
.thenReturn(Sets.newSet(2L));
// mock 移除发起人的用户
springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class))
.thenReturn(processInstanceService);
ProcessInstance processInstance = mock(ProcessInstance.class);
when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn("1");
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution);
// 断言
assertEquals(asSet(2L), results);
}
}
/**
* 场景:没有计算到候选人,但是被禁用移除,最终通过 empty 进行分配
*/
@Test
public void testCalculateUsersByActivity_some() {
try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
// 准备参数
String param = "1,2";
BpmnModel bpmnModel = mock(BpmnModel.class);
String activityId = randomString();
Long startUserId = 1L;
String processDefinitionId = randomString();
Map<String, Object> processVariables = new HashMap<>();
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy());
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask)))
.thenReturn(param);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 移除发起人的用户
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignStartUserHandlerType(same(userTask)))
.thenReturn(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType());
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
startUserId, processDefinitionId, processVariables);
// 断言
assertEquals(asSet(2L), results);
}
}
/**
* 场景:成功计算到候选人,但是移除了发起人的用户
*/
@Test
public void testCalculateUsersByActivity_none() {
try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
// 准备参数
String param = "1,2";
BpmnModel bpmnModel = mock(BpmnModel.class);
String activityId = randomString();
Long startUserId = 1L;
String processDefinitionId = randomString();
Map<String, Object> processVariables = new HashMap<>();
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy());
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask)))
.thenReturn(param);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 方法empty
when(emptyStrategy.calculateUsersByActivity(same(bpmnModel), eq(activityId),
eq(param), same(startUserId), same(processDefinitionId), same(processVariables)))
.thenReturn(Sets.newSet(2L));
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
startUserId, processDefinitionId, processVariables);
// 断言
assertEquals(asSet(2L), results);
}
}
private static void mockFlowElementExtensionElement(FlowElement element, String name, String value) {
if (value == null) {
return;
}
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
extensionElement.setElementText(value);
extensionElement.setName(name);
// mock
Map<String, List<ExtensionElement>> extensionElements = element.getExtensionElements();
if (extensionElements == null) {
extensionElements = new LinkedHashMap<>();
}
extensionElements.put(name, Collections.singletonList(extensionElement));
when(element.getExtensionElements()).thenReturn(extensionElements);
}
@Test
public void testRemoveDisableUsers() {
// 准备参数. 1L 可以找到2L 是禁用的3L 找不到
Set<Long> assigneeUserIds = asSet(1L, 2L, 3L);
// mock 方法
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(assigneeUserIds))).thenReturn(userMap);
// 调用
taskCandidateInvoker.removeDisableUsers(assigneeUserIds);
// 断言
assertEquals(asSet(1L), assigneeUserIds);
}
}

View File

@@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.expression;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
public class BpmTaskAssignLeaderExpressionTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskAssignLeaderExpression expression;
@Mock
private AdminUserApi adminUserApi;
@Mock
private DeptApi deptApi;
@Mock
private BpmProcessInstanceService processInstanceService;
@Test
public void testCalculateUsers_noDept() {
// 准备参数
DelegateExecution execution = mockDelegateExecution(1L);
// mock 方法(startUser)
AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L));
when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser));
// mock 方法(getStartUserDept)没有部门
when(deptApi.getDept(eq(10L))).thenReturn(success(null));
// 调用
Set<Long> result = expression.calculateUsers(execution, 1);
// 断言
assertEquals(0, result.size());
}
@Test
public void testCalculateUsers_noParentDept() {
// 准备参数
DelegateExecution execution = mockDelegateExecution(1L);
// mock 方法(startUser)
AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L));
when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser));
DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L)
.setLeaderUserId(20L));
// mock 方法getDept
when(deptApi.getDept(eq(10L))).thenReturn(success(startUserDept));
when(deptApi.getDept(eq(100L))).thenReturn(success(null));
// 调用
Set<Long> result = expression.calculateUsers(execution, 2);
// 断言
assertEquals(asSet(20L), result);
}
@Test
public void testCalculateUsers_existParentDept() {
// 准备参数
DelegateExecution execution = mockDelegateExecution(1L);
// mock 方法(startUser)
AdminUserRespDTO startUser = randomPojo(AdminUserRespDTO.class, o -> o.setDeptId(10L));
when(adminUserApi.getUser(eq(1L))).thenReturn(success(startUser));
DeptRespDTO startUserDept = randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(100L)
.setLeaderUserId(20L));
when(deptApi.getDept(eq(10L))).thenReturn(success(startUserDept));
// mock 方法(父 dept
DeptRespDTO parentDept = randomPojo(DeptRespDTO.class, o -> o.setId(100L).setParentId(1000L)
.setLeaderUserId(200L));
when(deptApi.getDept(eq(100L))).thenReturn(success(parentDept));
// 调用
Set<Long> result = expression.calculateUsers(execution, 2);
// 断言
assertEquals(asSet(200L), result);
}
@SuppressWarnings("SameParameterValue")
private DelegateExecution mockDelegateExecution(Long startUserId) {
ExecutionEntityImpl execution = new ExecutionEntityImpl();
execution.setProcessInstanceId(randomString());
// mock 返回 startUserId
ExecutionEntityImpl processInstance = new ExecutionEntityImpl();
processInstance.setStartUserId(String.valueOf(startUserId));
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId())))
.thenReturn(processInstance);
return execution;
}
}

View File

@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateDeptLeaderMultiStrategy strategy;
@Mock
private DeptApi deptApi;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "10,20|2";
// mock 方法
when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult<DeptRespDTO>>) invocationOnMock -> {
Long deptId = invocationOnMock.getArgument(0);
return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)));
});
// 调用
Set<Long> userIds = strategy.calculateUsers(param);
// 断言结果
assertEquals(Sets.newLinkedHashSet(11L, 1001L, 21L, 2001L), userIds);
}
}

View File

@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateDeptLeaderStrategy strategy;
@Mock
private DeptApi deptApi;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "10,20";
// mock 方法
when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList(
randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(10L).setLeaderUserId(11L)),
randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L)))));
// 调用
Set<Long> userIds = strategy.calculateUsers(param);
// 断言结果
assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
}
}

View File

@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateDeptMemberStrategy strategy;
@Mock
private DeptApi deptApi;
@Mock
private AdminUserApi adminUserApi;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "10,20";
// mock 方法
when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList(
randomPojo(AdminUserRespDTO.class, o -> o.setId(11L)),
randomPojo(AdminUserRespDTO.class, o -> o.setId(21L)))));
// 调用
Set<Long> userIds = strategy.calculateUsers(param);
// 断言结果
assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
}
}

View File

@@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.assertj.core.util.Sets;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateStartUserDeptLeaderMultiStrategy strategy;
@Mock
private BpmProcessInstanceService processInstanceService;
@Mock
private AdminUserApi adminUserApi;
@Mock
private DeptApi deptApi;
@Test
public void testCalculateUsersByTask() {
// 准备参数
String param = "2";
// mock 方法(获得流程发起人)
Long startUserId = 1L;
ProcessInstance processInstance = mock(ProcessInstance.class);
DelegateExecution execution = mock(DelegateExecution.class);
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
// mock 方法(获取发起人的 multi 部门负责人)
mockGetStartUserDept(startUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
// 断言
assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
}
@Test
public void testCalculateUsersByActivity() {
// 准备参数
String param = "2";
// mock 方法
Long startUserId = 1L;
mockGetStartUserDept(startUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
startUserId, null, null);
// 断言
assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
}
private void mockGetStartUserDept(Long startUserId) {
when(adminUserApi.getUser(eq(startUserId))).thenReturn(
success(randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L))));
when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult<DeptRespDTO>>) invocationOnMock -> {
Long deptId = invocationOnMock.getArgument(0);
return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)));
});
}
}

View File

@@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.assertj.core.util.Sets;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateStartUserDeptLeaderStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateStartUserDeptLeaderStrategy strategy;
@Mock
private BpmProcessInstanceService processInstanceService;
@Mock
private AdminUserApi adminUserApi;
@Mock
private DeptApi deptApi;
@Test
public void testCalculateUsersByTask() {
// 准备参数
String param = "2";
// mock 方法(获得流程发起人)
Long startUserId = 1L;
ProcessInstance processInstance = mock(ProcessInstance.class);
DelegateExecution execution = mock(DelegateExecution.class);
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
// mock 方法(获取发起人的部门负责人)
mockGetStartUserDeptLeader(startUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
// 断言
assertEquals(Sets.newLinkedHashSet(1001L), userIds);
}
@Test
public void testGetStartUserDeptLeader() {
// 准备参数
String param = "2";
// mock 方法
Long startUserId = 1L;
mockGetStartUserDeptLeader(startUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
startUserId, null, null);
// 断言
assertEquals(Sets.newLinkedHashSet(1001L), userIds);
}
private void mockGetStartUserDeptLeader(Long startUserId) {
when(adminUserApi.getUser(eq(startUserId))).thenReturn(
success(randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L))));
when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult<DeptRespDTO>>) invocationOnMock -> {
Long deptId = invocationOnMock.getArgument(0);
return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)));
});
}
}

View File

@@ -0,0 +1,68 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.map.MapUtil;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import org.assertj.core.util.Sets;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateStartUserSelectStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateStartUserSelectStrategy strategy;
@Mock
private BpmProcessInstanceService processInstanceService;
@Test
public void testCalculateUsersByTask() {
// 准备参数
String param = "2";
// mock 方法(获得流程发起人)
ProcessInstance processInstance = mock(ProcessInstance.class);
DelegateExecution execution = mock(DelegateExecution.class);
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
when(execution.getCurrentActivityId()).thenReturn("activity_001");
// mock 方法FlowableUtils
Map<String, Object> processVariables = new HashMap<>();
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
MapUtil.of("activity_001", List.of(1L, 2L)));
when(processInstance.getProcessVariables()).thenReturn(processVariables);
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
// 断言
assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds);
}
@Test
public void testCalculateUsersByActivity() {
// 准备参数
String activityId = "activity_001";
Map<String, Object> processVariables = new HashMap<>();
processVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES,
MapUtil.of("activity_001", List.of(1L, 2L)));
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(null, activityId, null,
null, null, processVariables);
// 断言
assertEquals(Sets.newLinkedHashSet(1L, 2L), userIds);
}
}

View File

@@ -0,0 +1,88 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
import cn.hutool.core.collection.ListUtil;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.delegate.DelegateExecution;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import java.util.Set;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
public class BpmTaskCandidateAssignEmptyStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateAssignEmptyStrategy strategy;
@Mock
private BpmProcessDefinitionService processDefinitionService;
@Test
public void testCalculateUsersByTask() {
try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class);
MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
// 准备参数
DelegateExecution execution = mock(DelegateExecution.class);
String param = randomString();
// mock 方法execution
String processDefinitionId = randomString();
when(execution.getProcessDefinitionId()).thenReturn(processDefinitionId);
FlowElement flowElement = mock(FlowElement.class);
when(execution.getCurrentFlowElement()).thenReturn(flowElement);
// mock 方法parseAssignEmptyHandlerType
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement)))
.thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType());
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerUserIds(same(flowElement)))
.thenReturn(ListUtil.of(1L, 2L));
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
// 断言
assertEquals(SetUtils.asSet(1L, 2L), userIds);
}
}
@Test
public void testCalculateUsersByActivity() {
try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
// 准备参数
String processDefinitionId = randomString();
String activityId = randomString();
String param = randomString();
// mock 方法getFlowElementById
FlowElement flowElement = mock(FlowElement.class);
BpmnModel bpmnModel = mock(BpmnModel.class);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId)))
.thenReturn(flowElement);
// mock 方法parseAssignEmptyHandlerType
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignEmptyHandlerType(same(flowElement)))
.thenReturn(BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType());
// mock 方法getProcessDefinitionInfo
BpmProcessDefinitionInfoDO processDefinition = randomPojo(BpmProcessDefinitionInfoDO.class,
o -> o.setManagerUserIds(ListUtil.of(1L, 2L)));
when(processDefinitionService.getProcessDefinitionInfo(eq(processDefinitionId))).thenReturn(processDefinition);
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(bpmnModel, activityId, param,
null, processDefinitionId, null);
// 断言
assertEquals(SetUtils.asSet(1L, 2L), userIds);
}
}
}

View File

@@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import org.flowable.engine.delegate.DelegateExecution;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.MockedStatic;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@Disabled // TODO 芋艿:临时注释
public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateExpressionStrategy strategy;
@Test
public void testCalculateUsersByTask() {
try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) {
// 准备参数
String param = "1,2";
DelegateExecution execution = mock(DelegateExecution.class);
// mock 方法
flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(execution), eq(param)))
.thenReturn(asSet(1L, 2L));
// 调用
Set<Long> results = strategy.calculateUsersByTask(execution, param);
// 断言
assertEquals(asSet(1L, 2L), results);
}
}
@Test
public void testCalculateUsersByActivity() {
try (MockedStatic<FlowableUtils> flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) {
// 准备参数
String param = "1,2";
Map<String, Object> processVariables = new HashMap<>();
// mock 方法
flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(processVariables), eq(param)))
.thenReturn(asSet(1L, 2L));
// 调用
Set<Long> results = strategy.calculateUsersByActivity(null, null, param,
null, null, processVariables);
// 断言
assertEquals(asSet(1L, 2L), results);
}
}
}

View File

@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Arrays;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@Disabled // TODO 芋艿:临时注释
public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateGroupStrategy strategy;
@Mock
private BpmUserGroupService userGroupService;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "1,2";
// mock 方法
BpmUserGroupDO userGroup1 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(11L, 12L)));
BpmUserGroupDO userGroup2 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(21L, 22L)));
when(userGroupService.getUserGroupList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(userGroup1, userGroup2));
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
// 断言
assertEquals(asSet(11L, 12L, 21L, 22L), userIds);
}
}

View File

@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@Disabled // TODO 芋艿:临时注释
public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidatePostStrategy strategy;
@Mock
private PostApi postApi;
@Mock
private AdminUserApi adminUserApi;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "1,2";
// mock 方法
List<AdminUserRespDTO> users = convertList(asSet(11L, 22L),
id -> new AdminUserRespDTO().setId(id));
when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(success(users));
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
// 断言
assertEquals(asSet(11L, 22L), userIds);
}
}

View File

@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@Disabled // TODO 芋艿:临时注释
public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateRoleStrategy strategy;
@Mock
private RoleApi roleApi;
@Mock
private PermissionApi permissionApi;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "1,2";
// mock 方法
when(permissionApi.getUserRoleIdListByRoleIds(eq(asSet(1L, 2L))))
.thenReturn(success(asSet(11L, 22L)));
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
// 断言
assertEquals(asSet(11L, 22L), userIds);
}
}

View File

@@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import org.assertj.core.util.Sets;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateStartUserStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateStartUserStrategy strategy;
@Mock
private BpmProcessInstanceService processInstanceService;
@Test
public void testCalculateUsersByTask() {
// 准备参数
String param = "2";
// mock 方法(获得流程发起人)
Long startUserId = 1L;
ProcessInstance processInstance = mock(ProcessInstance.class);
DelegateExecution execution = mock(DelegateExecution.class);
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
// 断言
assertEquals(Sets.newLinkedHashSet(startUserId), userIds);
}
@Test
public void testCalculateUsersByActivity() {
// 准备参数
Long startUserId = 1L;
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, null,
startUserId, null, null);
// 断言
assertEquals(Sets.newLinkedHashSet(startUserId), userIds);
}
}

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Disabled // TODO 芋艿:临时注释
public class BpmTaskCandidateUserStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateUserStrategy strategy;
@Test
public void test() {
// 准备参数
String param = "1,2";
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(null, param);
// 断言
assertEquals(asSet(1L, 2L), userIds);
}
}

View File

@@ -0,0 +1,136 @@
package cn.iocoder.yudao.module.bpm.service.category;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategoryPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCategorySaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryServiceImpl;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.CATEGORY_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link BpmCategoryServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(BpmCategoryServiceImpl.class)
public class BpmCategoryServiceImplTest extends BaseDbUnitTest {
@Resource
private BpmCategoryServiceImpl categoryService;
@Resource
private BpmCategoryMapper categoryMapper;
@Test
public void testCreateCategory_success() {
// 准备参数
BpmCategorySaveReqVO createReqVO = randomPojo(BpmCategorySaveReqVO.class).setId(null)
.setStatus(randomCommonStatus());
// 调用
Long categoryId = categoryService.createCategory(createReqVO);
// 断言
assertNotNull(categoryId);
// 校验记录的属性是否正确
BpmCategoryDO category = categoryMapper.selectById(categoryId);
assertPojoEquals(createReqVO, category, "id");
}
@Test
public void testUpdateCategory_success() {
// mock 数据
BpmCategoryDO dbCategory = randomPojo(BpmCategoryDO.class);
categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据
// 准备参数
BpmCategorySaveReqVO updateReqVO = randomPojo(BpmCategorySaveReqVO.class, o -> {
o.setId(dbCategory.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
});
// 调用
categoryService.updateCategory(updateReqVO);
// 校验是否更新正确
BpmCategoryDO category = categoryMapper.selectById(updateReqVO.getId()); // 获取最新的
assertPojoEquals(updateReqVO, category);
}
@Test
public void testUpdateCategory_notExists() {
// 准备参数
BpmCategorySaveReqVO updateReqVO = randomPojo(BpmCategorySaveReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> categoryService.updateCategory(updateReqVO), CATEGORY_NOT_EXISTS);
}
@Test
public void testDeleteCategory_success() {
// mock 数据
BpmCategoryDO dbCategory = randomPojo(BpmCategoryDO.class);
categoryMapper.insert(dbCategory);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbCategory.getId();
// 调用
categoryService.deleteCategory(id);
// 校验数据不存在了
assertNull(categoryMapper.selectById(id));
}
@Test
public void testDeleteCategory_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> categoryService.deleteCategory(id), CATEGORY_NOT_EXISTS);
}
@Test
public void testGetCategoryPage() {
// mock 数据
BpmCategoryDO dbCategory = randomPojo(BpmCategoryDO.class, o -> { // 等会查询到
o.setName("芋头");
o.setCode("xiaodun");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setCreateTime(buildTime(2023, 2, 2));
});
categoryMapper.insert(dbCategory);
// 测试 name 不匹配
categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setName("小盾")));
// 测试 code 不匹配
categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCode("tudou")));
// 测试 status 不匹配
categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 createTime 不匹配
categoryMapper.insert(cloneIgnoreId(dbCategory, o -> o.setCreateTime(buildTime(2024, 2, 2))));
// 准备参数
BpmCategoryPageReqVO reqVO = new BpmCategoryPageReqVO();
reqVO.setName("");
reqVO.setCode("xiao");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
// 调用
PageResult<BpmCategoryDO> pageResult = categoryService.getCategoryPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbCategory, pageResult.getList().get(0));
}
}

View File

@@ -0,0 +1,144 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormSaveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmFormMapper;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmFormFieldRespDTO;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.FORM_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link BpmFormServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(BpmFormServiceImpl.class)
public class BpmFormServiceTest extends BaseDbUnitTest {
@Resource
private BpmFormServiceImpl formService;
@Resource
private BpmFormMapper formMapper;
@Test
public void testCreateForm_success() {
// 准备参数
BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> {
o.setConf("{}");
o.setFields(randomFields());
});
// 调用
Long formId = formService.createForm(reqVO);
// 断言
assertNotNull(formId);
// 校验记录的属性是否正确
BpmFormDO form = formMapper.selectById(formId);
assertPojoEquals(reqVO, form);
}
@Test
public void testUpdateForm_success() {
// mock 数据
BpmFormDO dbForm = randomPojo(BpmFormDO.class, o -> {
o.setConf("{}");
o.setFields(randomFields());
});
formMapper.insert(dbForm);// @Sql: 先插入出一条存在的数据
// 准备参数
BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> {
o.setId(dbForm.getId()); // 设置更新的 ID
o.setConf("{'yudao': 'yuanma'}");
o.setFields(randomFields());
});
// 调用
formService.updateForm(reqVO);
// 校验是否更新正确
BpmFormDO form = formMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, form);
}
@Test
public void testUpdateForm_notExists() {
// 准备参数
BpmFormSaveReqVO reqVO = randomPojo(BpmFormSaveReqVO.class, o -> {
o.setConf("{'yudao': 'yuanma'}");
o.setFields(randomFields());
});
// 调用, 并断言异常
assertServiceException(() -> formService.updateForm(reqVO), FORM_NOT_EXISTS);
}
@Test
public void testDeleteForm_success() {
// mock 数据
BpmFormDO dbForm = randomPojo(BpmFormDO.class);
formMapper.insert(dbForm);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbForm.getId();
// 调用
formService.deleteForm(id);
// 校验数据不存在了
assertNull(formMapper.selectById(id));
}
@Test
public void testDeleteForm_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> formService.deleteForm(id), FORM_NOT_EXISTS);
}
@Test
public void testGetFormPage() {
// mock 数据
BpmFormDO dbForm = randomPojo(BpmFormDO.class, o -> { // 等会查询到
o.setName("芋道源码");
});
formMapper.insert(dbForm);
// 测试 name 不匹配
formMapper.insert(cloneIgnoreId(dbForm, o -> o.setName("源码")));
// 准备参数
BpmFormPageReqVO reqVO = new BpmFormPageReqVO();
reqVO.setName("芋道");
// 调用
PageResult<BpmFormDO> pageResult = formService.getFormPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbForm, pageResult.getList().get(0));
}
private List<String> randomFields() {
int size = RandomUtil.randomInt(1, 3);
return Stream.iterate(0, i -> i).limit(size)
.map(i -> JsonUtils.toJsonString(randomPojo(BpmFormFieldRespDTO.class)))
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,130 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupSaveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.group.BpmUserGroupPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmUserGroupMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.USER_GROUP_NOT_EXISTS;
/**
* {@link BpmUserGroupServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import(BpmUserGroupServiceImpl.class)
public class BpmUserGroupServiceTest extends BaseDbUnitTest {
@Resource
private BpmUserGroupServiceImpl userGroupService;
@Resource
private BpmUserGroupMapper userGroupMapper;
@Test
public void testCreateUserGroup_success() {
// 准备参数
BpmUserGroupSaveReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupSaveReqVO.class);
// 调用
Long userGroupId = userGroupService.createUserGroup(reqVO);
// 断言
Assertions.assertNotNull(userGroupId);
// 校验记录的属性是否正确
BpmUserGroupDO userGroup = userGroupMapper.selectById(userGroupId);
AssertUtils.assertPojoEquals(reqVO, userGroup);
}
@Test
public void testUpdateUserGroup_success() {
// mock 数据
BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class);
userGroupMapper.insert(dbUserGroup);// @Sql: 先插入出一条存在的数据
// 准备参数
BpmUserGroupSaveReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupSaveReqVO.class, o -> {
o.setId(dbUserGroup.getId()); // 设置更新的 ID
});
// 调用
userGroupService.updateUserGroup(reqVO);
// 校验是否更新正确
BpmUserGroupDO userGroup = userGroupMapper.selectById(reqVO.getId()); // 获取最新的
AssertUtils.assertPojoEquals(reqVO, userGroup);
}
@Test
public void testUpdateUserGroup_notExists() {
// 准备参数
BpmUserGroupSaveReqVO reqVO = RandomUtils.randomPojo(BpmUserGroupSaveReqVO.class);
// 调用, 并断言异常
AssertUtils.assertServiceException(() -> userGroupService.updateUserGroup(reqVO), USER_GROUP_NOT_EXISTS);
}
@Test
public void testDeleteUserGroup_success() {
// mock 数据
BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class);
userGroupMapper.insert(dbUserGroup);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbUserGroup.getId();
// 调用
userGroupService.deleteUserGroup(id);
// 校验数据不存在了
Assertions.assertNull(userGroupMapper.selectById(id));
}
@Test
public void testDeleteUserGroup_notExists() {
// 准备参数
Long id = RandomUtils.randomLongId();
// 调用, 并断言异常
AssertUtils.assertServiceException(() -> userGroupService.deleteUserGroup(id), USER_GROUP_NOT_EXISTS);
}
@Test
public void testGetUserGroupPage() {
// mock 数据
BpmUserGroupDO dbUserGroup = RandomUtils.randomPojo(BpmUserGroupDO.class, o -> { // 等会查询到
o.setName("芋道源码");
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
o.setCreateTime(buildTime(2021, 11, 11));
});
userGroupMapper.insert(dbUserGroup);
// 测试 name 不匹配
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setName("芋道")));
// 测试 status 不匹配
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
// 测试 createTime 不匹配
userGroupMapper.insert(cloneIgnoreId(dbUserGroup, o -> o.setCreateTime(buildTime(2021, 12, 12))));
// 准备参数
BpmUserGroupPageReqVO reqVO = new BpmUserGroupPageReqVO();
reqVO.setName("源码");
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2021, 11, 10),buildTime(2021, 11, 12)}));
// 调用
PageResult<BpmUserGroupDO> pageResult = userGroupService.getUserGroupPage(reqVO);
// 断言
Assertions.assertEquals(1, pageResult.getTotal());
Assertions.assertEquals(1, pageResult.getList().size());
AssertUtils.assertPojoEquals(dbUserGroup, pageResult.getList().get(0));
}
}

View File

@@ -0,0 +1,45 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
mybatis-plus:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
type-aliases-package: ${yudao.info.base-package}.dal.dataobject
global-config:
db-config:
id-type: AUTO # H2 主键递增
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao.module.bpm

View File

@@ -0,0 +1,4 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
</configuration>

View File

@@ -0,0 +1,3 @@
DELETE FROM "bpm_form";
DELETE FROM "bpm_user_group";
DELETE FROM "bpm_category";

View File

@@ -0,0 +1,43 @@
CREATE TABLE IF NOT EXISTS "bpm_user_group" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(63) NOT NULL,
"description" varchar(255) NOT NULL,
"status" tinyint NOT NULL,
"user_ids" varchar(255) NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '用户组';
CREATE TABLE IF NOT EXISTS "bpm_category" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(63) NOT NULL,
"code" varchar(63) NOT NULL,
"description" varchar(255) NOT NULL,
"status" tinyint NOT NULL,
"sort" int NOT NULL,
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '分类';
CREATE TABLE IF NOT EXISTS "bpm_form" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar(63) NOT NULL,
"status" tinyint NOT NULL,
"fields" varchar(255) NOT NULL,
"conf" varchar(255) NOT NULL,
"remark" varchar(255),
"creator" varchar(64) DEFAULT '',
"create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar(64) DEFAULT '',
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT '动态表单';