fields;
+
+ /**
+ * 备注
+ */
+ @ApiModelProperty("备注")
+ private String remark;
+
+
+ /**
+ * 更新操作实体
+ */
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ public static class BpmFormUpdateParam extends BpmFormParam {
+
+ /**
+ * 表Id
+ */
+ @ApiModelProperty("id")
+ @NotBlank(message = ValidMessage.ID_NOT_BLANK)
+ @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR)
+ private String id;
+
+ }
+
+
+ /**
+ * 分页查询实体
+ */
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ public static class BpmFormQueryParam extends BaseParam {
+
+ /**
+ * 表单名称
+ */
+ @ApiModelProperty("表单名称")
+ private String name;
+
+ }
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/BpmModelParam.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/BpmModelParam.java
new file mode 100644
index 000000000..fbed09f9a
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/BpmModelParam.java
@@ -0,0 +1,87 @@
+package com.njcn.bpm.pojo.param;
+
+import com.njcn.common.pojo.constant.PatternRegex;
+import com.njcn.web.constant.ValidMessage;
+import com.njcn.web.pojo.param.BaseParam;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.hibernate.validator.constraints.URL;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.io.Serializable;
+
+
+@Data
+public class BpmModelParam implements Serializable {
+
+
+ @ApiModelProperty("流程标识")
+ @NotNull(message = "流程标识不能为空")
+ private String key;
+
+ @ApiModelProperty("流程名称")
+ @NotNull(message = "流程名称不能为空")
+ private String name;
+
+ @ApiModelProperty( "流程图标")
+ private String icon;
+
+ @ApiModelProperty("流程分类")
+ private String category;
+
+ @ApiModelProperty("BPMN XML")
+ private String bpmnXml;
+
+ @ApiModelProperty("流程表单")
+ private String formId;
+
+ @ApiModelProperty("流程描述")
+ private String description;
+
+ private Integer formType = 10;
+
+
+ private String formCustomCreatePath;
+
+ private String formCustomViewPath;
+
+ /**
+ * 更新操作实体
+ */
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ public static class BpmModelUpdateParam extends BpmModelParam {
+
+ /**
+ * 表Id
+ */
+ @ApiModelProperty("id")
+ @NotBlank(message = ValidMessage.ID_NOT_BLANK)
+ private String id;
+
+ }
+
+
+ /**
+ * 分页查询实体
+ */
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ public static class BpmModelQueryParam extends BaseParam {
+
+ @ApiModelProperty("流程标识")
+ private String key;
+
+ @ApiModelProperty("流程名称")
+ private String name;
+
+ @ApiModelProperty("流程分类")
+ private String category;
+
+ }
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/BpmProcessDefinitionInfoParam.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/BpmProcessDefinitionInfoParam.java
new file mode 100644
index 000000000..52e9185d1
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/BpmProcessDefinitionInfoParam.java
@@ -0,0 +1,112 @@
+package com.njcn.bpm.pojo.param;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.njcn.common.pojo.constant.PatternRegex;
+import com.njcn.db.bo.BaseEntity;
+import com.njcn.web.constant.ValidMessage;
+import com.njcn.web.pojo.param.BaseParam;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * BPM 流程定义的拓信息
+ * 主要解决 Flowable {@link org.flowable.engine.repository.ProcessDefinition} 不支持拓展字段,所以新建该表
+ */
+@Data
+public class BpmProcessDefinitionInfoParam extends BaseEntity implements Serializable {
+
+
+ /**
+ * 流程定义的编号
+ *
+ * 关联 ProcessDefinition 的 id 属性
+ */
+ private String processDefinitionId;
+ /**
+ * 流程模型的编号
+ *
+ * 关联 Model 的 id 属性
+ */
+ private String modelId;
+
+ /**
+ * 图标
+ */
+ private String icon;
+ /**
+ * 描述
+ */
+ private String description;
+
+ /**
+ * 表单类型
+ */
+ private Integer formType;
+
+ /**
+ * 动态表单编号
+ */
+ private String formId;
+
+ /**
+ * 表单的配置
+ */
+ private String formConf;
+
+ /**
+ * 表单项的数组
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List formFields;
+
+
+ /**
+ * 自定义表单的提交路径,使用 Vue 的路由地址
+ */
+ private String formCustomCreatePath;
+
+ /**
+ * 自定义表单的查看路径,使用 Vue 的路由地址
+ */
+ private String formCustomViewPath;
+
+ /**
+ * 更新操作实体
+ */
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ public static class BpmProcessDefinitionInfoUpdateParam extends BpmProcessDefinitionInfoParam {
+
+ /**
+ * 表Id
+ */
+ @ApiModelProperty("id")
+ @NotBlank(message = ValidMessage.ID_NOT_BLANK)
+ @Pattern(regexp = PatternRegex.SYSTEM_ID, message = ValidMessage.ID_FORMAT_ERROR)
+ private String id;
+
+ }
+
+
+ /**
+ * 分页查询实体
+ */
+ @Data
+ @EqualsAndHashCode(callSuper = true)
+ public static class BpmProcessDefinitionInfoQueryParam extends BaseParam {
+
+ @ApiModelProperty("标识-精准匹配")
+ private String key;
+
+ }
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/PageParam.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/PageParam.java
new file mode 100644
index 000000000..2991e568c
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/param/PageParam.java
@@ -0,0 +1,34 @@
+package com.njcn.bpm.pojo.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@Schema(description="分页参数")
+@Data
+public class PageParam implements Serializable {
+
+ private static final Integer PAGE_NO = 1;
+ private static final Integer PAGE_SIZE = 10;
+
+ /**
+ * 每页条数 - 不分页
+ *
+ * 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
+ */
+ public static final Integer PAGE_SIZE_NONE = -1;
+
+ @NotNull(message = "页码不能为空")
+ @Min(value = 1, message = "页码最小值为 1")
+ private Integer pageNo = PAGE_NO;
+
+ @NotNull(message = "每页条数不能为空")
+ @Min(value = 1, message = "每页条数最小值为 1")
+ @Max(value = 100, message = "每页条数最大值为 100")
+ private Integer pageSize = PAGE_SIZE;
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmCategory.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmCategory.java
new file mode 100644
index 000000000..5d22181fa
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmCategory.java
@@ -0,0 +1,57 @@
+package com.njcn.bpm.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.njcn.db.bo.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * BPM 流程分类 DO
+ *
+ * @author 芋道源码
+ */
+@TableName("bpm_category")
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmCategory extends BaseEntity implements Serializable {
+
+ /**
+ * 分类编号
+ */
+ @TableId
+ private String id;
+ /**
+ * 分类名
+ */
+ private String name;
+ /**
+ * 分类标志
+ */
+ private String code;
+ /**
+ * 分类描述
+ */
+ private String description;
+ /**
+ * 分类状态
+ *
+ */
+ private Integer status;
+ /**
+ * 分类排序
+ */
+ private Integer sort;
+
+ /**
+ * 状态:0-删除 1-正常
+ */
+ private Integer state;
+}
\ No newline at end of file
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmForm.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmForm.java
new file mode 100644
index 000000000..5f7bd58a2
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmForm.java
@@ -0,0 +1,64 @@
+package com.njcn.bpm.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.njcn.db.bo.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * BPM 工作流的表单定义
+ * 用于工作流的申请表单,需要动态配置的场景
+ *
+ * @author 芋道源码
+ */
+@TableName(value = "bpm_form", autoResultMap = true)
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmForm extends BaseEntity implements Serializable {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private String id;
+ /**
+ * 表单名
+ */
+ private String name;
+ /**
+ * 状态
+ */
+ private Integer status;
+ /**
+ * 表单的配置
+ */
+ private String conf;
+ /**
+ * 表单项的数组
+ *
+ * 目前直接将 https://github.com/JakHuang/form-generator 生成的 JSON 串,直接保存
+ * 定义:https://github.com/JakHuang/form-generator/issues/46
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List fields;
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 状态:0-删除 1-正常
+ */
+ private Integer state;
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmProcessDefinitionInfo.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmProcessDefinitionInfo.java
new file mode 100644
index 000000000..876f74e22
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/BpmProcessDefinitionInfo.java
@@ -0,0 +1,95 @@
+package com.njcn.bpm.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.njcn.db.bo.BaseEntity;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * BPM 流程定义的拓信息
+ * 主要解决 Flowable {@link org.flowable.engine.repository.ProcessDefinition} 不支持拓展字段,所以新建该表
+ *
+ */
+@TableName(value = "bpm_process_definition_info", autoResultMap = true)
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmProcessDefinitionInfo extends BaseEntity implements Serializable {
+
+ /**
+ * 编号
+ */
+ @TableId
+ private String id;
+ /**
+ * 流程定义的编号
+ *
+ * 关联 ProcessDefinition 的 id 属性
+ */
+ private String processDefinitionId;
+ /**
+ * 流程模型的编号
+ *
+ * 关联 Model 的 id 属性
+ */
+ private String modelId;
+
+ /**
+ * 图标
+ */
+ private String icon;
+ /**
+ * 描述
+ */
+ private String description;
+
+ /**
+ * 表单类型
+ *
+ */
+ private Integer formType;
+
+ /**
+ * 动态表单编号
+ *
+ */
+ private String formId;
+
+ /**
+ * 表单的配置
+ *
+ */
+ private String formConf;
+
+ /**
+ * 表单项的数组
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List formFields;
+
+
+ /**
+ * 自定义表单的提交路径,使用 Vue 的路由地址
+ */
+ private String formCustomCreatePath;
+
+ /**
+ * 自定义表单的查看路径,使用 Vue 的路由地址
+ */
+ private String formCustomViewPath;
+
+ /**
+ * 状态:0-删除 1-正常
+ */
+ private Integer state;
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/WFForm.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/WFForm.java
new file mode 100644
index 000000000..a68bcae91
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/po/WFForm.java
@@ -0,0 +1,48 @@
+package com.njcn.bpm.pojo.po;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.njcn.db.bo.BaseEntity;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 流程表单
+ *
+ *
+ * @author hongawen
+ * @since 2024-04-25
+ */
+@Data
+@TableName("wf_form")
+public class WFForm extends BaseEntity implements Serializable{
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 流程表单id
+ */
+ private String id;
+
+ /**
+ * 表单名称
+ */
+ private String name;
+
+ /**
+ * 表单内容
+ */
+ private String content;
+
+ /**
+ * 表单描述
+ */
+ private String remark;
+
+ /**
+ * 状态:0-删除 1-正常
+ */
+ private Integer state;
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmCategoryVO.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmCategoryVO.java
new file mode 100644
index 000000000..1da698994
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmCategoryVO.java
@@ -0,0 +1,41 @@
+package com.njcn.bpm.pojo.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * BPM 流程分类 DO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class BpmCategoryVO implements Serializable {
+
+ /**
+ * 分类编号
+ */
+ private String id;
+ /**
+ * 分类名
+ */
+ private String name;
+ /**
+ * 分类标志
+ */
+ private String code;
+ /**
+ * 分类描述
+ */
+ private String description;
+ /**
+ * 分类状态
+ */
+ private Integer status;
+ /**
+ * 分类排序
+ */
+ private Integer sort;
+
+
+}
\ No newline at end of file
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmFormVO.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmFormVO.java
new file mode 100644
index 000000000..e3e5c4eea
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmFormVO.java
@@ -0,0 +1,26 @@
+package com.njcn.bpm.pojo.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class BpmFormVO implements Serializable {
+
+ private String id;
+
+ private String name;
+
+ private String conf;
+
+ private List fields;
+
+ private Integer status; // 参见 CommonStatusEnum 枚举
+
+ private String remark;
+
+ private LocalDateTime createTime;
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmModelRespVO.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmModelRespVO.java
new file mode 100644
index 000000000..4a8c16194
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmModelRespVO.java
@@ -0,0 +1,51 @@
+package com.njcn.bpm.pojo.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 流程模型 Response VO")
+@Data
+public class BpmModelRespVO {
+
+ private String id;
+
+ private String key;
+
+ private String name;
+
+ @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg")
+ private String icon;
+
+ @Schema(description = "流程描述", example = "我是描述")
+ private String description;
+
+ @Schema(description = "流程分类编码", example = "1")
+ private String category;
+ @Schema(description = "流程分类名字", example = "请假")
+ private String categoryName;
+
+ @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
+ private Integer formType;
+
+ @Schema(description = "表单编号", example = "1024")
+ private String formId; // 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
+ @Schema(description = "表单名字", example = "请假表单")
+ private String formName;
+
+ @Schema(description = "自定义表单的提交路径", example = "/bpm/oa/leave/create")
+ private String formCustomCreatePath; // 使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
+ @Schema(description = "自定义表单的查看路径", example = "/bpm/oa/leave/view")
+ private String formCustomViewPath; // ,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空
+
+ private LocalDateTime createTime;
+
+ private String bpmnXml;
+
+ /**
+ * 最新部署的流程定义
+ */
+ private BpmProcessDefinitionInfoVO processDefinition;
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmProcessDefinitionInfoVO.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmProcessDefinitionInfoVO.java
new file mode 100644
index 000000000..b20e60fbb
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/pojo/vo/BpmProcessDefinitionInfoVO.java
@@ -0,0 +1,123 @@
+package com.njcn.bpm.pojo.vo;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
+import com.njcn.db.bo.BaseEntity;
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * BPM 流程定义的拓信息
+ * 主要解决 Flowable {@link org.flowable.engine.repository.ProcessDefinition} 不支持拓展字段,所以新建该表
+ *
+ */
+@Data
+public class BpmProcessDefinitionInfoVO extends BaseEntity implements Serializable {
+
+ /**
+ * 编号
+ */
+ private String id;
+ /**
+ * 流程定义的编号
+ *
+ * 关联 ProcessDefinition 的 id 属性
+ */
+ private String processDefinitionId;
+ /**
+ * 流程模型的编号
+ *
+ * 关联 Model 的 id 属性
+ */
+ private String modelId;
+
+ /**
+ * 图标
+ */
+ private String icon;
+ /**
+ * 描述
+ */
+ private String description;
+
+ /**
+ * 表单类型
+ *
+ */
+ private Integer formType;
+
+ /**
+ * 动态表单编号
+ *
+ */
+ private String formId;
+
+ /**
+ * 表单的配置
+ *
+ */
+ private String formConf;
+
+ /**
+ * 表单项的数组
+ */
+ @TableField(typeHandler = JacksonTypeHandler.class)
+ private List formFields;
+
+
+ /**
+ * 自定义表单的提交路径,使用 Vue 的路由地址
+ */
+ private String formCustomCreatePath;
+
+ /**
+ * 自定义表单的查看路径,使用 Vue 的路由地址
+ */
+ private String formCustomViewPath;
+
+
+ private Integer version;
+
+ private String name;
+
+ private String key;
+
+
+ @ApiModelProperty("流程分类")
+ private String category;
+
+ @ApiModelProperty("流程分类名字")
+ private String categoryName;
+
+
+ @ApiModelProperty("表单名字")
+ private String formName;
+
+
+ private Integer suspensionState; // 参见 SuspensionState 枚举
+
+ @ApiModelProperty("部署时间")
+ private LocalDateTime deploymentTime; // 需要从对应的 Deployment 读取,非必须返回
+
+ @ApiModelProperty("BPMN")
+ private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回
+
+ @ApiModelProperty("发起用户需要选择审批人的任务数组")
+ private List startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回
+
+ @Schema(description = "BPMN UserTask 用户任务")
+ @Data
+ public static class UserTask {
+
+ private String id;
+
+ private String name;
+
+ }
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/BeanUtils.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/BeanUtils.java
new file mode 100644
index 000000000..942f3d39a
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/BeanUtils.java
@@ -0,0 +1,61 @@
+package com.njcn.bpm.utils;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Bean 工具类
+ *
+ * 1. 默认使用 {@link BeanUtil} 作为实现类,虽然不同 bean 工具的性能有差别,但是对绝大多数同学的项目,不用在意这点性能
+ * 2. 针对复杂的对象转换,可以搜参考 AuthConvert 实现,通过 mapstruct + default 配合实现
+ *
+ * @author 芋道源码
+ */
+public class BeanUtils {
+
+ public static T toBean(Object source, Class targetClass) {
+ return BeanUtil.toBean(source, targetClass);
+ }
+
+ public static T toBean(Object source, Class targetClass, Consumer peek) {
+ T target = toBean(source, targetClass);
+ if (target != null) {
+ peek.accept(target);
+ }
+ return target;
+ }
+
+ public static List toBean(List source, Class targetType) {
+ if (source == null) {
+ return null;
+ }
+ return CollectionUtils.convertList(source, s -> toBean(s, targetType));
+ }
+
+ public static List toBean(List source, Class targetType, Consumer peek) {
+ List list = toBean(source, targetType);
+ if (list != null) {
+ list.forEach(peek);
+ }
+ return list;
+ }
+//
+// public static Page toBean(Page source, Class targetType) {
+// return toBean(source, targetType, null);
+// }
+//
+// public static Page toBean(Page source, Class targetType, Consumer peek) {
+// if (source == null) {
+// return null;
+// }
+// List list = toBean(source.getRecords(), targetType);
+// if (peek != null) {
+// list.forEach(peek);
+// }
+// return new Page<>(list, source.getTotal());
+// }
+
+}
\ No newline at end of file
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/CollectionUtils.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/CollectionUtils.java
new file mode 100644
index 000000000..5ce0dcc67
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/CollectionUtils.java
@@ -0,0 +1,318 @@
+package com.njcn.bpm.utils;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ArrayUtil;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.Arrays.asList;
+
+public class CollectionUtils {
+
+ public static boolean containsAny(Object source, Object... targets) {
+ return asList(targets).contains(source);
+ }
+
+ public static boolean isAnyEmpty(Collection>... collections) {
+ return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
+ }
+
+ public static boolean anyMatch(Collection from, Predicate predicate) {
+ return from.stream().anyMatch(predicate);
+ }
+
+ public static List filterList(Collection from, Predicate predicate) {
+ if (CollUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return from.stream().filter(predicate).collect(Collectors.toList());
+ }
+
+ public static List distinct(Collection from, Function keyMapper) {
+ if (CollUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return distinct(from, keyMapper, (t1, t2) -> t1);
+ }
+
+ public static List distinct(Collection from, Function keyMapper, BinaryOperator cover) {
+ if (CollUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
+ }
+
+ public static List convertList(T[] from, Function func) {
+ if (ArrayUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return convertList(Arrays.asList(from), func);
+ }
+
+ public static List convertList(Collection from, Function func) {
+ if (CollUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+ public static List convertList(Collection from, Function func, Predicate filter) {
+ if (CollUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+ public static List convertListByFlatMap(Collection from,
+ Function> func) {
+ if (CollUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+ public static List convertListByFlatMap(Collection from,
+ Function super T, ? extends U> mapper,
+ Function> func) {
+ if (CollUtil.isEmpty(from)) {
+ return new ArrayList<>();
+ }
+ return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+
+ public static List mergeValuesFromMap(Map> map) {
+ return map.values()
+ .stream()
+ .flatMap(List::stream)
+ .collect(Collectors.toList());
+ }
+
+ public static Set convertSet(Collection from) {
+ return convertSet(from, v -> v);
+ }
+
+ public static Set convertSet(Collection from, Function func) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashSet<>();
+ }
+ return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
+ }
+
+ public static Set convertSet(Collection from, Function func, Predicate filter) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashSet<>();
+ }
+ return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
+ }
+
+ public static Map convertMapByFilter(Collection from, Predicate filter, Function keyFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
+ }
+
+ public static Set convertSetByFlatMap(Collection from,
+ Function> func) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashSet<>();
+ }
+ return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
+ }
+
+ public static Set convertSetByFlatMap(Collection from,
+ Function super T, ? extends U> mapper,
+ Function> func) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashSet<>();
+ }
+ return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
+ }
+
+ public static Map convertMap(Collection from, Function keyFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return convertMap(from, keyFunc, Function.identity());
+ }
+
+ public static Map convertMap(Collection from, Function keyFunc, Supplier extends Map> supplier) {
+ if (CollUtil.isEmpty(from)) {
+ return supplier.get();
+ }
+ return convertMap(from, keyFunc, Function.identity(), supplier);
+ }
+
+ public static Map convertMap(Collection from, Function keyFunc, Function valueFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
+ }
+
+ public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, BinaryOperator mergeFunction) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
+ }
+
+ public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, Supplier extends Map> supplier) {
+ if (CollUtil.isEmpty(from)) {
+ return supplier.get();
+ }
+ return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
+ }
+
+ public static Map convertMap(Collection from, Function keyFunc, Function valueFunc, BinaryOperator mergeFunction, Supplier extends Map> supplier) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
+ }
+
+ public static Map> convertMultiMap(Collection from, Function keyFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
+ }
+
+ public static Map> convertMultiMap(Collection from, Function keyFunc, Function valueFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return from.stream()
+ .collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
+ }
+
+ // 暂时没想好名字,先以 2 结尾噶
+ public static Map> convertMultiMap2(Collection from, Function keyFunc, Function valueFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return new HashMap<>();
+ }
+ return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
+ }
+
+ public static Map convertImmutableMap(Collection from, Function keyFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return Collections.emptyMap();
+ }
+ ImmutableMap.Builder builder = ImmutableMap.builder();
+ from.forEach(item -> builder.put(keyFunc.apply(item), item));
+ return builder.build();
+ }
+
+ /**
+ * 对比老、新两个列表,找出新增、修改、删除的数据
+ *
+ * @param oldList 老列表
+ * @param newList 新列表
+ * @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
+ * 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
+ * @return [新增列表、修改列表、删除列表]
+ */
+ public static List> diffList(Collection oldList, Collection newList,
+ BiFunction sameFunc) {
+ List createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
+ List updateList = new ArrayList<>();
+ List deleteList = new ArrayList<>();
+
+ // 通过以 oldList 为主遍历,找出 updateList 和 deleteList
+ for (T oldObj : oldList) {
+ // 1. 寻找是否有匹配的
+ T foundObj = null;
+ for (Iterator iterator = createList.iterator(); iterator.hasNext(); ) {
+ T newObj = iterator.next();
+ // 1.1 不匹配,则直接跳过
+ if (!sameFunc.apply(oldObj, newObj)) {
+ continue;
+ }
+ // 1.2 匹配,则移除,并结束寻找
+ iterator.remove();
+ foundObj = newObj;
+ break;
+ }
+ // 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
+ if (foundObj != null) {
+ updateList.add(foundObj);
+ } else {
+ deleteList.add(oldObj);
+ }
+ }
+ return asList(createList, updateList, deleteList);
+ }
+
+ public static boolean containsAny(Collection> source, Collection> candidates) {
+ return org.springframework.util.CollectionUtils.containsAny(source, candidates);
+ }
+
+ public static T getFirst(List from) {
+ return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
+ }
+
+ public static T findFirst(Collection from, Predicate predicate) {
+ return findFirst(from, predicate, Function.identity());
+ }
+
+ public static U findFirst(Collection from, Predicate predicate, Function func) {
+ if (CollUtil.isEmpty(from)) {
+ return null;
+ }
+ return from.stream().filter(predicate).findFirst().map(func).orElse(null);
+ }
+
+ public static > V getMaxValue(Collection from, Function valueFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return null;
+ }
+ assert !from.isEmpty(); // 断言,避免告警
+ T t = from.stream().max(Comparator.comparing(valueFunc)).get();
+ return valueFunc.apply(t);
+ }
+
+ public static > V getMinValue(List from, Function valueFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return null;
+ }
+ assert from.size() > 0; // 断言,避免告警
+ T t = from.stream().min(Comparator.comparing(valueFunc)).get();
+ return valueFunc.apply(t);
+ }
+
+ public static > V getSumValue(List from, Function valueFunc,
+ BinaryOperator accumulator) {
+ return getSumValue(from, valueFunc, accumulator, null);
+ }
+
+ public static > V getSumValue(Collection from, Function valueFunc,
+ BinaryOperator accumulator, V defaultValue) {
+ if (CollUtil.isEmpty(from)) {
+ return defaultValue;
+ }
+ assert !from.isEmpty(); // 断言,避免告警
+ return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
+ }
+
+ public static void addIfNotNull(Collection coll, T item) {
+ if (item == null) {
+ return;
+ }
+ coll.add(item);
+ }
+
+ public static Collection singleton(T obj) {
+ return obj == null ? Collections.emptyList() : Collections.singleton(obj);
+ }
+
+ public static List newArrayList(List> list) {
+ return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
+ }
+
+}
\ No newline at end of file
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/DateUtils.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/DateUtils.java
new file mode 100644
index 000000000..6000f7f2c
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/DateUtils.java
@@ -0,0 +1,149 @@
+package com.njcn.bpm.utils;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+
+import java.time.*;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * 时间工具类
+ *
+ * @author 芋道源码
+ */
+public class DateUtils {
+
+ /**
+ * 时区 - 默认
+ */
+ public static final String TIME_ZONE_DEFAULT = "GMT+8";
+
+ /**
+ * 秒转换成毫秒
+ */
+ public static final long SECOND_MILLIS = 1000;
+
+ public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
+
+ public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
+
+ /**
+ * 将 LocalDateTime 转换成 Date
+ *
+ * @param date LocalDateTime
+ * @return LocalDateTime
+ */
+ public static Date of(LocalDateTime date) {
+ if (date == null) {
+ return null;
+ }
+ // 将此日期时间与时区相结合以创建 ZonedDateTime
+ ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
+ // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
+ Instant instant = zonedDateTime.toInstant();
+ // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
+ return Date.from(instant);
+ }
+
+ /**
+ * 将 Date 转换成 LocalDateTime
+ *
+ * @param date Date
+ * @return LocalDateTime
+ */
+ public static LocalDateTime of(Date date) {
+ if (date == null) {
+ return null;
+ }
+ // 转为时间戳
+ Instant instant = date.toInstant();
+ // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
+ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+ }
+
+ public static Date addTime(Duration duration) {
+ return new Date(System.currentTimeMillis() + duration.toMillis());
+ }
+
+ public static boolean isExpired(LocalDateTime time) {
+ LocalDateTime now = LocalDateTime.now();
+ return now.isAfter(time);
+ }
+
+ /**
+ * 创建指定时间
+ *
+ * @param year 年
+ * @param mouth 月
+ * @param day 日
+ * @return 指定时间
+ */
+ public static Date buildTime(int year, int mouth, int day) {
+ return buildTime(year, mouth, day, 0, 0, 0);
+ }
+
+ /**
+ * 创建指定时间
+ *
+ * @param year 年
+ * @param mouth 月
+ * @param day 日
+ * @param hour 小时
+ * @param minute 分钟
+ * @param second 秒
+ * @return 指定时间
+ */
+ public static Date buildTime(int year, int mouth, int day,
+ int hour, int minute, int second) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.YEAR, year);
+ calendar.set(Calendar.MONTH, mouth - 1);
+ calendar.set(Calendar.DAY_OF_MONTH, day);
+ calendar.set(Calendar.HOUR_OF_DAY, hour);
+ calendar.set(Calendar.MINUTE, minute);
+ calendar.set(Calendar.SECOND, second);
+ calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
+ return calendar.getTime();
+ }
+
+ public static Date max(Date a, Date b) {
+ if (a == null) {
+ return b;
+ }
+ if (b == null) {
+ return a;
+ }
+ return a.compareTo(b) > 0 ? a : b;
+ }
+
+ public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
+ if (a == null) {
+ return b;
+ }
+ if (b == null) {
+ return a;
+ }
+ return a.isAfter(b) ? a : b;
+ }
+
+ /**
+ * 是否今天
+ *
+ * @param date 日期
+ * @return 是否
+ */
+ public static boolean isToday(LocalDateTime date) {
+ return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
+ }
+
+ /**
+ * 是否昨天
+ *
+ * @param date 日期
+ * @return 是否
+ */
+ public static boolean isYesterday(LocalDateTime date) {
+ return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
+ }
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/NumberUtils.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/NumberUtils.java
new file mode 100644
index 000000000..f5a927f34
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/NumberUtils.java
@@ -0,0 +1,64 @@
+package com.njcn.bpm.utils;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.math.BigDecimal;
+
+/**
+ * 数字的工具类,补全 {@link NumberUtil} 的功能
+ *
+ * @author 芋道源码
+ */
+public class NumberUtils {
+
+ public static Long parseLong(String str) {
+ return StrUtil.isNotEmpty(str) ? Long.valueOf(str) : null;
+ }
+
+ public static Integer parseInt(String str) {
+ return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null;
+ }
+
+ /**
+ * 通过经纬度获取地球上两点之间的距离
+ *
+ * 参考 <DistanceUtil> 实现,目前它已经被 hutool 删除
+ *
+ * @param lat1 经度1
+ * @param lng1 纬度1
+ * @param lat2 经度2
+ * @param lng2 纬度2
+ * @return 距离,单位:千米
+ */
+ public static double getDistance(double lat1, double lng1, double lat2, double lng2) {
+ double radLat1 = lat1 * Math.PI / 180.0;
+ double radLat2 = lat2 * Math.PI / 180.0;
+ double a = radLat1 - radLat2;
+ double b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
+ double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)
+ + Math.cos(radLat1) * Math.cos(radLat2)
+ * Math.pow(Math.sin(b / 2), 2)));
+ distance = distance * 6378.137;
+ distance = Math.round(distance * 10000d) / 10000d;
+ return distance;
+ }
+
+ /**
+ * 提供精确的乘法运算
+ *
+ * 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是,如果存在 null,则返回 null
+ *
+ * @param values 多个被乘值
+ * @return 积
+ */
+ public static BigDecimal mul(BigDecimal... values) {
+ for (BigDecimal value : values) {
+ if (value == null) {
+ return null;
+ }
+ }
+ return NumberUtil.mul(values);
+ }
+
+}
diff --git a/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/ProcessEnumUtil.java b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/ProcessEnumUtil.java
new file mode 100644
index 000000000..cd95b3334
--- /dev/null
+++ b/pqs-bpm/bpm-api/src/main/java/com/njcn/bpm/utils/ProcessEnumUtil.java
@@ -0,0 +1,46 @@
+package com.njcn.bpm.utils;
+
+import cn.hutool.core.util.StrUtil;
+import com.njcn.bpm.enums.BpmResponseEnum;
+import com.njcn.common.pojo.enums.response.CommonResponseEnum;
+import com.njcn.common.pojo.exception.BusinessException;
+import com.njcn.common.pojo.response.HttpResult;
+import com.njcn.common.utils.EnumUtils;
+
+import javax.validation.constraints.NotNull;
+import java.util.Objects;
+
+/**
+ * @author hongawen
+ * @version 1.0.0
+ * @date 2021年12月20日 10:03
+ */
+public class ProcessEnumUtil {
+
+ /**
+ * 获取HarmonicResponseEnum实例
+ */
+ public static BpmResponseEnum getHarmonicEnumResponseEnumByMessage(@NotNull Object value) {
+ BpmResponseEnum harmonicResponseEnum;
+ try {
+ String message = value.toString();
+ if(message.indexOf(StrUtil.C_COMMA)>0){
+ value = message.substring(message.indexOf(StrUtil.C_COMMA)+1);
+ }
+ harmonicResponseEnum = EnumUtils.valueOf(BpmResponseEnum.class, value, BpmResponseEnum.class.getMethod(BusinessException.GET_MESSAGE_METHOD));
+ return Objects.isNull(harmonicResponseEnum) ? BpmResponseEnum.BPM_COMMON_ERROR : harmonicResponseEnum;
+ } catch (NoSuchMethodException e) {
+ throw new BusinessException(CommonResponseEnum.INTERNAL_ERROR);
+ }
+ }
+ public static Enum> getExceptionEnum(HttpResult