工作流模块提交

This commit is contained in:
2024-05-09 14:23:18 +08:00
parent bedd70136b
commit 4936d22884
98 changed files with 6780 additions and 108 deletions

View File

@@ -0,0 +1,40 @@
package com.njcn.bpm.enums;
import org.flowable.engine.runtime.ProcessInstance;
/**
* BPM 通用常量
*
* @author 芋道源码
*/
public class BpmConstants {
/**
* 流程实例的变量 - 状态
*
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS";
/**
* 流程实例的变量 - 发起用户选择的审批人 Map
*
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
/**
* 任务的变量 - 状态
*
* @see org.flowable.task.api.Task#getTaskLocalVariables()
*/
public static final String TASK_VARIABLE_STATUS = "TASK_STATUS";
/**
* 任务的变量 - 理由
*
* 例如说:审批通过、不通过的理由
*
* @see org.flowable.task.api.Task#getTaskLocalVariables()
*/
public static final String TASK_VARIABLE_REASON = "TASK_REASON";
}

View File

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

View File

@@ -0,0 +1,50 @@
package com.njcn.bpm.enums;
import lombok.Getter;
/**
* 异常处理类
* @author qijian
* @version 1.0.0
* @date 2022年11月11日 09:56
*/
@Getter
public enum BpmResponseEnum {
/**
* 过程监督异常响应码的范围:
* A00550 ~ A00649
*/
BPM_COMMON_ERROR("A00568","工作流模块异常"),
BPM_XML_ERROR("A00568","流程标识格式不正确,需要以字母或下划线开头,后接任意字母、数字、中划线、下划线、句点!"),
BPM_MODEL_REPEAT("A00568","流程标识已存在"),
BPM_MODEL_NOT_EXIST("A00568","流程模型不存在"),
PROCESS_DEFINITION_NOT_EXISTS("A00568","流程定义不存在"),
FORM_NOT_EXISTS("A00568","动态表单不存在"),
MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG("A00568","部署流程失败,原因:流程表单未配置,请点击【修改流程】按钮进行配置"),
BPM_START_EVENT_NOT_EXIST("A00568","起始事件不存在"),
MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS("A00568","部署流程失败原因BPMN 流程图中,用户任务的名字不存在"),
REPEAT_NAME_FORM("A00568","流程表单名称重复"),
REPEAT_CATEGORY_NAME_FORM("A00568","流程类型名称重复"),
;
private final String code;
private final String message;
BpmResponseEnum(String code, String message) {
this.code = code;
this.message = message;
}
}

View File

@@ -0,0 +1,41 @@
package com.njcn.bpm.enums;
import cn.hutool.core.util.ArrayUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* BPM 任务的候选人策略枚举
*
* 例如说:分配给指定人审批
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum BpmTaskCandidateStrategyEnum {
ROLE(10, "角色"),
DEPT_MEMBER(20, "部门的成员"), // 包括负责人
DEPT_LEADER(21, "部门的负责人"),
POST(22, "岗位"),
USER(30, "用户"),
START_USER_SELECT(35, "发起人自选"), // 申请人自己,可在提交申请时选择此节点的审批人
USER_GROUP(40, "用户组"),
EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
;
/**
* 类型
*/
private final Integer strategy;
/**
* 描述
*/
private final String description;
public static BpmTaskCandidateStrategyEnum valueOf(Integer strategy) {
return ArrayUtil.firstMatch(o -> o.getStrategy().equals(strategy), values());
}
}

View File

@@ -0,0 +1,26 @@
package com.njcn.bpm.enums;
/**
* BPMN XML 常量信息
*
* @author 芋道源码
*/
public interface BpmnModelConstants {
String BPMN_FILE_SUFFIX = ".bpmn";
/**
* BPMN 中的命名空间
*/
String NAMESPACE = "http://flowable.org/bpmn";
/**
* BPMN UserTask 的扩展属性,用于标记候选人策略
*/
String USER_TASK_CANDIDATE_STRATEGY = "candidateStrategy";
/**
* BPMN UserTask 的扩展属性,用于标记候选人参数
*/
String USER_TASK_CANDIDATE_PARAM = "candidateParam";
}

View File

@@ -0,0 +1,29 @@
package com.njcn.bpm.enums;
import lombok.Data;
/**
* 错误码对象
*
* 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
*
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
*/
@Data
public class ErrorCode {
/**
* 错误码
*/
private final Integer code;
/**
* 错误提示
*/
private final String msg;
public ErrorCode(Integer code, String message) {
this.code = code;
this.msg = message;
}
}

View File

@@ -0,0 +1,40 @@
package com.njcn.bpm.enums;
/**
* 全局错误码枚举
* 0-999 系统异常编码保留
*
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
* 虽然说HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
*
* @author 芋道源码
*/
public interface GlobalErrorCodeConstants {
ErrorCode SUCCESS = new ErrorCode(0, "成功");
// ========== 客户端错误段 ==========
ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
// ========== 服务端错误段 ==========
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项");
// ========== 自定义错误段 ==========
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
}

View File

@@ -0,0 +1,15 @@
package com.njcn.bpm.enums;
/**
* 可生成 Int 数组的接口
*
* @author 芋道源码
*/
public interface IntArrayValuable {
/**
* @return int 数组
*/
int[] array();
}

View File

@@ -0,0 +1,41 @@
package com.njcn.bpm.pojo.dto;
import lombok.Data;
/**
* BPM 流程 MetaInfo Response DTO
* 主要用于 { Model#setMetaInfo(String)} 的存储
*
*
* @author 芋道源码
*/
@Data
public class BpmModelMetaInfoRespDTO {
/**
* 流程图标
*/
private String icon;
/**
* 流程描述
*/
private String description;
/**
* 表单类型
*/
private Integer formType;
/**
* 表单编号
*/
private String formId;
/**
* 自定义表单的提交路径,使用 Vue 的路由地址
*/
private String formCustomCreatePath;
/**
* 自定义表单的查看路径,使用 Vue 的路由地址
*/
private String formCustomViewPath;
}

View File

@@ -0,0 +1,116 @@
package com.njcn.bpm.pojo.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.njcn.bpm.enums.ErrorCode;
import com.njcn.bpm.enums.GlobalErrorCodeConstants;
import com.njcn.common.pojo.exception.BusinessException;
import lombok.Data;
import org.springframework.util.Assert;
import java.io.Serializable;
import java.util.Objects;
/**
* 通用返回
*
* @param <T> 数据泛型
*/
@Data
public class CommonResult<T> implements Serializable {
/**
* 错误码
*
* @see ErrorCode#getCode()
*/
private Integer code;
/**
* 返回数据
*/
private T data;
/**
* 错误提示,用户可阅读
*
* @see ErrorCode#getMsg() ()
*/
private String msg;
/**
* 将传入的 result 对象,转换成另外一个泛型结果的对象
*
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
*
* @param result 传入的 result 对象
* @param <T> 返回的泛型
* @return 新的 CommonResult 对象
*/
public static <T> CommonResult<T> error(CommonResult<?> result) {
return error(result.getCode(), result.getMsg());
}
public static <T> CommonResult<T> error(Integer code, String message) {
Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = code;
result.msg = message;
return result;
}
public static <T> CommonResult<T> error( String message) {
CommonResult<T> result = new CommonResult<>();
result.msg = message;
return result;
}
public static <T> CommonResult<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMsg());
}
public static <T> CommonResult<T> success(T data) {
CommonResult<T> result = new CommonResult<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.data = data;
result.msg = "";
return result;
}
public static boolean isSuccess(Integer code) {
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
}
@JsonIgnore // 避免 jackson 序列化
public boolean isSuccess() {
return isSuccess(code);
}
@JsonIgnore // 避免 jackson 序列化
public boolean isError() {
return !isSuccess();
}
// ========= 和 Exception 异常体系集成 =========
/**
*/
public void checkError() throws BusinessException {
if (isSuccess()) {
return;
}
// 业务异常
throw new BusinessException(msg);
}
/**
* 如果没有,则返回 {@link #data} 数据
*/
@JsonIgnore // 避免 jackson 序列化
public T getCheckedData() {
checkError();
return data;
}
public static <T> CommonResult<T> error(BusinessException serviceException) {
return error( serviceException.getMessage());
}
}

View File

@@ -0,0 +1,39 @@
package com.njcn.bpm.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "分页结果")
@Data
public final class PageResult<T> implements Serializable {
private List<T> list;
private Long total;
public PageResult() {
}
public PageResult(List<T> list, Long total) {
this.list = list;
this.total = total;
}
public PageResult(Long total) {
this.list = new ArrayList<>();
this.total = total;
}
public static <T> PageResult<T> empty() {
return new PageResult<>(0L);
}
public static <T> PageResult<T> empty(Long total) {
return new PageResult<>(total);
}
}

View File

@@ -0,0 +1,96 @@
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 lombok.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
/**
* BPM 流程分类
*
* @author 芋道源码
*/
@Data
public class BpmCategoryParam implements Serializable {
/**
* 分类名
*/
@ApiModelProperty("分类名")
@NotNull(message = "分类名不能为空")
private String name;
/**
* 分类标志
*/
@ApiModelProperty("分类标志")
@NotNull(message = "分类标志不能为空")
private String code;
/**
* 分类描述
*/
@ApiModelProperty("分类描述")
private String description;
/**
* 分类状态
*
*/
@ApiModelProperty("分类状态")
private Integer status = 1;
/**
* 分类排序
*/
@ApiModelProperty("分类排序")
private Integer sort = 100;
/**
* 更新操作实体
*/
@Data
@EqualsAndHashCode(callSuper = true)
public static class BpmCategoryUpdateParam extends BpmCategoryParam {
/**
* 表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 BpmCategoryQueryParam extends BaseParam {
/**
* 表单名称
*/
@ApiModelProperty("分类名称")
private String name;
/**
* 分类标志
*/
@ApiModelProperty("分类标志")
private String code;
}
}

View File

@@ -0,0 +1,88 @@
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 lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.util.List;
@Data
public class BpmFormParam implements Serializable {
/**
* 表单名
*/
@ApiModelProperty("表单名称")
@NotNull(message = "表单名称不能为空")
private String name;
/**
* 状态
*/
@ApiModelProperty("表单状态")
@NotNull(message = "表单状态不能为空")
private Integer status;
/**
* 表单的配置
*/
@ApiModelProperty("表单的配置-JSON 字符串")
@NotNull(message = "表单的配置不能为空")
private String conf;
/**
* 表单项的数组
*
*/
@ApiModelProperty("表单项的数组-JSON 字符串的数组")
@NotNull(message = "表单项的数组不能为空")
private List<String> 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
/**
* 流程定义的编号
* <p>
* 关联 ProcessDefinition 的 id 属性
*/
private String processDefinitionId;
/**
* 流程模型的编号
* <p>
* 关联 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<String> 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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<String> fields;
/**
* 备注
*/
private String remark;
/**
* 状态0-删除 1-正常
*/
private Integer state;
}

View File

@@ -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<String> formFields;
/**
* 自定义表单的提交路径,使用 Vue 的路由地址
*/
private String formCustomCreatePath;
/**
* 自定义表单的查看路径,使用 Vue 的路由地址
*/
private String formCustomViewPath;
/**
* 状态0-删除 1-正常
*/
private Integer state;
}

View File

@@ -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;
/**
* <p>
* 流程表单
* </p>
*
* @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;
}

View File

@@ -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;
}

View File

@@ -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<String> fields;
private Integer status; // 参见 CommonStatusEnum 枚举
private String remark;
private LocalDateTime createTime;
}

View File

@@ -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;
}

View File

@@ -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<String> 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<BpmProcessDefinitionInfoVO.UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回
@Schema(description = "BPMN UserTask 用户任务")
@Data
public static class UserTask {
private String id;
private String name;
}
}

View File

@@ -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> T toBean(Object source, Class<T> targetClass) {
return BeanUtil.toBean(source, targetClass);
}
public static <T> T toBean(Object source, Class<T> targetClass, Consumer<T> peek) {
T target = toBean(source, targetClass);
if (target != null) {
peek.accept(target);
}
return target;
}
public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {
if (source == null) {
return null;
}
return CollectionUtils.convertList(source, s -> toBean(s, targetType));
}
public static <S, T> List<T> toBean(List<S> source, Class<T> targetType, Consumer<T> peek) {
List<T> list = toBean(source, targetType);
if (list != null) {
list.forEach(peek);
}
return list;
}
//
// public static <S, T> Page<T> toBean(Page<S> source, Class<T> targetType) {
// return toBean(source, targetType, null);
// }
//
// public static <S, T> Page<T> toBean(Page<S> source, Class<T> targetType, Consumer<T> peek) {
// if (source == null) {
// return null;
// }
// List<T> list = toBean(source.getRecords(), targetType);
// if (peek != null) {
// list.forEach(peek);
// }
// return new Page<>(list, source.getTotal());
// }
}

View File

@@ -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 <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
return from.stream().anyMatch(predicate);
}
public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().filter(predicate).collect(Collectors.toList());
}
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return distinct(from, keyMapper, (t1, t2) -> t1);
}
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
}
public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
if (ArrayUtil.isEmpty(from)) {
return new ArrayList<>();
}
return convertList(Arrays.asList(from), func);
}
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
Function<T, ? extends Stream<? extends U>> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
Function<? super T, ? extends U> mapper,
Function<U, ? extends Stream<? extends R>> 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 <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
return map.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
}
public static <T> Set<T> convertSet(Collection<T> from) {
return convertSet(from, v -> v);
}
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
}
public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
Function<T, ? extends Stream<? extends U>> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
Function<? super T, ? extends U> mapper,
Function<U, ? extends Stream<? extends R>> 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 <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return convertMap(from, keyFunc, Function.identity());
}
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {
if (CollUtil.isEmpty(from)) {
return supplier.get();
}
return convertMap(from, keyFunc, Function.identity(), supplier);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {
if (CollUtil.isEmpty(from)) {
return supplier.get();
}
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
}
public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
}
public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream()
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
}
// 暂时没想好名字,先以 2 结尾噶
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
}
public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return Collections.emptyMap();
}
ImmutableMap.Builder<K, T> 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 <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
BiFunction<T, T, Boolean> sameFunc) {
List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
List<T> updateList = new ArrayList<>();
List<T> deleteList = new ArrayList<>();
// 通过以 oldList 为主遍历,找出 updateList 和 deleteList
for (T oldObj : oldList) {
// 1. 寻找是否有匹配的
T foundObj = null;
for (Iterator<T> 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> T getFirst(List<T> from) {
return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
}
public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {
return findFirst(from, predicate, Function.identity());
}
public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return null;
}
return from.stream().filter(predicate).findFirst().map(func).orElse(null);
}
public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> 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 <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> 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 <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,
BinaryOperator<V> accumulator) {
return getSumValue(from, valueFunc, accumulator, null);
}
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
BinaryOperator<V> 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 <T> void addIfNotNull(Collection<T> coll, T item) {
if (item == null) {
return;
}
coll.add(item);
}
public static <T> Collection<T> singleton(T obj) {
return obj == null ? Collections.emptyList() : Collections.singleton(obj);
}
public static <T> List<T> newArrayList(List<List<T>> list) {
return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
}
}

View File

@@ -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));
}
}

View File

@@ -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;
}
/**
* 通过经纬度获取地球上两点之间的距离
*
* 参考 <<a href="https://gitee.com/dromara/hutool/blob/1caabb586b1f95aec66a21d039c5695df5e0f4c1/hutool-core/src/main/java/cn/hutool/core/util/DistanceUtil.java">DistanceUtil</a>> 实现,目前它已经被 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);
}
}

View File

@@ -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<Object> result){
//如果返回错误,且为内部错误,则直接抛出异常
CommonResponseEnum commonResponseEnum = EnumUtils.getCommonResponseEnumByCode(result.getCode());
if (commonResponseEnum == CommonResponseEnum.DEVICE_RESPONSE_ENUM) {
return getHarmonicEnumResponseEnumByMessage(result.getMessage());
}
return commonResponseEnum;
}
}

View File

@@ -0,0 +1,55 @@
package com.njcn.bpm.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import org.springframework.util.StringUtils;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
import java.util.regex.Pattern;
/**
* 校验工具类
*
* @author 芋道源码
*/
public class ValidationUtils {
private static final Pattern PATTERN_MOBILE = Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])|(?:4[0,1,4-9])|(?:5[0-3,5-9])|(?:6[2,5-7])|(?:7[0-8])|(?:8[\\d])|(?:9[0-3,5-9]))\\d{8}$");
private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*");
public static boolean isMobile(String mobile) {
return StringUtils.hasText(mobile)
&& PATTERN_MOBILE.matcher(mobile).matches();
}
public static boolean isURL(String url) {
return StringUtils.hasText(url)
&& PATTERN_URL.matcher(url).matches();
}
public static boolean isXmlNCName(String str) {
return StringUtils.hasText(str)
&& PATTERN_XML_NCNAME.matcher(str).matches();
}
public static void validate(Object object, Class<?>... groups) {
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Assert.notNull(validator);
validate(validator, object, groups);
}
public static void validate(Validator validator, Object object, Class<?>... groups) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
if (CollUtil.isNotEmpty(constraintViolations)) {
throw new ConstraintViolationException(constraintViolations);
}
}
}