工作流模块提交

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

@@ -1,6 +1,5 @@
package com.njcn.advance.pojo.vo.govern.voltage;
import com.njcn.advance.pojo.po.govern.voltage.SgSensitiveUnit;
import lombok.Data;
import java.io.Serializable;

View File

@@ -9,13 +9,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.advance.enums.AdvanceResponseEnum;
import com.njcn.advance.mapper.govern.voltage.SgMachineMapper;
import com.njcn.advance.pojo.param.govern.voltage.SgMachineParam;
import com.njcn.advance.pojo.param.govern.voltage.SgProductLineParam;
import com.njcn.advance.pojo.po.govern.voltage.SgMachine;
import com.njcn.advance.pojo.po.govern.voltage.SgProductLine;
import com.njcn.advance.pojo.po.govern.voltage.SgSensitiveUnit;
import com.njcn.advance.pojo.po.govern.voltage.SgUser;
import com.njcn.advance.pojo.vo.govern.voltage.SgMachineVO;
import com.njcn.advance.pojo.vo.govern.voltage.SgProductLineVO;
import com.njcn.advance.service.govern.voltage.ISgMachineService;
import com.njcn.advance.service.govern.voltage.ISgSensitiveUnitService;
import com.njcn.common.pojo.enums.common.DataStateEnum;

97
pqs-bpm/bpm-api/pom.xml Normal file
View File

@@ -0,0 +1,97 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>pqs</artifactId>
<version>1.0.0</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>bpm-api</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<flowable.version>6.8.0</flowable.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<easy-trans.version>2.2.11</easy-trans.version>
</properties>
<dependencies>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>common-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>common-db</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>common-microservice</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Flowable 工作流相关 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId>
<artifactId>easy-trans-spring-boot-starter</artifactId>
<version>${easy-trans.version}</version>
<exclusions>
<exclusion>
<artifactId>spring-context</artifactId>
<groupId>org.springframework</groupId>
</exclusion>
<exclusion>
<artifactId>spring-cloud-commons</artifactId>
<groupId>org.springframework.cloud</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId>
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
<version>${easy-trans.version}</version>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId>
<artifactId>easy-trans-anno</artifactId>
<version>${easy-trans.version}</version>
</dependency>
</dependencies>
</project>

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

102
pqs-bpm/bpm-boot/pom.xml Normal file
View File

@@ -0,0 +1,102 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>pqs-bpm</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>bpm-boot</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 每个插件都要引入自己的对外接口 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>common-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>bpm-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>user-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<finalName>bpmboot</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerArgument>-Xlint:unchecked</compilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>build-image</id>
<phase>${docker.operate}</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
<configuration>
<!--<serverId>36dockerHarbor</serverId>-->
<registryUrl>http://${docker.repostory}</registryUrl>
<!-- 镜像名称 -->
<imageName>${docker.repostory}/${docker.registry.name}/${project.artifactId}</imageName>
<imageTags>
<imageTag>latest</imageTag>
</imageTags>
<dockerHost>${docker.url}</dockerHost>
<dockerDirectory>${basedir}/</dockerDirectory>
<resources>
<resource>
<targetPath>/ROOT</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,27 @@
package com.njcn.bpm;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.DependsOn;
/**
* pqs
*
* @author cdf
* @date 2022/11/10
*/
@Slf4j
@DependsOn("proxyMapperRegister")
@MapperScan("com.njcn.**.mapper")
@EnableFeignClients(basePackages = "com.njcn")
@SpringBootApplication(scanBasePackages = "com.njcn")
public class BpmApplication {
public static void main(String[] args) {
SpringApplication.run(BpmApplication.class, args);
}
}

View File

@@ -0,0 +1,47 @@
package com.njcn.bpm.behavior;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
/**
* 自定义的 ActivityBehaviorFactory 实现类,目的如下:
* 1. 自定义 {@link #createUserTaskActivityBehavior(UserTask)}:实现自定义的流程任务的 assignee 负责人的分配
*
* @author 芋道源码
*/
@Setter
public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Override
public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
BpmUserTaskActivityBehavior bpmUserTaskActivityBehavior = new BpmUserTaskActivityBehavior(userTask);
bpmUserTaskActivityBehavior.setTaskCandidateInvoker(taskCandidateInvoker);
return bpmUserTaskActivityBehavior;
}
@Override
public ParallelMultiInstanceBehavior createParallelMultiInstanceBehavior(Activity activity,
AbstractBpmnActivityBehavior behavior) {
BpmParallelMultiInstanceBehavior bpmParallelMultiInstanceBehavior = new BpmParallelMultiInstanceBehavior(activity, behavior);
bpmParallelMultiInstanceBehavior.setTaskCandidateInvoker(taskCandidateInvoker);
return bpmParallelMultiInstanceBehavior;
}
@Override
public SequentialMultiInstanceBehavior createSequentialMultiInstanceBehavior(Activity activity,
AbstractBpmnActivityBehavior behavior) {
BpmSequentialMultiInstanceBehavior bpmSequentialMultiInstanceBehavior = new BpmSequentialMultiInstanceBehavior(activity, behavior);
bpmSequentialMultiInstanceBehavior.setTaskCandidateInvoker(taskCandidateInvoker);
return bpmSequentialMultiInstanceBehavior;
}
}

View File

@@ -0,0 +1,57 @@
package com.njcn.bpm.behavior;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.utils.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior;
import java.util.List;
import java.util.Set;
/**
* 自定义的【并行】的【多个】流程任务的 assignee 负责人的分配
* 第一步,基于分配规则,计算出分配任务的【多个】候选人们。
* 第二步,将【多个】任务候选人们,设置到 DelegateExecution 的 collectionVariable 变量中,以便 BpmUserTaskActivityBehavior 使用它
*
* @author kemengkai
* @since 2022-04-21 16:57
*/
@Setter
public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehavior {
private BpmTaskCandidateInvoker taskCandidateInvoker;
public BpmParallelMultiInstanceBehavior(Activity activity,
AbstractBpmnActivityBehavior innerActivityBehavior) {
super(activity, innerActivityBehavior);
}
/**
* 重写该方法,主要实现两个功能:
* 1. 忽略原有的 collectionVariable、collectionElementVariable 表达式,而是采用自己定义的
* 2. 获得任务的处理人,并设置到 collectionVariable 中,用于 BpmUserTaskActivityBehavior 从中可以获取任务的处理人
*
* 注意,多个任务实例,每个任务实例对应一个处理人,所以返回的数量就是任务处理人的数量
*
* @param execution 执行任务
* @return 数量
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
// 第一步,设置 collectionVariable 和 CollectionVariable
// 从 execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
List<String> assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
return assigneeUserIds.size();
}
}

View File

@@ -0,0 +1,52 @@
package com.njcn.bpm.behavior;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.utils.FlowableUtils;
import lombok.Setter;
import org.flowable.bpmn.model.Activity;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 自定义的【串行】的【多个】流程任务的 assignee 负责人的分配
*
* 本质上,实现和 {@link BpmParallelMultiInstanceBehavior} 一样,只是继承的类不一样
*
* @author 芋道源码
*/
@Setter
public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceBehavior {
private BpmTaskCandidateInvoker taskCandidateInvoker;
public BpmSequentialMultiInstanceBehavior(Activity activity, AbstractBpmnActivityBehavior innerActivityBehavior) {
super(activity, innerActivityBehavior);
}
/**
* 逻辑和 {@link BpmParallelMultiInstanceBehavior#resolveNrOfInstances(DelegateExecution)} 类似
*
* 差异的点:是在【第二步】的时候,需要返回 LinkedHashSet 集合!因为它需要有序!
*/
@Override
protected int resolveNrOfInstances(DelegateExecution execution) {
// 第一步,设置 collectionVariable 和 CollectionVariable
// 从 execution.getVariable() 读取所有任务处理人的 key
super.collectionExpression = null; // collectionExpression 和 collectionVariable 是互斥的
super.collectionVariable = FlowableUtils.formatExecutionCollectionVariable(execution.getCurrentActivityId());
// 从 execution.getVariable() 读取当前所有任务处理的人的 key
super.collectionElementVariable = FlowableUtils.formatExecutionCollectionElementVariable(execution.getCurrentActivityId());
// 第二步,获取任务的所有处理人
List<String> assigneeUserIds = new ArrayList<>(taskCandidateInvoker.calculateUsers(execution)); // 保证有序!!!
execution.setVariable(super.collectionVariable, assigneeUserIds);
return assigneeUserIds.size();
}
}

View File

@@ -0,0 +1,67 @@
package com.njcn.bpm.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.TaskHelper;
import org.flowable.task.service.TaskService;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import java.util.List;
import java.util.Set;
/**
* 自定义的【单个】流程任务的 assignee 负责人的分配
* 第一步,基于分配规则,计算出分配任务的【单个】候选人。如果找不到,则直接报业务异常,不继续执行后续的流程;
* 第二步,随机选择一个候选人,则选择作为 assignee 负责人。
*
* @author 芋道源码
*/
@Slf4j
public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
@Setter
private BpmTaskCandidateInvoker taskCandidateInvoker;
public BpmUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}
@Override
protected void handleAssignments(TaskService taskService, String assignee, String owner,
List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager,
DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
// 第一步,获得任务的候选用户
String assigneeUserId = calculateTaskCandidateUsers(execution);
Assert.notNull(assigneeUserId, "任务处理人不能为空");
// 第二步,设置作为负责人
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
}
private String calculateTaskCandidateUsers(DelegateExecution execution) {
// 情况一,如果是多实例的任务,例如说会签、或签等情况,则从 Variable 中获取。
// 顺序审批可见 BpmSequentialMultiInstanceBehavior并发审批可见 BpmSequentialMultiInstanceBehavior
if (super.multiInstanceActivityBehavior != null) {
return execution.getVariable(super.multiInstanceActivityBehavior.getCollectionElementVariable(), String.class);
}
// 情况二,如果非多实例的任务,则计算任务处理人
// 第一步,先计算可处理该任务的处理人们
List<String> candidateUserIds = taskCandidateInvoker.calculateUsers(execution);
// 第二步,后随机选择一个任务的处理人
// 疑问:为什么一定要选择一个任务处理人?
// 解答:项目对 bpm 的任务是责任到人,所以每个任务有且仅有一个处理人。
// 如果希望一个任务可以同时被多个人处理,可以考虑使用 BpmParallelMultiInstanceBehavior 实现的会签 or 或签。
int index = RandomUtil.randomInt(candidateUserIds.size());
return CollUtil.get(candidateUserIds, index);
}
}

View File

@@ -0,0 +1,91 @@
package com.njcn.bpm.config;
import cn.hutool.core.collection.ListUtil;
import com.njcn.bpm.behavior.BpmActivityBehaviorFactory;
import com.njcn.bpm.event.BpmProcessInstanceEventPublisher;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.strategy.IBpmTaskCandidateStrategy;
import com.njcn.user.api.UserFeignClient;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.List;
/**
* BPM 模块的 Flowable 配置类
*
* @author jason
*/
@Configuration(proxyBeanMethods = false)
public class BpmFlowableConfiguration {
/**
* 参考 {@link org.flowable.spring.boot.FlowableJobConfiguration} 类,创建对应的 AsyncListenableTaskExecutor Bean
*
* 如果不创建会导致项目启动时Flowable 报错的问题
*/
@Bean(name = "applicationTaskExecutor")
@ConditionalOnMissingBean(name = "applicationTaskExecutor")
public AsyncListenableTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("flowable-task-Executor-");
executor.setAwaitTerminationSeconds(30);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAllowCoreThreadTimeOut(true);
executor.initialize();
return executor;
}
/**
* BPM 模块的 ProcessEngineConfigurationConfigurer 实现类:
*
* 1. 设置各种监听器
* 2. 设置自定义的 ActivityBehaviorFactory 实现
*/
@Bean
public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer(
ObjectProvider<FlowableEventListener> listeners,
BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
return configuration -> {
// 注册监听器,例如说 BpmActivityEventListener
configuration.setEventListeners(ListUtil.toList(listeners.iterator()));
// 设置 ActivityBehaviorFactory 实现类,用于流程任务的审核人的自定义
configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
};
}
// =========== 审批人相关的 Bean ==========
@Bean
public BpmActivityBehaviorFactory bpmActivityBehaviorFactory(BpmTaskCandidateInvoker bpmTaskCandidateInvoker) {
BpmActivityBehaviorFactory bpmActivityBehaviorFactory = new BpmActivityBehaviorFactory();
bpmActivityBehaviorFactory.setTaskCandidateInvoker(bpmTaskCandidateInvoker);
return bpmActivityBehaviorFactory;
}
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") // adminUserApi 可以注入成功
public BpmTaskCandidateInvoker bpmTaskCandidateInvoker(List<IBpmTaskCandidateStrategy> strategyList,
UserFeignClient adminUserApi) {
return new BpmTaskCandidateInvoker(strategyList, adminUserApi);
}
// =========== 自己拓展的 Bean ==========
@Bean
public BpmProcessInstanceEventPublisher processInstanceEventPublisher(ApplicationEventPublisher publisher) {
return new BpmProcessInstanceEventPublisher(publisher);
}
}

View File

@@ -0,0 +1,111 @@
package com.njcn.bpm.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.common.utils.LogUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/bpm/category")
@Validated
@Slf4j
@Api(tags = "流程分类控制器")
@RequiredArgsConstructor
public class BpmCategoryController extends BaseController {
private final IBpmCategoryService categoryService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@PostMapping("/add")
@ApiOperation("创建流程分类")
@ApiImplicitParam(name = "bpmCategoryParam", value = "流程分类数据", required = true)
public HttpResult<String> add(@Valid @RequestBody BpmCategoryParam bpmCategoryParam) {
String methodDescribe = getMethodDescribe("add");
String categoryId = categoryService.createCategory(bpmCategoryParam);
if (StrUtil.isNotBlank(categoryId)) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, categoryId, methodDescribe);
} else {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe);
}
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/update")
@ApiOperation("更新流程分类")
@ApiImplicitParam(name = "updateParam", value = "流程分类数据", required = true)
public HttpResult<Object> update(@RequestBody @Validated BpmCategoryParam.BpmCategoryUpdateParam updateParam) {
String methodDescribe = getMethodDescribe("update");
categoryService.updateCategory(updateParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/delete")
@ApiOperation("删除流程分类")
@ApiImplicitParam(name = "ids", value = "流程分类索引", required = true, dataTypeClass = List.class)
public HttpResult<Object> delete(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("delete");
LogUtil.njcnDebug(log, "{}流程分类ID数据为{}", methodDescribe, String.join(StrUtil.COMMA, ids));
categoryService.deleteCategory(ids);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@GetMapping("/getById")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得流程分类")
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<BpmCategory> getById(String id) {
String methodDescribe = getMethodDescribe("getById");
BpmCategory bpmCategory = categoryService.getById(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, bpmCategory, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/list")
@ApiOperation("查询流程分类数据")
@ApiImplicitParam(name = "bpmCategoryQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmCategoryVO>> list(@RequestBody BpmCategoryParam.BpmCategoryQueryParam bpmCategoryQueryParam) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{},查询流程分类数据为:{}", methodDescribe, bpmCategoryQueryParam);
Page<BpmCategoryVO> result = categoryService.getCategoryPage(bpmCategoryQueryParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@GetMapping("/simpleList")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框")
public HttpResult<List<BpmCategory>> getCategorySimpleList() {
String methodDescribe = getMethodDescribe("getCategorySimpleList");
List<BpmCategory> list = categoryService.getCategoryList();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe);
}
}

View File

@@ -0,0 +1,112 @@
package com.njcn.bpm.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmFormParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.common.utils.LogUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/bpm/form")
@Validated
@Slf4j
@Api(tags = "流程表单控制器")
@RequiredArgsConstructor
public class BpmFormController extends BaseController {
private final IBpmFormService formService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/list")
@ApiOperation("查询流程表单数据")
@ApiImplicitParam(name = "bpmFormQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmFormVO>> list(@RequestBody BpmFormParam.BpmFormQueryParam bpmFormQueryParam) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{},查询流程表单数据为:{}", methodDescribe, bpmFormQueryParam);
Page<BpmFormVO> result = formService.getFormPage(bpmFormQueryParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@PostMapping("/add")
@ApiOperation("新增流程表单")
@ApiImplicitParam(name = "bpmFormParam", value = "流程表单数据", required = true)
public HttpResult<String> add(@Valid @RequestBody BpmFormParam bpmFormParam) {
String methodDescribe = getMethodDescribe("add");
String wfFormId = formService.createForm(bpmFormParam);
if (StrUtil.isNotBlank(wfFormId)) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, wfFormId, methodDescribe);
} else {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, null, methodDescribe);
}
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/update")
@ApiOperation("更新流程表单")
@ApiImplicitParam(name = "updateParam", value = "流程表单数据", required = true)
public HttpResult<Object> update(@RequestBody @Validated BpmFormParam.BpmFormUpdateParam updateParam) {
String methodDescribe = getMethodDescribe("update");
formService.updateForm(updateParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/delete")
@ApiOperation("删除流程表单")
@ApiImplicitParam(name = "ids", value = "流程表单索引", required = true, dataTypeClass = List.class)
public HttpResult<Object> delete(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("delete");
LogUtil.njcnDebug(log, "{}流程表单ID数据为{}", methodDescribe, String.join(StrUtil.COMMA, ids));
formService.deleteForm(ids);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
@GetMapping("/getById")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得动态表单")
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<BpmForm> getById(String id) {
String methodDescribe = getMethodDescribe("getById");
BpmForm form = formService.getById(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, form, methodDescribe);
}
@GetMapping("/simpleList")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Operation(summary = "获得动态表单的精简列表", description = "用于表单下拉框")
public HttpResult<List<BpmForm>> getFormSimpleList() {
String methodDescribe = getMethodDescribe("getFormSimpleList");
List<BpmForm> list = formService.getFormList();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe);
}
}

View File

@@ -0,0 +1,174 @@
package com.njcn.bpm.controller;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
import com.njcn.bpm.pojo.param.BpmModelParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmModelRespVO;
import com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.bpm.service.IBpmModelService;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.utils.BpmModelConvert;
import com.njcn.bpm.utils.CollectionUtils;
import com.njcn.bpm.utils.JsonUtils;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.common.utils.LogUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.njcn.bpm.pojo.dto.CommonResult.success;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
import static com.njcn.bpm.utils.CollectionUtils.convertSet;
@RestController
@RequestMapping("/bpm/model")
@Validated
@Slf4j
@Api(tags = "流程模型控制器")
@RequiredArgsConstructor
public class BpmModelController extends BaseController {
private final IBpmModelService modelService;
private final IBpmFormService formService;
private final IBpmCategoryService categoryService;
private final IBpmProcessDefinitionService processDefinitionService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/list")
@ApiOperation("获得模型分页")
@ApiImplicitParam(name = "bpmModelQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmModelRespVO>> getModelPage(@RequestBody BpmModelParam.BpmModelQueryParam bpmModelQueryParam) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{},查询流程表单数据为:{}", methodDescribe, bpmModelQueryParam);
Page<Model> pageResult = modelService.getModelPage(bpmModelQueryParam);
if (CollUtil.isEmpty(pageResult.getRecords())) {
Page<BpmModelRespVO> result = new Page<>();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
// 拼接数据
// 获得 Form 表单
Set<String> formIds = convertSet(pageResult.getRecords(), model -> {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
return metaInfo != null ? metaInfo.getFormId() : null;
});
Map<String, BpmForm> formMap = formService.getFormMap(formIds);
// 获得 Category Map
Map<String, BpmCategory> categoryMap = categoryService.getCategoryMap(
convertSet(pageResult.getRecords(), Model::getCategory));
// 获得 Deployment Map
Set<String> deploymentIds = new HashSet<>();
pageResult.getRecords().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
// 获得 ProcessDefinition Map
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmModelConvert.INSTANCE.buildModelPage(pageResult, formMap, categoryMap, deploymentMap, processDefinitionMap), methodDescribe);
}
@GetMapping("/getById")
@Operation(summary = "获得模型")
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<BpmModelRespVO> getById(String id) {
Model model = modelService.getModel(id);
String methodDescribe = getMethodDescribe("getById");
if (model == null) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
byte[] bpmnBytes = modelService.getModelBpmnXML(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmModelConvert.INSTANCE.buildModel(model, bpmnBytes), methodDescribe);
}
@Operation(summary = "新建模型")
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@PostMapping("/add")
@ApiOperation("新建模型")
@ApiImplicitParam(name = "createRetVO", value = "模型数据", required = true)
public HttpResult<String> createModel(@Validated @RequestBody BpmModelParam bpmModelParam) {
String methodDescribe = getMethodDescribe("createModel");
String modelId = modelService.createModel(bpmModelParam, null);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, modelId, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@PostMapping("/update")
@ApiOperation("修改模型")
@ApiImplicitParam(name = "updateParam", value = "流程分类数据", required = true)
public HttpResult<Object> update( @Validated @RequestBody BpmModelParam.BpmModelUpdateParam updateParam) {
String methodDescribe = getMethodDescribe("update");
modelService.updateModel(updateParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
// @PostMapping("/import")
// @Operation(summary = "导入模型")
// @PreAuthorize("@ss.hasPermission('bpm:model:import')")
// public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
// BpmModelCreateReqVO createReqVO = BeanUtils.toBean(importReqVO, BpmModelCreateReqVO.class);
// // 读取文件
// String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
// return success(modelService.createModel(createReqVO, bpmnXml));
// }
@PostMapping("/deploy")
@Operation(summary = "部署模型")
@Parameter(name = "id", description = "编号", required = true)
public HttpResult<Boolean> deployModel(String id) {
String methodDescribe = getMethodDescribe("deployModel");
modelService.deployModel(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
//
// @PutMapping("/update-state")
// @Operation(summary = "修改模型的状态", description = "实际更新的部署的流程定义的状态")
// @PreAuthorize("@ss.hasPermission('bpm:model:update')")
// public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
// modelService.updateModelState(reqVO.getId(), reqVO.getState());
// return success(true);
// }
//
// @DeleteMapping("/delete")
// @Operation(summary = "删除模型")
// @Parameter(name = "id", description = "编号", required = true, example = "1024")
// @PreAuthorize("@ss.hasPermission('bpm:model:delete')")
// public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
// modelService.deleteModel(id);
// return success(true);
// }
}

View File

@@ -0,0 +1,123 @@
package com.njcn.bpm.controller;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmProcessDefinitionInfoParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import com.njcn.bpm.pojo.vo.BpmProcessDefinitionInfoVO;
import com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.strategy.BpmTaskCandidateStartUserSelectStrategy;
import com.njcn.bpm.utils.BpmProcessDefinitionConvert;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.web.controller.BaseController;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.njcn.bpm.utils.CollectionUtils.convertSet;
@RestController
@RequestMapping("/bpm/processDefinition")
@Validated
@Slf4j
@Api(tags = "管理后台 - 流程定义")
@RequiredArgsConstructor
public class BpmProcessDefinitionController extends BaseController {
private final IBpmProcessDefinitionService processDefinitionService;
private final IBpmFormService formService;
private final IBpmCategoryService categoryService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@PostMapping("/page")
@ApiOperation("获得流程定义分页")
@ApiImplicitParam(name = "bpmProcessDefinitionInfoQueryParam", value = "查询参数", required = true)
public HttpResult<Page<BpmProcessDefinitionInfoVO>> getProcessDefinitionPage(
BpmProcessDefinitionInfoParam.BpmProcessDefinitionInfoQueryParam bpmProcessDefinitionInfoQueryParam) {
String methodDescribe = getMethodDescribe("getProcessDefinitionPage");
Page<ProcessDefinition> pageResult = processDefinitionService.getProcessDefinitionPage(bpmProcessDefinitionInfoQueryParam);
if (CollUtil.isEmpty(pageResult.getRecords())) {
Page<BpmProcessDefinitionInfoVO> result = new Page<>();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
// 获得 Category Map
Map<String, BpmCategory> categoryMap = categoryService.getCategoryMap(
convertSet(pageResult.getRecords(), ProcessDefinition::getCategory));
// 获得 Deployment Map
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
convertSet(pageResult.getRecords(), ProcessDefinition::getDeploymentId));
// 获得 BpmProcessDefinitionInfoDO Map
Map<String, BpmProcessDefinitionInfo> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
convertSet(pageResult.getRecords(), ProcessDefinition::getId));
// 获得 Form Map
Map<String, BpmForm> formMap = formService.getFormMap(
convertSet(processDefinitionMap.values(), BpmProcessDefinitionInfo::getFormId));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionPage(
pageResult, deploymentMap, processDefinitionMap, formMap, categoryMap), methodDescribe);
}
@GetMapping("/list")
@Operation(summary = "获得流程定义列表")
@Parameter(name = "suspensionState", description = "挂起状态", required = true) // 参见 Flowable SuspensionState 枚举
public HttpResult<List<BpmProcessDefinitionInfoVO>> getProcessDefinitionList(Integer suspensionState) {
String methodDescribe = getMethodDescribe("getProcessDefinitionList");
List<ProcessDefinition> list = processDefinitionService.getProcessDefinitionListBySuspensionState(suspensionState);
if (CollUtil.isEmpty(list)) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, new ArrayList<>(), methodDescribe);
}
// 获得 BpmProcessDefinitionInfoDO Map
Map<String, BpmProcessDefinitionInfo> processDefinitionMap = processDefinitionService.getProcessDefinitionInfoMap(
convertSet(list, ProcessDefinition::getId));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinitionList(
list, null, processDefinitionMap, null, null), methodDescribe);
}
@GetMapping("/get")
@Operation(summary = "获得流程定义")
@Parameter(name = "id", description = "流程编号", required = true, example = "1024")
@Parameter(name = "key", description = "流程定义标识", required = true, example = "1024")
public HttpResult<BpmProcessDefinitionInfoVO> getProcessDefinition(
@RequestParam(value = "id", required = false) String id,
@RequestParam(value = "key", required = false) String key) {
String methodDescribe = getMethodDescribe("getProcessDefinition");
ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id)
: processDefinitionService.getActiveProcessDefinition(key);
if (processDefinition == null) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
}
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
processDefinition, null, null, null, null, bpmnModel, userTaskList), methodDescribe);
}
}

View File

@@ -0,0 +1,23 @@
package com.njcn.bpm.event;
import lombok.AllArgsConstructor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
/**
*
* @author 芋道源码
*/
@AllArgsConstructor
@Validated
public class BpmProcessInstanceEventPublisher {
private final ApplicationEventPublisher publisher;
public void sendProcessInstanceResultEvent(@Valid BpmProcessInstanceStatusEvent event) {
publisher.publishEvent(event);
}
}

View File

@@ -0,0 +1,42 @@
package com.njcn.bpm.event;
import lombok.Data;
import org.springframework.context.ApplicationEvent;
import javax.validation.constraints.NotNull;
/**
* 流程实例的状态(结果)发生变化的 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,22 @@
package com.njcn.bpm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* BPM 流程分类 Mapper
*
* @author 芋道源码
*/
@Mapper
public interface BpmCategoryMapper extends BaseMapper<BpmCategory> {
Page<BpmCategoryVO> page(@Param("page") Page<Object> objectPage, @Param("ew") QueryWrapper<BpmCategoryVO> categoryVOQueryWrapper);
}

View File

@@ -0,0 +1,21 @@
package com.njcn.bpm.mapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 动态表单 Mapper
*
* @author 风里雾里
*/
@Mapper
public interface BpmFormMapper extends BaseMapper<BpmForm> {
Page<BpmFormVO> page(@Param("page")Page<Object> objectPage, @Param("ew") QueryWrapper<BpmFormVO> categoryVOQueryWrapper);
}

View File

@@ -0,0 +1,13 @@
package com.njcn.bpm.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BpmProcessDefinitionInfoMapper extends BaseMapper<BpmProcessDefinitionInfo> {
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.bpm.mapper.BpmCategoryMapper">
<!--获取流程表单分页列表-->
<select id="page" resultType="BpmCategoryVO">
SELECT
bpm_category.id,
bpm_category.name,
bpm_category.code,
bpm_category.status,
bpm_category.create_time,
bpm_category.remark
FROM bpm_category bpm_category
WHERE ${ew.sqlSegment}
</select>
</mapper>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.bpm.mapper.BpmFormMapper">
<!--获取流程表单分页列表-->
<select id="page" resultType="BpmFormVO">
SELECT
bpm_form.id,
bpm_form.name,
bpm_form.status,
bpm_form.create_time,
bpm_form.remark
FROM bpm_form bpm_form
WHERE ${ew.sqlSegment}
</select>
</mapper>

View File

@@ -0,0 +1,70 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
/**
* BPM 流程分类 Service 接口
*
* @author 芋道源码
*/
public interface IBpmCategoryService extends IService<BpmCategory> {
/**
* 创建流程分类
*
* @return 编号
*/
String createCategory( BpmCategoryParam bpmCategoryParam);
/**
* 更新流程分类
*
*/
void updateCategory( BpmCategoryParam.BpmCategoryUpdateParam updateParam);
/**
* 删除流程分类
*/
void deleteCategory(List<String> ids);
/**
* 获得流程分类分页
*
* @return 流程分类分页
*/
Page<BpmCategoryVO> getCategoryPage(BpmCategoryParam.BpmCategoryQueryParam bpmCategoryQueryParam);
/**
* 获得流程分类 Map基于指定编码
*
* @param codes 编号数组
* @return 流程分类 Map
*/
default Map<String, BpmCategory> getCategoryMap(Collection<String> codes) {
return convertMap(getCategoryListByCode(codes), BpmCategory::getCode);
}
/**
* 获得流程分类列表,基于指定编码
*
* @return 流程分类列表
*/
List<BpmCategory> getCategoryListByCode(Collection<String> codes);
List<BpmCategory> getCategoryList();
}

View File

@@ -0,0 +1,77 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.bpm.pojo.param.BpmFormParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.utils.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 动态表单 Service 接口
*
* @author @风里雾里
*/
public interface IBpmFormService extends IService<BpmForm> {
/**
* 创建动态表单
*
* @return 编号
*/
String createForm(BpmFormParam bpmFormParam);
/**
* 更新动态表单
*
*/
void updateForm(BpmFormParam.BpmFormUpdateParam updateParam);
/**
* 删除动态表单
*
* @param id 编号
*/
void deleteForm(List<String> id);
/**
* 获得动态表单列表
*
* @return 动态表单列表
*/
List<BpmForm> getFormList();
/**
* 获得动态表单列表
*
* @param ids 编号
* @return 动态表单列表
*/
List<BpmForm> getFormList(Collection<String> ids);
/**
* 获得动态表单 Map
*
* @param ids 编号
* @return 动态表单 Map
*/
default Map<String, BpmForm> getFormMap(Collection<String> ids) {
return CollectionUtils.convertMap(this.getFormList(ids), BpmForm::getId);
}
/**
* 获得动态表单分页
*
* @return 动态表单分页
*/
Page<BpmFormVO> getFormPage(BpmFormParam.BpmFormQueryParam bpmFormQueryParam);
}

View File

@@ -0,0 +1,84 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.param.BpmModelParam;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Model;
import javax.validation.Valid;
/**
* Flowable流程模型接口
*
* @author yunlongn
*/
public interface IBpmModelService {
/**
* 获得流程模型分页
*
* @return 流程模型分页
*/
Page<Model> getModelPage(BpmModelParam.BpmModelQueryParam bpmModelQueryParam);
/**
* 创建流程模型
*
* @param bpmnXml BPMN XML
* @return 创建的流程模型的编号
*/
String createModel(BpmModelParam bpmModelParam, String bpmnXml);
/**
* 获得流程模块
*
* @param id 编号
* @return 流程模型
*/
Model getModel(String id);
/**
* 获得流程模型的 BPMN XML
*
* @param id 编号
* @return BPMN XML
*/
byte[] getModelBpmnXML(String id);
/**
* 修改流程模型
*
*/
void updateModel(BpmModelParam.BpmModelUpdateParam updateParam);
/**
* 将流程模型,部署成一个流程定义
*
* @param id 编号
*/
void deployModel(String id);
/**
* 删除模型
*
* @param id 编号
*/
void deleteModel(String id);
/**
* 修改模型的状态,实际更新的部署的流程定义的状态
*
* @param id 编号
* @param state 状态
*/
void updateModelState(String id, Integer state);
/**
* 获得流程定义编号对应的 BPMN Model
*
* @param processDefinitionId 流程定义编号
* @return BPMN Model
*/
BpmnModel getBpmnModelByDefinitionId(String processDefinitionId);
}

View File

@@ -0,0 +1,163 @@
package com.njcn.bpm.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmProcessDefinitionInfoParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
/**
* Flowable流程定义接口
*
* @author yunlong.li
* @author ZJQ
* @author 芋道源码
*/
public interface IBpmProcessDefinitionService extends IService<BpmProcessDefinitionInfo> {
/**
* 获得流程定义分页
*
* @return 流程定义 Page
*/
Page<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionInfoParam.BpmProcessDefinitionInfoQueryParam bpmProcessDefinitionInfoQueryParam);
/**
* 获得流程定义列表
*
* @param suspensionState 中断状态
* @return 流程定义列表
*/
List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState);
/**
* 基于流程模型,创建流程定义
*
* @param model 流程模型
* @param modelMetaInfo 流程模型元信息
* @param bpmnBytes BPMN XML 字节数组
* @param form 表单
* @return 流程编号
*/
String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo, byte[] bpmnBytes, BpmForm form);
/**
* 更新流程定义状态
*
* @param id 流程定义的编号
* @param state 状态
*/
void updateProcessDefinitionState(String id, Integer state);
/**
* 获得流程定义对应的 BPMN
*
* @param id 流程定义编号
* @return BPMN
*/
BpmnModel getProcessDefinitionBpmnModel(String id);
/**
* 获得流程定义的信息
*
* @param id 流程定义编号
* @return 流程定义信息
*/
BpmProcessDefinitionInfo getProcessDefinitionInfo(String id);
/**
* 获得流程定义的信息 List
*
* @param ids 流程定义编号数组
* @return 流程额定义信息数组
*/
List<BpmProcessDefinitionInfo> getProcessDefinitionInfoList(Collection<String> ids);
default Map<String, BpmProcessDefinitionInfo> getProcessDefinitionInfoMap(Set<String> ids) {
return convertMap(getProcessDefinitionInfoList(ids), BpmProcessDefinitionInfo::getProcessDefinitionId);
}
/**
* 获得流程定义编号对应的 ProcessDefinition
*
* @param id 流程定义编号
* @return 流程定义
*/
ProcessDefinition getProcessDefinition(String id);
/**
* 获得 ids 对应的 ProcessDefinition 数组
*
* @param ids 编号的数组
* @return 流程定义的数组
*/
List<ProcessDefinition> getProcessDefinitionList(Set<String> ids);
default Map<String, ProcessDefinition> getProcessDefinitionMap(Set<String> ids) {
return convertMap(getProcessDefinitionList(ids), ProcessDefinition::getId);
}
/**
* 获得 deploymentId 对应的 ProcessDefinition
*
* @param deploymentId 部署编号
* @return 流程定义
*/
ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId);
/**
* 获得 deploymentIds 对应的 ProcessDefinition 数组
*
* @param deploymentIds 部署编号的数组
* @return 流程定义的数组
*/
List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds);
/**
* 获得流程定义标识对应的激活的流程定义
*
* @param key 流程定义的标识
* @return 流程定义
*/
ProcessDefinition getActiveProcessDefinition(String key);
/**
* 获得 ids 对应的 Deployment Map
*
* @param ids 部署编号的数组
* @return 流程部署 Map
*/
default Map<String, Deployment> getDeploymentMap(Set<String> ids) {
return convertMap(getDeploymentList(ids), Deployment::getId);
}
/**
* 获得 ids 对应的 Deployment 数组
*
* @param ids 部署编号的数组
* @return 流程部署的数组
*/
List<Deployment> getDeploymentList(Set<String> ids);
/**
* 获得 id 对应的 Deployment
*
* @param id 部署编号
* @return 流程部署
*/
Deployment getDeployment(String id);
}

View File

@@ -0,0 +1,143 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.bpm.enums.BpmResponseEnum;
import com.njcn.bpm.mapper.BpmCategoryMapper;
import com.njcn.bpm.pojo.param.BpmCategoryParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmCategoryVO;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.service.IBpmCategoryService;
import com.njcn.bpm.utils.BeanUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* BPM 流程分类 Service 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class BpmCategoryServiceImpl extends ServiceImpl<BpmCategoryMapper, BpmCategory> implements IBpmCategoryService {
@Override
public String createCategory(BpmCategoryParam bpmCategoryParam) {
// 校验唯一
checkCategoryName(bpmCategoryParam, false);
checkCategoryCode(bpmCategoryParam, false);
// 插入
BpmCategory category = BeanUtils.toBean(bpmCategoryParam, BpmCategory.class);
this.baseMapper.insert(category);
return category.getId();
}
@Override
public void updateCategory(BpmCategoryParam.BpmCategoryUpdateParam updateParam) {
// 校验唯一
checkCategoryName(updateParam, true);
checkCategoryCode(updateParam, true);
// 更新
BpmCategory updateObj = BeanUtils.toBean(updateParam, BpmCategory.class);
this.baseMapper.updateById(updateObj);
}
@Override
public void deleteCategory(List<String> ids) {
this.lambdaUpdate().set(BpmCategory::getState, DataStateEnum.DELETED.getCode())
.in(BpmCategory::getId, ids)
.update();
}
@Override
public Page<BpmCategoryVO> getCategoryPage(BpmCategoryParam.BpmCategoryQueryParam bpmCategoryQueryParam) {
QueryWrapper<BpmCategoryVO> categoryVOQueryWrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(bpmCategoryQueryParam.getName())) {
categoryVOQueryWrapper.like("bpm_category.name", bpmCategoryQueryParam.getName());
}
if (StrUtil.isNotBlank(bpmCategoryQueryParam.getCode())) {
categoryVOQueryWrapper.like("bpm_category.name", bpmCategoryQueryParam.getCode());
}
categoryVOQueryWrapper.eq("bpm_category.state", DataStateEnum.ENABLE.getCode());
categoryVOQueryWrapper.orderByDesc("bpm_category.update_time");
return this.baseMapper.page(new Page<>(PageFactory.getPageNum(bpmCategoryQueryParam), PageFactory.getPageSize(bpmCategoryQueryParam)), categoryVOQueryWrapper);
}
@Override
public List<BpmCategory> getCategoryListByCode(Collection<String> codes) {
if (CollUtil.isEmpty(codes)) {
return Collections.emptyList();
}
LambdaQueryWrapper<BpmCategory> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(BpmCategory::getCode,codes);
return this.baseMapper.selectList(lambdaQueryWrapper);
}
@Override
public List<BpmCategory> getCategoryList() {
LambdaQueryWrapper<BpmCategory> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(BpmCategory::getId,BpmCategory::getName)
.eq(BpmCategory::getState,DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(lambdaQueryWrapper);
}
private void checkCategoryName(BpmCategoryParam bpmCategoryParam, boolean isExcludeSelf) {
LambdaQueryWrapper<BpmCategory> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
//判断流程表单的名称
categoryLambdaQueryWrapper
.eq(BpmCategory::getName, bpmCategoryParam.getName())
.eq(BpmCategory::getState, DataStateEnum.ENABLE.getCode());
//更新的时候,需排除当前记录
if (isExcludeSelf) {
if (bpmCategoryParam instanceof BpmCategoryParam.BpmCategoryUpdateParam) {
categoryLambdaQueryWrapper.ne(BpmCategory::getId, ((BpmCategoryParam.BpmCategoryUpdateParam) bpmCategoryParam).getId());
}
}
int nameCountByAccount = this.count(categoryLambdaQueryWrapper);
//大于等于1个则表示重复
if (nameCountByAccount >= 1) {
throw new BusinessException(BpmResponseEnum.REPEAT_CATEGORY_NAME_FORM);
}
}
private void checkCategoryCode(BpmCategoryParam bpmCategoryParam, boolean isExcludeSelf) {
LambdaQueryWrapper<BpmCategory> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
//判断流程表单的名称
categoryLambdaQueryWrapper
.eq(BpmCategory::getCode, bpmCategoryParam.getCode())
.eq(BpmCategory::getState, DataStateEnum.ENABLE.getCode());
//更新的时候,需排除当前记录
if (isExcludeSelf) {
if (bpmCategoryParam instanceof BpmCategoryParam.BpmCategoryUpdateParam) {
categoryLambdaQueryWrapper.ne(BpmCategory::getId, ((BpmCategoryParam.BpmCategoryUpdateParam) bpmCategoryParam).getId());
}
}
int nameCountByAccount = this.count(categoryLambdaQueryWrapper);
//大于等于1个则表示重复
if (nameCountByAccount >= 1) {
throw new BusinessException(BpmResponseEnum.REPEAT_CATEGORY_NAME_FORM);
}
}
}

View File

@@ -0,0 +1,139 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.bpm.enums.BpmResponseEnum;
import com.njcn.bpm.mapper.BpmFormMapper;
import com.njcn.bpm.pojo.param.BpmFormParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.vo.BpmFormVO;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.utils.BeanUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.*;
/**
* 动态表单 Service 实现类
*
* @author 风里雾里
*/
@Service
@Validated
public class BpmFormServiceImpl extends ServiceImpl<BpmFormMapper, BpmForm> implements IBpmFormService {
@Override
public Page<BpmFormVO> getFormPage(BpmFormParam.BpmFormQueryParam bpmFormQueryParam) {
QueryWrapper<BpmFormVO> bpmFormVOQueryWrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(bpmFormQueryParam.getName())) {
bpmFormVOQueryWrapper.like("bpm_form.name", bpmFormQueryParam.getName());
}
bpmFormVOQueryWrapper.eq("bpm_form.state", DataStateEnum.ENABLE.getCode());
bpmFormVOQueryWrapper.orderByDesc("bpm_form.update_time");
return this.baseMapper.page(new Page<>(PageFactory.getPageNum(bpmFormQueryParam), PageFactory.getPageSize(bpmFormQueryParam)), bpmFormVOQueryWrapper);
}
@Override
public String createForm(BpmFormParam bpmFormParam) {
// this.validateFields(createReqVO.getFields());
checkFormName(bpmFormParam, false);
// 插入
BpmForm form = BeanUtils.toBean(bpmFormParam, BpmForm.class);
this.baseMapper.insert(form);
// 返回
return form.getId();
}
private void checkFormName(BpmFormParam bpmFormParam, boolean isExcludeSelf) {
LambdaQueryWrapper<BpmForm> categoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
//判断流程表单的名称
categoryLambdaQueryWrapper
.eq(BpmForm::getName, bpmFormParam.getName())
.eq(BpmForm::getState, DataStateEnum.ENABLE.getCode());
//更新的时候,需排除当前记录
if (isExcludeSelf) {
if (bpmFormParam instanceof BpmFormParam.BpmFormUpdateParam) {
categoryLambdaQueryWrapper.ne(BpmForm::getId, ((BpmFormParam.BpmFormUpdateParam) bpmFormParam).getId());
}
}
int nameCountByAccount = this.count(categoryLambdaQueryWrapper);
//大于等于1个则表示重复
if (nameCountByAccount >= 1) {
throw new BusinessException(BpmResponseEnum.REPEAT_NAME_FORM);
}
}
@Override
public void updateForm(BpmFormParam.BpmFormUpdateParam updateParam) {
checkFormName(updateParam, true);
BpmForm updateObj = BeanUtils.toBean(updateParam, BpmForm.class);
this.updateById(updateObj);
}
@Override
public void deleteForm(List<String> ids) {
this.lambdaUpdate().set(BpmForm::getState, DataStateEnum.DELETED.getCode())
.in(BpmForm::getId, ids)
.update();
}
// private void validateFormExists(Long id) {
// if (this.baseMapper.selectById(id) == null) {
// throw exception(ErrorCodeConstants.FORM_NOT_EXISTS);
// }
// }
@Override
public List<BpmForm> getFormList() {
LambdaQueryWrapper<BpmForm> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(BpmForm::getId,BpmForm::getName)
.eq(BpmForm::getState,DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(lambdaQueryWrapper);
}
@Override
public List<BpmForm> getFormList(Collection<String> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return this.baseMapper.selectBatchIds(ids);
}
/**
* 校验 Field避免 field 重复
*
* @param fields field 数组
*/
private void validateFields(List<String> fields) {
if (true) { // TODO 芋艿:兼容 Vue3 工作流:因为采用了新的表单设计器,所以暂时不校验
return;
}
// Map<String, String> fieldMap = new HashMap<>(); // key 是 vModelvalue 是 label
// for (String field : fields) {
// BpmFormFieldRespDTO fieldDTO = JsonUtils.parseObject(field, BpmFormFieldRespDTO.class);
// Assert.notNull(fieldDTO);
// String oldLabel = fieldMap.put(fieldDTO.getVModel(), fieldDTO.getLabel());
// // 如果不存在,则直接返回
// if (oldLabel == null) {
// continue;
// }
// // 如果存在,则报错
// throw exception(ErrorCodeConstants.FORM_FIELD_REPEAT, oldLabel, fieldDTO.getLabel(), fieldDTO.getVModel());
// }
}
}

View File

@@ -0,0 +1,283 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.enums.BpmModelFormTypeEnum;
import com.njcn.bpm.enums.BpmResponseEnum;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmModelParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.service.IBpmFormService;
import com.njcn.bpm.service.IBpmModelService;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.strategy.BpmTaskCandidateInvoker;
import com.njcn.bpm.utils.*;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Objects;
/**
* Flowable流程模型实现
* 主要进行 Flowable {@link Model} 的维护
*
* @author yunlongn
* @author 芋道源码
* @author jason
*/
@Service
@Validated
@Slf4j
public class BpmModelServiceImpl implements IBpmModelService {
@Resource
private RepositoryService repositoryService;
@Resource
private IBpmProcessDefinitionService processDefinitionService;
@Resource
private IBpmFormService bpmFormService;
@Resource
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Override
public Page<Model> getModelPage(BpmModelParam.BpmModelQueryParam bpmModelQueryParam) {
ModelQuery modelQuery = repositoryService.createModelQuery();
if (StrUtil.isNotBlank(bpmModelQueryParam.getKey())) {
modelQuery.modelKey(bpmModelQueryParam.getKey());
}
if (StrUtil.isNotBlank(bpmModelQueryParam.getName())) {
modelQuery.modelNameLike("%" + bpmModelQueryParam.getName() + "%"); // 模糊匹配
}
if (StrUtil.isNotBlank(bpmModelQueryParam.getCategory())) {
modelQuery.modelCategory(bpmModelQueryParam.getCategory());
}
// 执行查询
long count = modelQuery.count();
if (count == 0) {
return new Page<>();
}
int offset = PageFactory.getPageSize(bpmModelQueryParam) * (PageFactory.getPageNum(bpmModelQueryParam) - 1);
List<Model> models = modelQuery
.modelTenantId(ProcessEngineConfiguration.NO_TENANT_ID)
.orderByCreateTime().desc()
.listPage(offset, PageFactory.getPageSize(bpmModelQueryParam));
Page<Model> page = new Page<>();
page.setRecords(models);
page.setTotal(count);
page.setSize(PageFactory.getPageSize(bpmModelQueryParam));
page.setCurrent(PageFactory.getPageNum(bpmModelQueryParam));
return page;
}
@Override
@Transactional(rollbackFor = Exception.class)
public String createModel(BpmModelParam bpmModelParam, String bpmnXml) {
if (!ValidationUtils.isXmlNCName(bpmModelParam.getKey())) {
throw new BusinessException(BpmResponseEnum.BPM_XML_ERROR);
}
// 校验流程标识已经存在
Model keyModel = getModelByKey(bpmModelParam.getKey());
if (keyModel != null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_REPEAT);
}
// 创建流程定义
Model model = repositoryService.newModel();
BpmModelConvert.INSTANCE.copyToCreateModel(model, bpmModelParam);
model.setTenantId(FlowableUtils.getTenantId());
// 保存流程定义
repositoryService.saveModel(model);
// 保存 BPMN XML
saveModelBpmnXml(model, bpmnXml);
return model.getId();
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
public void updateModel(BpmModelParam.BpmModelUpdateParam updateParam) {
// 校验流程模型存在
Model model = getModel(updateParam.getId());
if (model == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 修改流程定义
BpmModelConvert.INSTANCE.copyToUpdateModel(model, updateParam);
// 更新模型
repositoryService.saveModel(model);
// 更新 BPMN XML
saveModelBpmnXml(model, updateParam.getBpmnXml());
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
public void deployModel(String id) {
// 1.1 校验流程模型存在
Model model = getModel(id);
if (ObjectUtils.isEmpty(model)) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 1.2 校验流程图
byte[] bpmnBytes = getModelBpmnXML(model.getId());
validateBpmnXml(bpmnBytes);
// 1.3 校验表单已配
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
BpmForm form = validateFormConfig(metaInfo);
// 1.4 校验任务分配规则已配置
taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
// 2.1 创建流程定义
String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form);
// 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
updateProcessDefinitionSuspended(model.getDeploymentId());
// 2.3 更新 model 的 deploymentId进行关联
ProcessDefinition definition = processDefinitionService.getProcessDefinition(definitionId);
model.setDeploymentId(definition.getDeploymentId());
repositoryService.saveModel(model);
}
private void validateBpmnXml(byte[] bpmnBytes) {
BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
if (bpmnModel == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 1. 没有 StartEvent
StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
if (startEvent == null) {
throw new BusinessException(BpmResponseEnum.BPM_START_EVENT_NOT_EXIST);
}
// 2. 校验 UserTask 的 name 都配置了
List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
UserTask userTask1 = userTasks.get(0);
userTask1.getName();
userTasks.forEach(userTask -> {
if (StrUtil.isEmpty(userTask.getName())) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS);
}
});
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteModel(String id) {
// 校验流程模型存在
Model model = getModel(id);
if (model == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 执行删除
repositoryService.deleteModel(id);
// 禁用流程定义
updateProcessDefinitionSuspended(model.getDeploymentId());
}
@Override
public void updateModelState(String id, Integer state) {
// 1.1 校验流程模型存在
Model model = getModel(id);
if (model == null) {
throw new BusinessException(BpmResponseEnum.BPM_MODEL_NOT_EXIST);
}
// 1.2 校验流程定义存在
ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
if (definition == null) {
throw new BusinessException(BpmResponseEnum.PROCESS_DEFINITION_NOT_EXISTS);
}
// 2. 更新状态
processDefinitionService.updateProcessDefinitionState(definition.getId(), state);
}
@Override
public BpmnModel getBpmnModelByDefinitionId(String processDefinitionId) {
return repositoryService.getBpmnModel(processDefinitionId);
}
/**
* 校验流程表单已配置
*
* @param metaInfo 流程模型元数据
* @return 表单配置
*/
private BpmForm validateFormConfig(BpmModelMetaInfoRespDTO metaInfo) {
if (metaInfo == null || metaInfo.getFormType() == null) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
// 校验表单存在
if (Objects.equals(metaInfo.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) {
if (metaInfo.getFormId() == null) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
BpmForm form = bpmFormService.getById(metaInfo.getFormId());
if (form == null) {
throw new BusinessException(BpmResponseEnum.FORM_NOT_EXISTS);
}
return form;
} else {
if (StrUtil.isEmpty(metaInfo.getFormCustomCreatePath()) || StrUtil.isEmpty(metaInfo.getFormCustomViewPath())) {
throw new BusinessException(BpmResponseEnum.MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
return null;
}
}
private void saveModelBpmnXml(Model model, String bpmnXml) {
if (StrUtil.isEmpty(bpmnXml)) {
return;
}
repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
}
/**
* 挂起 deploymentId 对应的流程定义
* <p>
* 注意:这里一个 deploymentId 只关联一个流程定义
*
* @param deploymentId 流程发布Id
*/
private void updateProcessDefinitionSuspended(String deploymentId) {
if (StrUtil.isEmpty(deploymentId)) {
return;
}
ProcessDefinition oldDefinition = processDefinitionService.getProcessDefinitionByDeploymentId(deploymentId);
if (oldDefinition == null) {
return;
}
processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
}
private Model getModelByKey(String key) {
return repositoryService.createModelQuery().modelKey(key).singleResult();
}
@Override
public Model getModel(String id) {
return repositoryService.getModel(id);
}
@Override
public byte[] getModelBpmnXML(String id) {
return repositoryService.getModelEditorSource(id);
}
}

View File

@@ -0,0 +1,218 @@
package com.njcn.bpm.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.bpm.enums.BpmnModelConstants;
import com.njcn.bpm.mapper.BpmCategoryMapper;
import com.njcn.bpm.mapper.BpmProcessDefinitionInfoMapper;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.param.BpmProcessDefinitionInfoParam;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import com.njcn.bpm.service.IBpmProcessDefinitionService;
import com.njcn.bpm.utils.BeanUtils;
import com.njcn.bpm.utils.FlowableUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.web.factory.PageFactory;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static com.njcn.bpm.utils.CollectionUtils.addIfNotNull;
import static java.util.Collections.emptyList;
/**
* 流程定义实现
* 主要进行 Flowable {@link ProcessDefinition} 和 {@link Deployment} 的维护
*
* @author yunlongn
* @author ZJQ
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessDefinitionServiceImpl extends ServiceImpl<BpmProcessDefinitionInfoMapper, BpmProcessDefinitionInfo> implements IBpmProcessDefinitionService {
@Resource
private RepositoryService repositoryService;
@Override
public Page<ProcessDefinition> getProcessDefinitionPage(BpmProcessDefinitionInfoParam.BpmProcessDefinitionInfoQueryParam bpmProcessDefinitionInfoQueryParam) {
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
if (StrUtil.isNotBlank(bpmProcessDefinitionInfoQueryParam.getKey())) {
query.processDefinitionKey(bpmProcessDefinitionInfoQueryParam.getKey());
}
// 执行查询
long count = query.count();
if (count == 0) {
return new Page<>();
}
int offset = PageFactory.getPageSize(bpmProcessDefinitionInfoQueryParam) * (PageFactory.getPageNum(bpmProcessDefinitionInfoQueryParam) - 1);
List<ProcessDefinition> processDefinitionList = query.orderByProcessDefinitionVersion().desc()
.listPage(offset, PageFactory.getPageSize(bpmProcessDefinitionInfoQueryParam));
Page<ProcessDefinition> page = new Page<>();
page.setRecords(processDefinitionList);
page.setTotal(count);
page.setSize(PageFactory.getPageSize(bpmProcessDefinitionInfoQueryParam));
page.setCurrent(PageFactory.getPageNum(bpmProcessDefinitionInfoQueryParam));
return page;
}
@Override
public ProcessDefinition getProcessDefinition(String id) {
return repositoryService.getProcessDefinition(id);
}
@Override
public List<ProcessDefinition> getProcessDefinitionList(Set<String> ids) {
return repositoryService.createProcessDefinitionQuery().processDefinitionIds(ids).list();
}
@Override
public ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId) {
if (StrUtil.isEmpty(deploymentId)) {
return null;
}
return repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
}
@Override
public List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds) {
if (CollUtil.isEmpty(deploymentIds)) {
return emptyList();
}
return repositoryService.createProcessDefinitionQuery().deploymentIds(deploymentIds).list();
}
@Override
public ProcessDefinition getActiveProcessDefinition(String key) {
return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
}
@Override
public List<Deployment> getDeploymentList(Set<String> ids) {
if (CollUtil.isEmpty(ids)) {
return emptyList();
}
List<Deployment> list = new ArrayList<>(ids.size());
for (String id : ids) {
addIfNotNull(list, getDeployment(id));
}
return list;
}
@Override
public Deployment getDeployment(String id) {
if (StrUtil.isEmpty(id)) {
return null;
}
return repositoryService.createDeploymentQuery().deploymentId(id).singleResult();
}
@Override
public String createProcessDefinition(Model model, BpmModelMetaInfoRespDTO modelMetaInfo,
byte[] bpmnBytes, BpmForm form) {
// 创建 Deployment 部署
Deployment deploy = repositoryService.createDeployment()
.key(model.getKey()).name(model.getName()).category(model.getCategory())
.addBytes(model.getKey() + BpmnModelConstants.BPMN_FILE_SUFFIX, bpmnBytes)
.tenantId(FlowableUtils.getTenantId())
.disableSchemaValidation() // 禁用 XML Schema 验证,因为有自定义的属性
.deploy();
// 设置 ProcessDefinition 的 category 分类
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deploy.getId()).singleResult();
repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory());
// 注意 1ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
// 注意 2目前该项目的设计上需要保证 Model、Deployment、ProcessDefinition 使用相同的 key保证关联性。
// 否则,会导致 ProcessDefinition 的分页无法查询到。
if (!Objects.equals(definition.getKey(), model.getKey())) {
throw new BusinessException("流程定义的标识期望是(" + model.getKey() + "),当前是(" + definition.getKey() + "),请修改 BPMN 流程图");
}
if (!Objects.equals(definition.getName(), model.getName())) {
throw new BusinessException("流程定义的名字期望是(" + model.getName() + "),当前是(" + definition.getName() + "),请修改 BPMN 流程图");
}
// 插入拓展表
BpmProcessDefinitionInfo definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfo.class);
definitionDO.setModelId(model.getId());
definitionDO.setProcessDefinitionId(definition.getId());
if (form != null) {
definitionDO.setFormFields(form.getFields());
definitionDO.setFormConf(form.getConf());
}
this.baseMapper.insert(definitionDO);
return definition.getId();
}
@Override
public void updateProcessDefinitionState(String id, Integer state) {
// 激活
if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
repositoryService.activateProcessDefinitionById(id, false, null);
return;
}
// 挂起
if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
// suspendProcessInstances = false进行中的任务不进行挂起。
// 原因:只要新的流程不允许发起即可,老流程继续可以执行。
repositoryService.suspendProcessDefinitionById(id, false, null);
return;
}
log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
}
@Override
public BpmnModel getProcessDefinitionBpmnModel(String id) {
return repositoryService.getBpmnModel(id);
}
@Override
public BpmProcessDefinitionInfo getProcessDefinitionInfo(String id) {
LambdaQueryWrapper<BpmProcessDefinitionInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(BpmProcessDefinitionInfo::getProcessDefinitionId, id);
return this.baseMapper.selectOne(lambdaQueryWrapper);
}
@Override
public List<BpmProcessDefinitionInfo> getProcessDefinitionInfoList(Collection<String> ids) {
LambdaQueryWrapper<BpmProcessDefinitionInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(BpmProcessDefinitionInfo::getProcessDefinitionId, ids)
.eq(BpmProcessDefinitionInfo::getState, DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(lambdaQueryWrapper);
}
@Override
public List<ProcessDefinition> getProcessDefinitionListBySuspensionState(Integer suspensionState) {
// 拼接查询条件
ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), suspensionState)) {
query.suspended();
} else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), suspensionState)) {
query.active();
}
// 执行查询
query.processDefinitionTenantId(FlowableUtils.getTenantId());
return query.list();
}
}

View File

@@ -0,0 +1,139 @@
package com.njcn.bpm.service.task;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.njcn.bpm.utils.CollectionUtils.convertMap;
/**
* 流程实例 Service 接口
*
* @author 芋道源码
*/
public interface IBpmProcessInstanceService {
/**
* 获得流程实例
*
* @param id 流程实例的编号
* @return 流程实例
*/
ProcessInstance getProcessInstance(String id);
//
// /**
// * 获得流程实例列表
// *
// * @param ids 流程实例的编号集合
// * @return 流程实例列表
// */
// List<ProcessInstance> getProcessInstances(Set<String> ids);
//
// /**
// * 获得流程实例 Map
// *
// * @param ids 流程实例的编号集合
// * @return 流程实例列表 Map
// */
// default Map<String, ProcessInstance> getProcessInstanceMap(Set<String> ids) {
// return convertMap(getProcessInstances(ids), ProcessInstance::getProcessInstanceId);
// }
//
// /**
// * 获得历史的流程实例
// *
// * @param id 流程实例的编号
// * @return 历史的流程实例
// */
// HistoricProcessInstance getHistoricProcessInstance(String id);
//
// /**
// * 获得历史的流程实例列表
// *
// * @param ids 流程实例的编号集合
// * @return 历史的流程实例列表
// */
// List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids);
//
// /**
// * 获得历史的流程实例 Map
// *
// * @param ids 流程实例的编号集合
// * @return 历史的流程实例列表 Map
// */
// default Map<String, HistoricProcessInstance> getHistoricProcessInstanceMap(Set<String> ids) {
// return convertMap(getHistoricProcessInstances(ids), HistoricProcessInstance::getId);
// }
//
// /**
// * 获得流程实例的分页
// *
// * @param userId 用户编号
// * @param pageReqVO 分页请求
// * @return 流程实例的分页
// */
// PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId, @Valid BpmProcessInstancePageReqVO pageReqVO);
//
// /**
// * 创建流程实例(提供给前端)
// *
// * @param userId 用户编号
// * @param createReqVO 创建信息
// * @return 实例的编号
// */
// String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO);
//
// /**
// * 创建流程实例(提供给内部)
// *
// * @param userId 用户编号
// * @param createReqDTO 创建信息
// * @return 实例的编号
// */
// String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO);
//
// /**
// * 发起人取消流程实例
// *
// * @param userId 用户编号
// * @param cancelReqVO 取消信息
// */
// void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO);
//
// /**
// * 管理员取消流程实例
// *
// * @param userId 用户编号
// * @param cancelReqVO 取消信息
// */
// void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO);
//
// /**
// * 更新 ProcessInstance 拓展记录为取消
// *
// * @param event 流程取消事件
// */
// void updateProcessInstanceWhenCancel(FlowableCancelledEvent event);
//
// /**
// * 更新 ProcessInstance 拓展记录为完成
// *
// * @param instance 流程任务
// */
// void updateProcessInstanceWhenApprove(ProcessInstance instance);
//
// /**
// * 更新 ProcessInstance 拓展记录为不通过
// *
// * @param id 流程编号
// * @param reason 理由。例如说,审批不通过时,需要传递该值
// */
// void updateProcessInstanceReject(String id, String reason);
}

View File

@@ -0,0 +1,292 @@
package com.njcn.bpm.service.task.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.bpm.service.task.IBpmProcessInstanceService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.event.FlowableCancelledEvent;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
/**
* 流程实例 Service 实现类
*
* ProcessDefinition & ProcessInstance & Execution & Task 的关系:
* 1. <a href="https://blog.csdn.net/bobozai86/article/details/105210414" />
*
* HistoricProcessInstance & ProcessInstance 的关系:
* 1. <a href=" https://my.oschina.net/843294669/blog/71902" />
*
* 简单来说,前者 = 历史 + 运行中的流程实例,后者仅是运行中的流程实例
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements IBpmProcessInstanceService {
@Resource
private RuntimeService runtimeService;
@Resource
private HistoryService historyService;
// @Resource
// private BpmProcessDefinitionService processDefinitionService;
// @Resource
// private BpmMessageService messageService;
//
// @Resource
// private AdminUserApi adminUserApi;
//
// @Resource
// private BpmProcessInstanceEventPublisher processInstanceEventPublisher;
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery()
.includeProcessVariables()
.processInstanceId(id)
.singleResult();
}
//
// @Override
// public List<ProcessInstance> getProcessInstances(Set<String> ids) {
// return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
// }
//
// @Override
// public HistoricProcessInstance getHistoricProcessInstance(String id) {
// return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).includeProcessVariables().singleResult();
// }
//
// @Override
// public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
// return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
// }
//
// @Override
// public PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
// BpmProcessInstancePageReqVO pageReqVO) {
// // 通过 BpmProcessInstanceExtDO 表,先查询到对应的分页
// HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
// .includeProcessVariables()
// .processInstanceTenantId(FlowableUtils.getTenantId())
// .orderByProcessInstanceStartTime().desc();
// if (userId != null) { // 【我的流程】菜单时,需要传递该字段
// processInstanceQuery.startedBy(String.valueOf(userId));
// } else if (pageReqVO.getStartUserId() != null) { // 【管理流程】菜单时,才会传递该字段
// processInstanceQuery.startedBy(String.valueOf(pageReqVO.getStartUserId()));
// }
// if (StrUtil.isNotEmpty(pageReqVO.getName())) {
// processInstanceQuery.processInstanceNameLike("%" + pageReqVO.getName() + "%");
// }
// if (StrUtil.isNotEmpty(pageReqVO.getProcessDefinitionId())) {
// processInstanceQuery.processDefinitionId("%" + pageReqVO.getProcessDefinitionId() + "%");
// }
// if (StrUtil.isNotEmpty(pageReqVO.getCategory())) {
// processInstanceQuery.processDefinitionCategory(pageReqVO.getCategory());
// }
// if (pageReqVO.getStatus() != null) {
// processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, pageReqVO.getStatus());
// }
// if (ArrayUtil.isNotEmpty(pageReqVO.getCreateTime())) {
// processInstanceQuery.startedAfter(DateUtils.of(pageReqVO.getCreateTime()[0]));
// processInstanceQuery.startedBefore(DateUtils.of(pageReqVO.getCreateTime()[1]));
// }
// // 查询数量
// long processInstanceCount = processInstanceQuery.count();
// if (processInstanceCount == 0) {
// return PageResult.empty(processInstanceCount);
// }
// // 查询列表
// List<HistoricProcessInstance> processInstanceList = processInstanceQuery.listPage(PageUtils.getStart(pageReqVO), pageReqVO.getPageSize());
// return new PageResult<>(processInstanceList, processInstanceCount);
// }
//
// @Override
// @Transactional(rollbackFor = Exception.class)
// public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) {
// // 获得流程定义
// ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
// // 发起流程
// return createProcessInstance0(userId, definition, createReqVO.getVariables(), null,
// createReqVO.getStartUserSelectAssignees());
// }
//
// @Override
// public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {
// // 获得流程定义
// ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
// // 发起流程
// return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey(),
// createReqDTO.getStartUserSelectAssignees());
// }
//
// private String createProcessInstance0(Long userId, ProcessDefinition definition,
// Map<String, Object> variables, String businessKey,
// Map<String, List<Long>> startUserSelectAssignees) {
// // 1.1 校验流程定义
// if (definition == null) {
// throw exception(PROCESS_DEFINITION_NOT_EXISTS);
// }
// if (definition.isSuspended()) {
// throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
// }
// // 1.2 校验发起人自选审批人
// validateStartUserSelectAssignees(definition, startUserSelectAssignees);
//
// // 2. 创建流程实例
// if (variables == null) {
// variables = new HashMap<>();
// }
// FlowableUtils.filterProcessInstanceFormVariable(variables); // 过滤一下,避免 ProcessInstance 系统级的变量被占用
// variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, // 流程实例状态:审批中
// BpmProcessInstanceStatusEnum.RUNNING.getStatus());
// if (CollUtil.isNotEmpty(startUserSelectAssignees)) {
// variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES, startUserSelectAssignees);
// }
// ProcessInstance instance = runtimeService.createProcessInstanceBuilder()
// .processDefinitionId(definition.getId())
// .businessKey(businessKey)
// .name(definition.getName().trim())
// .variables(variables)
// .start();
// return instance.getId();
// }
//
// private void validateStartUserSelectAssignees(ProcessDefinition definition, Map<String, List<Long>> startUserSelectAssignees) {
// // 1. 获得发起人自选审批人的 UserTask 列表
// BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(definition.getId());
// List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
// if (CollUtil.isEmpty(userTaskList)) {
// return;
// }
//
// // 2. 校验发起人自选审批人的 UserTask 是否都配置了
// userTaskList.forEach(userTask -> {
// List<Long> assignees = startUserSelectAssignees != null ? startUserSelectAssignees.get(userTask.getId()) : null;
// if (CollUtil.isEmpty(assignees)) {
// throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG, userTask.getName());
// }
// Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assignees);
// assignees.forEach(assignee -> {
// if (userMap.get(assignee) == null) {
// throw exception(PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS, userTask.getName(), assignee);
// }
// });
// });
// }
//
// @Override
// public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
// // 1.1 校验流程实例存在
// ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
// if (instance == null) {
// throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
// }
// // 1.2 只能取消自己的
// if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
// throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
// }
//
// // 2. 通过删除流程实例,实现流程实例的取消,
// // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。
// deleteProcessInstance(cancelReqVO.getId(),
// BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_START_USER.format(cancelReqVO.getReason()));
//
// // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法
// }
//
// @Override
// public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {
// // 1.1 校验流程实例存在
// ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
// if (instance == null) {
// throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
// }
// // 1.2 管理员取消,不用校验是否为自己的
// AdminUserRespDTO user = adminUserApi.getUser(userId);
//
// // 2. 通过删除流程实例,实现流程实例的取消,
// // 删除流程实例,正则执行任务 ACT_RU_TASK. 任务会被删除。
// deleteProcessInstance(cancelReqVO.getId(),
// BpmDeleteReasonEnum.CANCEL_PROCESS_INSTANCE_BY_ADMIN.format(user.getNickname(), cancelReqVO.getReason()));
//
// // 3. 进一步的处理,交给 updateProcessInstanceCancel 方法
// }
//
// @Override
// public void updateProcessInstanceWhenCancel(FlowableCancelledEvent event) {
// // 1. 判断是否为 Reject 不通过。如果是,则不进行更新.
// // 因为updateProcessInstanceReject 方法(审批不通过),已经进行更新了
// if (BpmDeleteReasonEnum.isRejectReason((String) event.getCause())) {
// return;
// }
//
// // 2. 更新流程实例 status
// runtimeService.setVariable(event.getProcessInstanceId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
// BpmProcessInstanceStatusEnum.CANCEL.getStatus());
//
// // 3. 发送流程实例的状态事件
// // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
// HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
// // 发送流程实例的状态事件
// processInstanceEventPublisher.sendProcessInstanceResultEvent(
// BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.CANCEL.getStatus()));
// }
//
// @Override
// public void updateProcessInstanceWhenApprove(ProcessInstance instance) {
// // 1. 更新流程实例 status
// runtimeService.setVariable(instance.getId(), BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
// BpmProcessInstanceStatusEnum.APPROVE.getStatus());
//
// // 2. 发送流程被【通过】的消息
// messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));
//
// // 3. 发送流程实例的状态事件
// // 注意:此时如果去查询 ProcessInstance 的话,字段是不全的,所以去查询了 HistoricProcessInstance
// HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
// processInstanceEventPublisher.sendProcessInstanceResultEvent(
// BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.APPROVE.getStatus()));
// }
//
// @Override
// @Transactional(rollbackFor = Exception.class)
// public void updateProcessInstanceReject(String id, String reason) {
// // 1. 更新流程实例 status
// runtimeService.setVariable(id, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.REJECT.getStatus());
//
// // 2. 删除流程实例,以实现驳回任务时,取消整个审批流程
// ProcessInstance processInstance = getProcessInstance(id);
// deleteProcessInstance(id, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));
//
// // 3. 发送流程被【不通过】的消息
// messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(processInstance, reason));
//
// // 4. 发送流程实例的状态事件
// processInstanceEventPublisher.sendProcessInstanceResultEvent(
// BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, processInstance, BpmProcessInstanceStatusEnum.REJECT.getStatus()));
// }
//
// private void deleteProcessInstance(String id, String reason) {
// runtimeService.deleteProcessInstance(id, reason);
// }
}

View File

@@ -0,0 +1,113 @@
package com.njcn.bpm.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.google.common.annotations.VisibleForTesting;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import com.njcn.bpm.utils.BpmnModelUtils;
import com.njcn.bpm.utils.CollectionUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.user.api.UserFeignClient;
import com.njcn.user.pojo.po.User;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @author 芋道源码
*/
@Slf4j
public class BpmTaskCandidateInvoker {
private final Map<BpmTaskCandidateStrategyEnum, IBpmTaskCandidateStrategy> strategyMap = new HashMap<>();
private final UserFeignClient adminUserApi;
public BpmTaskCandidateInvoker(List<IBpmTaskCandidateStrategy> strategyList,
UserFeignClient adminUserApi) {
strategyList.forEach(strategy -> {
IBpmTaskCandidateStrategy oldStrategy = strategyMap.put(strategy.getStrategy(), strategy);
Assert.isNull(oldStrategy, "策略(%s) 重复", strategy.getStrategy());
});
this.adminUserApi = adminUserApi;
}
/**
* 校验流程模型的任务分配规则全部都配置了
* 目的:如果有规则未配置,会导致流程任务找不到负责人,进而流程无法进行下去!
*
* @param bpmnBytes BPMN XML
*/
public void validateBpmnConfig(byte[] bpmnBytes) {
BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
assert bpmnModel != null;
List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
// 遍历所有的 UserTask校验审批人配置
userTaskList.forEach(userTask -> {
// 1. 非空校验
Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
String param = BpmnModelUtils.parseCandidateParam(userTask);
if (strategy == null) {
throw new BusinessException("部署流程失败原因BPMN 流程图中,用户任务(" + userTask.getName() + ")的名字不存在");
}
IBpmTaskCandidateStrategy candidateStrategy = getCandidateStrategy(strategy);
if (candidateStrategy.isParamRequired() && StrUtil.isBlank(param)) {
throw new BusinessException("部署流程失败,原因:用户任务(" + userTask.getName() + ")未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
}
// 2. 具体策略校验
getCandidateStrategy(strategy).validateParam(param);
});
}
/**
* 计算任务的候选人
*
* @param execution 执行任务
* @return 用户编号集合
*/
public List<String> calculateUsers(DelegateExecution execution) {
Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
// 1.1 计算任务的候选人
List<String> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
// 1.2 移除被禁用的用户
removeDisableUsers(userIds);
// 2. 校验是否有候选人
if (CollUtil.isEmpty(userIds)) {
log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(),
execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param);
throw new BusinessException("操作失败,原因:找不到任务的审批人!");
}
return userIds;
}
@VisibleForTesting
void removeDisableUsers(List<String> assigneeUserIds) {
if (CollUtil.isEmpty(assigneeUserIds)) {
return;
}
List<User> users = adminUserApi.getUserByIdList(assigneeUserIds).getData();
Map<String, User> userMap = CollectionUtils.convertMap(users, User::getId);
assigneeUserIds.removeIf(id -> {
User user = userMap.get(id);
return user == null || !DataStateEnum.ENABLE.getCode().equals(user.getState());
});
}
private IBpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
IBpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum);
Assert.notNull(strategyObj, "策略(%s) 不存在", strategy);
return strategyObj;
}
}

View File

@@ -0,0 +1,44 @@
package com.njcn.bpm.strategy;
import com.njcn.user.api.UserFeignClient;
import com.njcn.bpm.utils.StrUtils;
import org.flowable.engine.delegate.DelegateExecution;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
/**
* 角色 {@link IBpmTaskCandidateStrategy} 实现类
*
* @author kyle
*/
@Component
public class BpmTaskCandidateRoleStrategy implements IBpmTaskCandidateStrategy {
@Resource
private UserFeignClient userFeignClient;
// @Resource
// private PermissionApi permissionApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.ROLE;
}
@Override
public void validateParam(String param) {
// Set<Long> roleIds = StrUtils.splitToLongSet(param);
// roleApi.validRoleList(roleIds);
}
@Override
public List<String> calculateUsers(DelegateExecution execution, String param) {
List<String> roleIds = StrUtils.splitToStringList(param);
return userFeignClient.getUserIdByRoleId(roleIds).getData();
}
}

View File

@@ -0,0 +1,74 @@
package com.njcn.bpm.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import com.njcn.bpm.service.task.IBpmProcessInstanceService;
import com.njcn.bpm.utils.BpmnModelUtils;
import com.njcn.bpm.utils.FlowableUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
*
* @author 芋道源码
*/
@Component
public class BpmTaskCandidateStartUserSelectStrategy implements IBpmTaskCandidateStrategy {
@Resource
@Lazy // 延迟加载,避免循环依赖
private IBpmProcessInstanceService processInstanceService;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
}
@Override
public void validateParam(String param) {}
@Override
public List<String> calculateUsers(DelegateExecution execution, String param) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
Map<String, List<String>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空",
execution.getProcessInstanceId());
// 获得审批人
List<String> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
return new ArrayList<>(assignees);
}
@Override
public boolean isParamRequired() {
return false;
}
/**
* 获得发起人自选审批人的 UserTask 列表
*
* @param bpmnModel BPMN 模型
* @return UserTask 列表
*/
public static List<UserTask> getStartUserSelectUserTaskList(BpmnModel bpmnModel) {
if (bpmnModel == null) {
return null;
}
List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
if (CollUtil.isEmpty(userTaskList)) {
return null;
}
userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask),
BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
return userTaskList;
}
}

View File

@@ -0,0 +1,39 @@
package com.njcn.bpm.strategy;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import com.njcn.bpm.utils.StrUtils;
import com.njcn.user.api.UserFeignClient;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 用户 {@link IBpmTaskCandidateStrategy} 实现类
*
* @author kyle
*/
@Component
public class BpmTaskCandidateUserStrategy implements IBpmTaskCandidateStrategy {
@Resource
private UserFeignClient adminUserApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.USER;
}
@Override
public void validateParam(String param) {
// adminUserApi.validateUserList(StrUtils.splitToLongSet(param));
//暂时默认均有效后期可以加强管理todo...
}
@Override
public List<String> calculateUsers(DelegateExecution execution, String param) {
return StrUtils.splitToStringList(param);
}
}

View File

@@ -0,0 +1,49 @@
package com.njcn.bpm.strategy;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.engine.delegate.DelegateExecution;
import java.util.List;
import java.util.Set;
/**
* BPM 任务的候选人的策略接口
*
* 例如说:分配审批人
*
* @author 芋道源码
*/
public interface IBpmTaskCandidateStrategy {
/**
* 对应策略
*
* @return 策略
*/
BpmTaskCandidateStrategyEnum getStrategy();
/**
* 校验参数
*
* @param param 参数
*/
void validateParam(String param);
/**
* 基于执行任务,获得任务的候选用户们
*
* @param execution 执行任务
* @return 用户编号集合
*/
List<String> calculateUsers(DelegateExecution execution, String param);
/**
* 是否一定要输入参数
*
* @return 是否
*/
default boolean isParamRequired() {
return true;
}
}

View File

@@ -0,0 +1,151 @@
package com.njcn.bpm.utils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.enums.BpmModelFormTypeEnum;
import com.njcn.bpm.pojo.dto.BpmModelMetaInfoRespDTO;
import com.njcn.bpm.pojo.dto.PageResult;
import com.njcn.bpm.pojo.param.BpmModelParam;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import com.njcn.bpm.pojo.vo.BpmModelRespVO;
import com.njcn.bpm.pojo.vo.BpmProcessDefinitionInfoVO;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 流程模型 Convert
*
* @author yunlongn
*/
@Mapper
public interface BpmModelConvert {
BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class);
default Page<BpmModelRespVO> buildModelPage(Page<Model> pageResult,
Map<String, BpmForm> formMap,
Map<String, BpmCategory> categoryMap, Map<String, Deployment> deploymentMap,
Map<String, ProcessDefinition> processDefinitionMap) {
List<BpmModelRespVO> list = CollectionUtils.convertList(pageResult.getRecords(), model -> {
BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model);
BpmForm form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
BpmCategory category = categoryMap.get(model.getCategory());
Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
return buildModel0(model, metaInfo, form, category, deployment, processDefinition);
});
Page<BpmModelRespVO> voPage = new Page<>();
voPage.setRecords(list);
voPage.setTotal(pageResult.getTotal());
voPage.setSize(pageResult.getSize());
voPage.setCurrent(pageResult.getCurrent());
return voPage;
}
default BpmModelRespVO buildModel(Model model,
byte[] bpmnBytes) {
BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model);
BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null);
if (ArrayUtil.isNotEmpty(bpmnBytes)) {
modelVO.setBpmnXml(new String(bpmnBytes));
}
return modelVO;
}
default BpmModelRespVO buildModel0(Model model,
BpmModelMetaInfoRespDTO metaInfo, BpmForm form, BpmCategory category,
Deployment deployment, ProcessDefinition processDefinition) {
BpmModelRespVO modelRespVO = new BpmModelRespVO();
modelRespVO.setId(model.getId());
modelRespVO.setName(model.getName());
modelRespVO.setKey(model.getKey());
modelRespVO.setCategory(model.getCategory());
modelRespVO.setCreateTime(DateUtils.of(model.getCreateTime()));
// Form
if (metaInfo != null) {
modelRespVO.setFormType(metaInfo.getFormType());
modelRespVO.setFormId(metaInfo.getFormId());
modelRespVO.setFormCustomCreatePath(metaInfo.getFormCustomCreatePath());
modelRespVO.setFormCustomViewPath(metaInfo.getFormCustomViewPath());
modelRespVO.setIcon(metaInfo.getIcon());
modelRespVO.setDescription(metaInfo.getDescription());
}
if (form != null) {
modelRespVO.setFormId(form.getId());
modelRespVO.setFormName(form.getName());
}
// Category
if (category != null) {
modelRespVO.setCategoryName(category.getName());
}
// ProcessDefinition
if (processDefinition != null) {
modelRespVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionInfoVO.class));
modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ?
SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
if (deployment != null) {
modelRespVO.getProcessDefinition().setDeploymentTime(DateUtils.of(deployment.getDeploymentTime()));
}
}
return modelRespVO;
}
default void copyToCreateModel(Model model, BpmModelParam bean) {
model.setName(bean.getName());
model.setKey(bean.getKey());
model.setCategory(bean.getCategory());
// model.setMetaInfo(buildMetaInfoStr(null,
// null, bean.getDescription(),
// null, null, null, null));
//类型暂时写死为表单 todo...
model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model),
bean.getIcon(), bean.getDescription(),
BpmModelFormTypeEnum.NORMAL.getType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath()));
}
default void copyToUpdateModel(Model model, BpmModelParam.BpmModelUpdateParam bean) {
model.setName(bean.getName());
model.setCategory(bean.getCategory());
model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model),
bean.getIcon(), bean.getDescription(),
bean.getFormType(), bean.getFormId(), bean.getFormCustomCreatePath(), bean.getFormCustomViewPath()));
}
default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo,
String icon, String description,
Integer formType, String formId, String formCustomCreatePath, String formCustomViewPath) {
if (metaInfo == null) {
metaInfo = new BpmModelMetaInfoRespDTO();
}
// 只有非空,才进行设置,避免更新时的覆盖
if (StrUtil.isNotEmpty(icon)) {
metaInfo.setIcon(icon);
}
if (StrUtil.isNotEmpty(description)) {
metaInfo.setDescription(description);
}
if (Objects.nonNull(formType)) {
metaInfo.setFormType(formType);
metaInfo.setFormId(formId);
metaInfo.setFormCustomCreatePath(formCustomCreatePath);
metaInfo.setFormCustomViewPath(formCustomViewPath);
}
return JsonUtils.toJsonString(metaInfo);
}
default BpmModelMetaInfoRespDTO buildMetaInfo(Model model) {
return JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
}
}

View File

@@ -0,0 +1,100 @@
package com.njcn.bpm.utils;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.bpm.pojo.po.BpmCategory;
import com.njcn.bpm.pojo.po.BpmForm;
import com.njcn.bpm.pojo.po.BpmProcessDefinitionInfo;
import com.njcn.bpm.pojo.vo.BpmProcessDefinitionInfoVO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
* Bpm 流程定义的 Convert
*
* @author yunlong.li
*/
@Mapper
public interface BpmProcessDefinitionConvert {
BpmProcessDefinitionConvert INSTANCE = Mappers.getMapper(BpmProcessDefinitionConvert.class);
default Page<BpmProcessDefinitionInfoVO> buildProcessDefinitionPage(Page<ProcessDefinition> page,
Map<String, Deployment> deploymentMap,
Map<String, BpmProcessDefinitionInfo> processDefinitionInfoMap,
Map<String, BpmForm> formMap,
Map<String, BpmCategory> categoryMap) {
List<BpmProcessDefinitionInfoVO> list = buildProcessDefinitionList(page.getRecords(), deploymentMap, processDefinitionInfoMap, formMap, categoryMap);
Page<BpmProcessDefinitionInfoVO> voPage = new Page<>();
voPage.setRecords(list);
voPage.setTotal(page.getTotal());
voPage.setSize(page.getSize());
voPage.setCurrent(page.getCurrent());
return voPage;
}
default List<BpmProcessDefinitionInfoVO> buildProcessDefinitionList(List<ProcessDefinition> list,
Map<String, Deployment> deploymentMap,
Map<String, BpmProcessDefinitionInfo> processDefinitionInfoMap,
Map<String, BpmForm> formMap,
Map<String, BpmCategory> categoryMap) {
return CollectionUtils.convertList(list, definition -> {
Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
BpmProcessDefinitionInfo processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfo.class);
BpmForm form = null;
if (processDefinitionInfo != null) {
form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmForm.class);
}
BpmCategory category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategory.class);
return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null);
});
}
default BpmProcessDefinitionInfoVO buildProcessDefinition(ProcessDefinition definition,
Deployment deployment,
BpmProcessDefinitionInfo processDefinitionInfo,
BpmForm form,
BpmCategory category,
BpmnModel bpmnModel,
List<UserTask> startUserSelectUserTaskList) {
BpmProcessDefinitionInfoVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionInfoVO.class);
respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
// Deployment
if (deployment != null) {
respVO.setDeploymentTime(LocalDateTimeUtil.of(deployment.getDeploymentTime()));
}
// BpmProcessDefinitionInfoDO
if (processDefinitionInfo != null) {
copyTo(processDefinitionInfo, respVO);
// Form
if (form != null) {
respVO.setFormName(form.getName());
}
}
// Category
if (category != null) {
respVO.setCategoryName(category.getName());
}
// BpmnModel
if (bpmnModel != null) {
respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionInfoVO.UserTask.class));
}
return respVO;
}
@Mapping(source = "from.id", target = "to.id", ignore = true)
void copyTo(BpmProcessDefinitionInfo from, @MappingTarget BpmProcessDefinitionInfoVO to);
}

View File

@@ -0,0 +1,331 @@
package com.njcn.bpm.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import com.njcn.bpm.enums.BpmnModelConstants;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 流程模型转操作工具类
*/
public class BpmnModelUtils {
public static Integer parseCandidateStrategy(FlowElement userTask) {
return NumberUtils.parseInt(userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
}
public static String parseCandidateParam(FlowElement userTask) {
return userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
}
/**
* 根据节点,获取入口连线
*
* @param source 起始节点
* @return 入口连线列表
*/
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getIncomingFlows();
}
return new ArrayList<>();
}
/**
* 根据节点,获取出口连线
*
* @param source 起始节点
* @return 出口连线列表
*/
public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) {
if (source instanceof FlowNode) {
return ((FlowNode) source).getOutgoingFlows();
}
return new ArrayList<>();
}
/**
* 获取流程元素信息
*
* @param model bpmnModel 对象
* @param flowElementId 元素 ID
* @return 元素信息
*/
public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
Process process = model.getMainProcess();
return process.getFlowElement(flowElementId);
}
/**
* 获得 BPMN 流程中,指定的元素们
*
* @param model 模型
* @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等
* @return 元素们
*/
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
return result;
}
public static StartEvent getStartEvent(BpmnModel model) {
Process process = model.getMainProcess();
// 从 initialFlowElement 找
FlowElement startElement = process.getInitialFlowElement();
if (startElement instanceof StartEvent) {
return (StartEvent) startElement;
}
// 从 flowElementList 找
return (StartEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof StartEvent);
}
public static BpmnModel getBpmnModel(byte[] bpmnBytes) {
if (ArrayUtil.isEmpty(bpmnBytes)) {
return null;
}
BpmnXMLConverter converter = new BpmnXMLConverter();
// 补充说明:由于在 Flowable 中自定义了属性,所以 validateSchema 传递 false
return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), false, false);
}
public static String getBpmnXml(BpmnModel model) {
if (model == null) {
return null;
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return new String(converter.convertToXML(model));
}
// ========== 遍历相关的方法 ==========
/**
* 找到 source 节点之前的所有用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 已找到的用户任务节点
* @return 用户任务节点 数组
*/
public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList);
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 类型为用户节点,则新增父级节点
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement());
}
// 类型为子流程,则添加子流程开始节点出口处相连的节点
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) {
// 获取子流程用户任务节点
List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
}
}
// 继续迭代
userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代获取子流程用户任务节点
*
* @param source 起始节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 用户任务节点
*/
public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList);
}
return userTaskList;
}
/**
* 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行
* 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况
*
* @param source 起始节点
* @param target 目标节点
* @param visitedElements 已经经过的连线的 ID用于判断线路是否重复
* @return 结果
*/
public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
// 不能是开始事件和子流程
if (source instanceof StartEvent && isInEventSubprocess(source)) {
return false;
}
// 根据类型,获取入口连线
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source);
if (CollUtil.isEmpty(sequenceFlows)) {
return true;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (visitedElements.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
visitedElements.add(sequenceFlow.getId());
// 这条线路存在目标节点,这条线路完成,进入下个线路
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement();
if (target.getId().equals(sourceFlowElement.getId())) {
continue;
}
// 如果目标节点为并行网关,则不继续
if (sourceFlowElement instanceof ParallelGateway) {
return false;
}
// 否则就继续迭代
if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) {
return false;
}
}
return true;
}
/**
* 判断当前节点是否属于不同的子流程
*
* @param flowElement 被判断的节点
* @return true 表示属于子流程
*/
private static boolean isInEventSubprocess(FlowElement flowElement) {
FlowElementsContainer flowElementsContainer = flowElement.getParentContainer();
while (flowElementsContainer != null) {
if (flowElementsContainer instanceof EventSubProcess) {
return true;
}
if (flowElementsContainer instanceof FlowElement) {
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer();
} else {
flowElementsContainer = null;
}
}
return false;
}
/**
* 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找
*
* @param source 起始节点
* @param runTaskKeyList 正在运行的任务 Key用于校验任务节点是否是正在运行的节点
* @param hasSequenceFlow 已经经过的连线的 ID用于判断线路是否重复
* @param userTaskList 需要撤回的用户任务列表
* @return 子级任务节点列表
*/
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList,
Set<String> hasSequenceFlow, List<UserTask> userTaskList) {
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow;
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList;
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代
if (source instanceof StartEvent && source.getSubProcess() != null) {
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
// 根据类型,获取出口连线
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source);
if (sequenceFlows == null) {
return userTaskList;
}
// 循环找到目标元素
for (SequenceFlow sequenceFlow : sequenceFlows) {
// 如果发现连线重复,说明循环了,跳过这个循环
if (hasSequenceFlow.contains(sequenceFlow.getId())) {
continue;
}
// 添加已经走过的连线
hasSequenceFlow.add(sequenceFlow.getId());
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加
if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) {
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement());
continue;
}
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) {
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null);
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续
if (CollUtil.isNotEmpty(childUserTaskList)) {
userTaskList.addAll(childUserTaskList);
continue;
}
}
// 继续迭代
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList);
}
return userTaskList;
}
}

View File

@@ -0,0 +1,178 @@
package com.njcn.bpm.utils;
import com.njcn.bpm.enums.BpmConstants;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.TaskInfo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Flowable 相关的工具方法
*
* @author 芋道源码
*/
public class FlowableUtils {
// ========== User 相关的工具方法 ==========
public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId));
}
public static void clearAuthenticatedUserId() {
Authentication.setAuthenticatedUserId(null);
}
public static String getTenantId() {
return ProcessEngineConfiguration.NO_TENANT_ID;
}
// ========== Execution 相关的工具方法 ==========
/**
* 格式化多实例(并签、或签)的 collectionVariable 变量(多实例对应的多审批人列表)
*
* @param activityId 活动编号
* @return collectionVariable 变量
*/
public static String formatExecutionCollectionVariable(String activityId) {
return activityId + "_assignees";
}
/**
* 格式化多实例(并签、或签)的 collectionElementVariable 变量(当前实例对应的一个审批人)
*
* @param activityId 活动编号
* @return collectionElementVariable 变量
*/
public static String formatExecutionCollectionElementVariable(String activityId) {
return activityId + "_assignee";
}
// ========== ProcessInstance 相关的工具方法 ==========
public static Integer getProcessInstanceStatus(ProcessInstance processInstance) {
return getProcessInstanceStatus(processInstance.getProcessVariables());
}
public static Integer getProcessInstanceStatus(HistoricProcessInstance processInstance) {
return getProcessInstanceStatus(processInstance.getProcessVariables());
}
/**
* 获得流程实例的状态
*
* @param processVariables 流程实例的 variables
* @return 状态
*/
private static Integer getProcessInstanceStatus(Map<String, Object> processVariables) {
return (Integer) processVariables.get(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
}
/**
* 获得流程实例的表单
*
* @param processInstance 流程实例
* @return 表单
*/
public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) {
Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables());
filterProcessInstanceFormVariable(formVariables);
return formVariables;
}
/**
* 过滤流程实例的表单
*
* 为什么要过滤?目前使用 processVariables 存储所有流程实例的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示
*
* @param processVariables 流程实例的 variables
* @return 过滤后的表单
*/
public static Map<String, Object> filterProcessInstanceFormVariable(Map<String, Object> processVariables) {
processVariables.remove(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
return processVariables;
}
/**
* 获得流程实例的发起用户选择的审批人 Map
*
* @param processInstance 流程实例
* @return 发起用户选择的审批人 Map
*/
@SuppressWarnings("unchecked")
public static Map<String, List<String>> getStartUserSelectAssignees(ProcessInstance processInstance) {
return (Map<String, List<String>>) processInstance.getProcessVariables().get(
BpmConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
}
// ========== Task 相关的工具方法 ==========
/**
* 获得任务的状态
*
* @param task 任务
* @return 状态
*/
public static Integer getTaskStatus(TaskInfo task) {
return (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
}
/**
* 获得任务的审批原因
*
* @param task 任务
* @return 审批原因
*/
public static String getTaskReason(TaskInfo task) {
return (String) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON);
}
/**
* 获得任务的表单
*
* @param task 任务
* @return 表单
*/
public static Map<String, Object> getTaskFormVariable(TaskInfo task) {
Map<String, Object> formVariables = new HashMap<>(task.getTaskLocalVariables());
filterTaskFormVariable(formVariables);
return formVariables;
}
/**
* 过滤任务的表单
*
* 为什么要过滤?目前使用 taskLocalVariables 存储所有任务的拓展字段,需要过滤掉一部分的系统字段,从而实现表单的展示
*
* @param taskLocalVariables 任务的 taskLocalVariables
* @return 过滤后的表单
*/
public static Map<String, Object> filterTaskFormVariable(Map<String, Object> taskLocalVariables) {
taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_STATUS);
taskLocalVariables.remove(BpmConstants.TASK_VARIABLE_REASON);
return taskLocalVariables;
}
// ========== Expression 相关的工具方法 ==========
public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
assert processEngineConfiguration != null;
ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
assert expressionManager != null;
Expression expression = expressionManager.createExpression(expressionString);
return expression.getValue(variableContainer);
}
}

View File

@@ -0,0 +1,202 @@
package com.njcn.bpm.utils;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
/**
* JSON 工具类
*
* @author 芋道源码
*/
@Slf4j
public class JsonUtils {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化
}
/**
* 初始化 objectMapper 属性
* <p>
* 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
*
* @param objectMapper ObjectMapper 对象
*/
public static void init(ObjectMapper objectMapper) {
JsonUtils.objectMapper = objectMapper;
}
@SneakyThrows
public static String toJsonString(Object object) {
return objectMapper.writeValueAsString(object);
}
@SneakyThrows
public static byte[] toJsonByte(Object object) {
return objectMapper.writeValueAsBytes(object);
}
@SneakyThrows
public static String toJsonPrettyString(Object object) {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}
public static <T> T parseObject(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, Type type) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 将字符串解析成指定类型的对象
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
*
* @param text 字符串
* @param clazz 类型
* @return 对象
*/
public static <T> T parseObject2(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
return JSONUtil.toBean(text, clazz);
}
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
try {
return objectMapper.readValue(bytes, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", bytes, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
*
* @param text 字符串
* @param typeReference 类型引用
* @return 指定类型的对象
*/
public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
return null;
}
}
public static <T> List<T> parseArray(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(String text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(byte[] text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static boolean isJson(String text) {
return JSONUtil.isTypeJSON(text);
}
}

View File

@@ -0,0 +1,83 @@
package com.njcn.bpm.utils;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 字符串工具类
*
* @author 芋道源码
*/
public class StrUtils {
public static String maxLength(CharSequence str, int maxLength) {
return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
}
/**
* 给定字符串是否以任何一个字符串开始
* 给定字符串和数组为空都返回 false
*
* @param str 给定字符串
* @param prefixes 需要检测的开始字符串
* @since 3.0.6
*/
public static boolean startWithAny(String str, Collection<String> prefixes) {
if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
return false;
}
for (CharSequence suffix : prefixes) {
if (StrUtil.startWith(str, suffix, false)) {
return true;
}
}
return false;
}
public static List<Long> splitToLong(String value, CharSequence separator) {
long[] longs = StrUtil.splitToLong(value, separator);
return Arrays.stream(longs).boxed().collect(Collectors.toList());
}
public static Set<Long> splitToLongSet(String value) {
return splitToLongSet(value, StrPool.COMMA);
}
public static Set<Long> splitToLongSet(String value, CharSequence separator) {
long[] longs = StrUtil.splitToLong(value, separator);
return Arrays.stream(longs).boxed().collect(Collectors.toSet());
}
public static List<Integer> splitToInteger(String value, CharSequence separator) {
int[] integers = StrUtil.splitToInt(value, separator);
return Arrays.stream(integers).boxed().collect(Collectors.toList());
}
/**
* 移除字符串中,包含指定字符串的行
*
* @param content 字符串
* @param sequence 包含的字符串
* @return 移除后的字符串
*/
public static String removeLineContains(String content, String sequence) {
if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {
return content;
}
return Arrays.stream(content.split("\n"))
.filter(line -> !line.contains(sequence))
.collect(Collectors.joining("\n"));
}
public static List<String> splitToStringList(String value) {
return StrUtil.split(value, StrPool.COMMA);
}
}

View File

@@ -0,0 +1,69 @@
#当前服务的基本信息
microservice:
ename: @artifactId@
name: '@name@'
version: @version@
sentinel:
url: @sentinel.url@
gateway:
url: @gateway.url@
server:
port: 10321
#feign接口开启服务熔断降级处理
feign:
sentinel:
enabled: true
spring:
application:
name: @artifactId@
#nacos注册中心以及配置中心的指定
cloud:
nacos:
discovery:
ip: @service.server.url@
server-addr: @nacos.url@
namespace: @nacos.namespace@
config:
server-addr: @nacos.url@
namespace: @nacos.namespace@
file-extension: yaml
shared-configs:
- data-id: share-config.yaml
refresh: true
- data-Id: bpm-config.yaml
refresh: true
main:
allow-bean-definition-overriding: true
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
flowable:
database-schema-update: false
db-history-used: true # flowable6 默认 true 生成信息表,无需手动设置
check-process-definitions: false # 设置为 false禁用 /resources/processes 自动部署 BPMN XML 流程
history-level: full # full保存历史数据的最高级别可保存全部流程相关细节包括流程流转各节点参数
async-executor-activate: false
#项目日志的配置
logging:
config: http://@nacos.url@/nacos/v1/cs/configs?tenant=@nacos.namespace@&group=DEFAULT_GROUP&dataId=logback.xml
level:
root: info
#mybatis配置信息
mybatis-plus:
type-aliases-package: com.njcn.bpm.pojo
gw:
url: dwzyywzt-pms3-proxy.com
code: 13B9B47F1E483324E05338297A0A0595
mqtt:
client-id: @artifactId@${random.value}

28
pqs-bpm/pom.xml Normal file
View File

@@ -0,0 +1,28 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>pqs</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>pqs-bpm</artifactId>
<packaging>pom</packaging>
<modules>
<module>bpm-api</module>
<module>bpm-boot</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@@ -21,7 +21,7 @@ public class GenerateCode {
private static final String TARGET_DIR = "D://code";
private static final String DB_URL = "jdbc:mysql://192.168.1.24:13306/pqsinfo_hn";
private static final String DB_URL = "jdbc:mysql://192.168.1.24:13306/pqsinfo_hn_process";
// private static final String DB_URL = "jdbc:oracle:thin:@192.168.1.170:1521:pqsbase";
private static final String USERNAME = "root";
@@ -30,9 +30,9 @@ public class GenerateCode {
public static void main(String[] args) {
List<Module> modules = Stream.of(
new Module("hongawen", "com.njcn.advance.govern", "voltage", Stream.of(
"sg_harmonic_file"
).collect(Collectors.toList()), "")
new Module("hongawen", "com.njcn.process", "workflow", Stream.of(
"wf_deploy_form"
).collect(Collectors.toList()), "wf_")
).collect(Collectors.toList());
generateJavaFile(modules);
}

View File

@@ -34,7 +34,7 @@ import java.util.stream.Stream;
@Slf4j
public class XssRequestWrapper extends HttpServletRequestWrapper {
private final static String[] WHITE_PARAMETER_NAME = {"password", "mxContent", "docContent", "bgImage","fileContent","flowableXml"};
private final static String[] WHITE_PARAMETER_NAME = {"password", "mxContent", "docContent", "bgImage","fileContent","flowableXml","bpmnXml"};
public XssRequestWrapper(HttpServletRequest request) {

View File

@@ -21,16 +21,16 @@
<dependencies>
<!--pq-device-boot和pms-device-boot只能选择一个-->
<!-- <dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>pq-device-boot</artifactId>
<version>${project.version}</version>
</dependency>-->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>pms-device-boot</artifactId>
<version>${project.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.njcn</groupId>-->
<!-- <artifactId>pms-device-boot</artifactId>-->
<!-- <version>${project.version}</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.njcn</groupId>

View File

@@ -134,7 +134,7 @@ public class OracleTerminalExcel implements Serializable {
@Excel(name = "合同号", width = 15)
private String contract;
@Excel(name = "电度功能", replace = {"关闭_0", "开启_1"}, width = 15)
@Excel(name = "电度功能", replace = {"关闭_0", "开启_1","null_0"}, width = 15)
private Integer electroplate;
@Excel(name = "母线", width = 15)

View File

@@ -172,6 +172,13 @@ spring:
filters:
- SwaggerHeaderFilter
- StripPrefix=1
- id: bpm-boot
uri: lb://bpm-boot
predicates:
- Path=/bpm-boot/**
filters:
- SwaggerHeaderFilter
- StripPrefix=1
#项目日志的配置
logging:
config: http://@nacos.url@/nacos/v1/cs/configs?tenant=@nacos.namespace@&group=DEFAULT_GROUP&dataId=logback.xml
@@ -211,7 +218,11 @@ whitelist:
#- /event-boot/**
#- /quality-boot/**
#- /harmonic-prepare/**
#- /process-boot/**
- /process-boot/**
- /bpm-boot/**
- /system-boot/**
- /user-boot/**
- /user-boot/user/listAllUserByDeptId
mqtt:

View File

@@ -31,7 +31,8 @@ spring:
- data-id: share-config.yaml
refresh: true
#数据中心使用
# - data-Id: share-config-datasource-db.yaml
# - data-Id: share-config-datasource-db.yaml
# refresh: true
#PMS使用
- data-Id: share-config-harmonic-db.yaml
refresh: true

View File

@@ -26,8 +26,25 @@ public enum ProcessResponseEnum {
SUPV_PLAN_REPEAT("A00568","监督计划名称已存在"),
NO_UPDATE("A00568","该申请单非新建,驳回状态不准许更新"),
REPEAT_NAME_CATEGORY("A00568","流程表单名称重复"),
REPEAT_CODE_CATEGORY("A00568","流程表单编号重复"),
;
FORM_NOT_EXIST("A00568","流程表单不存在"),
MODEL_NO_EXIST("A00568","流程模型不存在"),
NO_MODEL_XML("A00568","未获取到流程模型设计"),
NO_XML("A00568","请先设计流程图"),
NO_STARTER_MODEL_XML("A00568","开始节点不存在,请检查流程设计是否有误"),
MODEL_HAS_NO_FORM("A00568","请配置流程表单"),
LATEST_VERSION("A00568","当前版本已是最新版!"),
PROCESS_DATASOURCE_MISS("A00568","数据源没找到!"),
;
private final String code;

View File

@@ -30,7 +30,7 @@ public class FlowProcDefDto implements Serializable {
@ApiModelProperty("流程key")
private String flowKey;
@ApiModelProperty("流程分类")
@ApiModelProperty("流程表单")
private String category;
@ApiModelProperty("配置表单名称")

View File

@@ -17,7 +17,7 @@ public class FlowSaveXmlVo implements Serializable {
private String name;
/**
* 流程分类
* 流程表单
*/
private String category;

View File

@@ -38,4 +38,8 @@ public class FlowServiceFactory {
@Resource
protected ProcessEngine processEngine;
// @Resource
// protected FormService formService;
}

View File

@@ -3,11 +3,13 @@ package com.njcn.system.controller;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.dto.SimpleDTO;
import com.njcn.common.pojo.dto.SimpleTreeDTO;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
@@ -60,6 +62,19 @@ public class DictTypeController extends BaseController {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
/**
* 查询所有字典类型数据
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/listAll")
@ApiOperation("查询所有字典类型数据")
public HttpResult<List<DictType>> listAll() {
String methodDescribe = getMethodDescribe("listAll");
LogUtil.njcnDebug(log, "{}", methodDescribe);
List<DictType> dictTypeList = dictTypeService.list(new LambdaQueryWrapper<DictType>().eq(DictType::getState, DataStateEnum.ENABLE.getCode()));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, dictTypeList, methodDescribe);
}
/**
* 新增字典类型

View File

@@ -1,5 +1,6 @@
package com.njcn.user.api;
import cn.hutool.core.lang.tree.Tree;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.ServerInfo;
import com.njcn.common.pojo.enums.common.LogEnum;
@@ -160,4 +161,7 @@ public interface DeptFeignClient {
*/
@GetMapping("/getAllDept")
HttpResult<List<Dept>> getAllDept();
@GetMapping("/orgTreeSelector")
HttpResult<List<Tree<String>>> orgTreeSelector();
}

View File

@@ -1,16 +1,26 @@
package com.njcn.user.api;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.ServerInfo;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.user.api.fallback.UserFeignClientFallbackFactory;
import com.njcn.user.pojo.dto.UserDTO;
import com.njcn.user.pojo.po.User;
import com.njcn.user.pojo.vo.UserVO;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.List;
/**
@@ -18,7 +28,7 @@ import java.util.List;
* @version 1.0.0
* @date 2021年05月08日 15:11
*/
@FeignClient(value = ServerInfo.USER,path = "/user",fallbackFactory = UserFeignClientFallbackFactory.class,contextId = "user")
@FeignClient(value = ServerInfo.USER, path = "/user", fallbackFactory = UserFeignClientFallbackFactory.class, contextId = "user")
public interface UserFeignClient {
/**
@@ -37,10 +47,11 @@ public interface UserFeignClient {
* @return 用户基本信息
*/
@GetMapping("/getUserByPhone/{phone}")
HttpResult<UserDTO> getUserByPhone(@PathVariable("phone")String phone);
HttpResult<UserDTO> getUserByPhone(@PathVariable("phone") String phone);
/**
* 认证后根据用户名判断用户状态
*
* @param loginName 登录名
* @return 校验结果
*/
@@ -58,6 +69,7 @@ public interface UserFeignClient {
/**
* 根据用户id集合查询用户信息
*
* @param ids
* @return
*/
@@ -66,10 +78,11 @@ public interface UserFeignClient {
@PostMapping("/appuserByIdList")
@ApiImplicitParam(name = "ids", value = "用户id集合", required = true)
HttpResult<List<User>> appuserByIdList(@RequestBody List<String> ids);
HttpResult<List<User>> appuserByIdList(@RequestBody List<String> ids);
/**
* 根据部门ids查询接收短信通知的用户信息
*
* @param deptId
* @return
*/
@@ -78,14 +91,24 @@ public interface UserFeignClient {
/**
* 根据部门ids查询用户信息
*
* @param deptId
* @return
*/
@PostMapping("/getUserInfoByDeptIds")
HttpResult<List<User>> getUserInfoByDeptIds(@RequestBody List<String> deptId);
/**
* 根据角色id查询用户id
*
* @param roleId 角色id集合
*/
@PostMapping("/getUserIdByRoleId")
HttpResult<List<String>> getUserIdByRoleId(@RequestBody List<String> roleId);
/**
* 根据角色Code集合查询用户信息
*
* @param roleCode
* @return
*/
@@ -95,4 +118,7 @@ public interface UserFeignClient {
@GetMapping("/getUserById")
HttpResult<UserVO> getUserById(@RequestParam("id") String id);
@PostMapping("/getUserListByIds")
HttpResult<List<User>> getUserListByIds(@RequestBody List<String> ids);
}

View File

@@ -1,5 +1,6 @@
package com.njcn.user.api.fallback;
import cn.hutool.core.lang.tree.Tree;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.pojo.response.HttpResult;
@@ -146,6 +147,12 @@ public class DeptFeignClientFallbackFactory implements FallbackFactory<DeptFeign
log.error("{}异常,降级处理,异常为:{}","获取所有单位:",cause.toString());
throw new BusinessException(finalExceptionEnum);
}
@Override
public HttpResult<List<Tree<String>>> orgTreeSelector() {
log.error("{}异常,降级处理,异常为:{}","获取所有单位树:",cause.toString());
throw new BusinessException(finalExceptionEnum);
}
};
}
}

View File

@@ -1,5 +1,6 @@
package com.njcn.user.api.fallback;
import cn.hutool.json.JSONObject;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.pojo.response.HttpResult;
@@ -86,6 +87,12 @@ public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeign
throw new BusinessException(finalExceptionEnum);
}
@Override
public HttpResult<List<String>> getUserIdByRoleId(List<String> roleId) {
log.error("{}异常,降级处理,异常为:{}","根据角色ids查询用户id",cause.toString());
throw new BusinessException(finalExceptionEnum);
}
@Override
public HttpResult<List<User>> getUserListByRoleCode(String roleCode) {
log.error("{}异常,降级处理,异常为:{}","根据角色Code集合查询用户信息",cause.toString());
@@ -98,6 +105,11 @@ public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeign
throw new BusinessException(finalExceptionEnum);
}
@Override
public HttpResult<List<User>> getUserListByIds(List<String> ids) {
log.error("{}异常,降级处理,异常为:{}","根据用户id集合获取用户集合",cause.toString());
throw new BusinessException(finalExceptionEnum);
}
};
}

View File

@@ -1,7 +1,12 @@
package com.njcn.user.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
@@ -14,6 +19,7 @@ import com.njcn.system.pojo.dto.AreaTreeDTO;
import com.njcn.user.pojo.dto.DeptDTO;
import com.njcn.user.pojo.param.DeptParam;
import com.njcn.user.pojo.po.Dept;
import com.njcn.user.pojo.po.Role;
import com.njcn.user.pojo.vo.DeptAllTreeVO;
import com.njcn.user.pojo.vo.DeptTreeVO;
import com.njcn.user.pojo.vo.DeptVO;
@@ -29,6 +35,7 @@ import springfox.documentation.annotations.ApiIgnore;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* <p>
@@ -155,6 +162,17 @@ public class DeptController extends BaseController {
}
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/getDeptListByIds")
@ApiOperation("根据部门id集合查询部门信息")
@ApiImplicitParam(name = "ids", value = "部门id集合", required = true)
public HttpResult<List<Dept>> getDeptListByIds(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("getDeptListByIds");
List<Dept> users = deptService.list((new LambdaQueryWrapper<Dept>().in(CollUtil.isNotEmpty(ids), Dept::getId, ids)));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
/**
* 判断是否有关联信息
*/
@@ -187,6 +205,19 @@ public class DeptController extends BaseController {
}
/**
* 获取部门树
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/deptTreeSelector")
@ApiOperation("部门信息树")
public HttpResult<List<Tree<String>>> deptTreeSelector() {
String methodDescribe = getMethodDescribe("deptTreeSelector");
List<Tree<String>> result = deptService.orgTreeSelector();
//删除返回失败查不到数据返回空数组兼容治理项目没有部门直接报错的bug
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/existMonitorDeptTree")
@@ -511,6 +542,19 @@ public class DeptController extends BaseController {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
/**
* 获取所有的部门集合
* @author xy
* @date 2023/12/11
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/orgTreeSelector")
@ApiOperation("获取所有单位树")
public HttpResult<List<Tree<String>>> orgTreeSelector() {
String methodDescribe = getMethodDescribe("orgTreeSelector");
List<Tree<String>> result = deptService.orgTreeSelector();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
/*++++++++++++++++++++++++++++++++pms专用+++++++++++++++++++++++++++++++end*/
}

View File

@@ -1,6 +1,8 @@
package com.njcn.user.controller;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
@@ -12,6 +14,7 @@ import com.njcn.common.utils.LogUtil;
import com.njcn.user.pojo.param.DeptParam;
import com.njcn.user.pojo.param.RoleParam;
import com.njcn.user.pojo.po.Role;
import com.njcn.user.pojo.po.User;
import com.njcn.user.pojo.vo.RoleVO;
import com.njcn.user.service.IRoleService;
import io.swagger.annotations.Api;
@@ -114,6 +117,16 @@ public class RoleController extends BaseController {
}
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/getRoleListByIds")
@ApiOperation("根据角色id集合查询角色信息")
@ApiImplicitParam(name = "ids", value = "角色id集合", required = true)
public HttpResult<List<Role>> getRoleListByIds(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("getRoleListByIds");
List<Role> users = roleService.list((new LambdaQueryWrapper<Role>().in(CollUtil.isNotEmpty(ids), Role::getId, ids)));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
/**
* 根据角色id查询相关联的资源和组件
*/
@@ -145,5 +158,29 @@ public class RoleController extends BaseController {
List<Role> result = roleService.selectRoleDetail(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
/**
* 查询所有角色
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/allRoleList")
@ApiOperation("查询所有角色")
public HttpResult<Object> allRoleList() {
String methodDescribe = getMethodDescribe("allRoleList");
List<Role> result = roleService.allRoleList();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
/**
* 查询所有角色
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/simpleList")
@ApiOperation("查询所有角色作为下拉框")
public HttpResult<Object> simpleList() {
String methodDescribe = getMethodDescribe("simpleList");
List<Role> result = roleService.simpleList();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
}

View File

@@ -3,6 +3,8 @@ package com.njcn.user.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
@@ -26,6 +28,7 @@ import com.njcn.user.pojo.dto.UserDTO;
import com.njcn.user.pojo.param.UserInfoParm;
import com.njcn.user.pojo.param.UserParam;
import com.njcn.user.pojo.param.UserPasswordParam;
import com.njcn.user.pojo.po.Role;
import com.njcn.user.pojo.po.User;
import com.njcn.user.pojo.vo.UserVO;
import com.njcn.user.service.IUserService;
@@ -48,6 +51,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* <p>
@@ -194,18 +198,19 @@ public class UserController extends BaseController {
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD)
@PostMapping("/uploadImage")
@ApiOperation("上传头像")
public HttpResult<MinIoUploadResDTO> uploadImage( MultipartFile issuesFile){
public HttpResult<MinIoUploadResDTO> uploadImage(MultipartFile issuesFile) {
String methodDescribe = getMethodDescribe("uploadImage");
String filePath = userService.uploadImage(issuesFile);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,new MinIoUploadResDTO(issuesFile.getOriginalFilename(),filePath), methodDescribe);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, new MinIoUploadResDTO(issuesFile.getOriginalFilename(), filePath), methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPLOAD)
@PostMapping("/getUrl")
@ApiOperation("获取头像url")
public HttpResult<String> getUrl(@RequestParam("headSculpture") String headSculpture){
public HttpResult<String> getUrl(@RequestParam("headSculpture") String headSculpture) {
String methodDescribe = getMethodDescribe("getUrl");
String url = userService.getUrl(headSculpture);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,url, methodDescribe);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, url, methodDescribe);
}
@@ -221,6 +226,16 @@ public class UserController extends BaseController {
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/listAllUserByDeptId")
@ApiOperation("查询部门下所有状态正常的用户")
public HttpResult<List<UserVO>> listAllUserByDeptId(String deptId) {
String methodDescribe = getMethodDescribe("listAllUserByDeptId");
List<UserVO> list = userService.listAllUserByDeptId(deptId);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, list, methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/exportUser")
@ApiOperation("用户数据")
@@ -228,19 +243,18 @@ public class UserController extends BaseController {
public HttpResult<String> exportUser(@RequestBody @Validated UserParam.UserQueryParam queryParam) {
String methodDescribe = getMethodDescribe("exportUser");
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, userService.exportUser(queryParam,methodDescribe), methodDescribe);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, userService.exportUser(queryParam, methodDescribe), methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON,operateType = OperateType.DOWNLOAD)
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DOWNLOAD)
@GetMapping("/exportUserExcel")
@ApiOperation("导出用户数据")
@ApiImplicitParam(name = "filePath", value = "报表路径", required = true)
public void exportUserExcel(String filePath, HttpServletResponse response) {
PoiUtil.exportFileByAbsolutePath(filePath,response);
PoiUtil.exportFileByAbsolutePath(filePath, response);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/checkUserList")
@ApiOperation("审核用户列表")
@@ -337,7 +351,7 @@ public class UserController extends BaseController {
//秘钥先删除再添加
redisUtil.delete(loginName + ip);
// 保存私钥到 redis
redisUtil.saveByKeyWithExpire(loginName + ip, privateKey, 5*60L);
redisUtil.saveByKeyWithExpire(loginName + ip, privateKey, 5 * 60L);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, publicKey, methodDescribe);
}
@@ -356,6 +370,7 @@ public class UserController extends BaseController {
/**
* 密码二次确认
*
* @param password 确认密码
*/
@OperateInfo
@@ -366,7 +381,7 @@ public class UserController extends BaseController {
String methodDescribe = getMethodDescribe("passwordConfirm");
LogUtil.njcnDebug(log, "{},用户输入的密码:{}", methodDescribe, password);
boolean result = userService.passwordConfirm(password);
if (result){
if (result) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
} else {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
@@ -376,7 +391,7 @@ public class UserController extends BaseController {
/**
* 首次登录修改密码
*
* @param userPasswordParam 用户信息
* @param userPasswordParam 用户信息
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
@PutMapping("/updateFirstPassword")
@@ -393,6 +408,7 @@ public class UserController extends BaseController {
/**
* 用于激活处于休眠、锁定、密码过期的用户
*
* @param id 用户ID
* @return
*/
@@ -404,7 +420,7 @@ public class UserController extends BaseController {
String methodDescribe = getMethodDescribe("activateUser");
LogUtil.njcnDebug(log, "{}用户输入的id{}", methodDescribe, id);
boolean result = userService.activateUser(id);
if (result){
if (result) {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
} else {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
@@ -415,13 +431,13 @@ public class UserController extends BaseController {
@PostMapping("/userByIdList")
@ApiOperation("根据用户id集合查询用户信息")
@ApiImplicitParam(name = "ids", value = "用户id集合", required = true)
public HttpResult<List<User>> getUserByIdList(@RequestBody List<String> ids) {
public HttpResult<List<User>> getUserByIdList(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("getUserByIdList");
List<User> users = userService.list((new LambdaQueryWrapper<User>()
.in(CollUtil.isNotEmpty(ids),User::getId,ids)
.in(CollUtil.isNotEmpty(ids), User::getId, ids)
.isNotNull(User::getDeptId)
.ne(User::getDeptId,"")
.ne(User::getState,"0")
.ne(User::getDeptId, "")
.ne(User::getState, "0")
));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
@@ -430,15 +446,24 @@ public class UserController extends BaseController {
@PostMapping("/appuserByIdList")
@ApiOperation("根据用户id集合查询用户信息")
@ApiImplicitParam(name = "ids", value = "用户id集合", required = true)
public HttpResult<List<User>> appuserByIdList(@RequestBody List<String> ids) {
public HttpResult<List<User>> appuserByIdList(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("appuserByIdList");
List<User> users = userService.list((new LambdaQueryWrapper<User>()
.in(CollUtil.isNotEmpty(ids),User::getId,ids)
.in(CollUtil.isNotEmpty(ids), User::getId, ids)
));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/getUserListByIds")
@ApiOperation("根据用户id集合查询用户信息")
@ApiImplicitParam(name = "ids", value = "用户id集合", required = true)
public HttpResult<List<User>> getUserListByIds(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("getUserListByIds");
List<User> users = userService.list((new LambdaQueryWrapper<User>().in(CollUtil.isNotEmpty(ids), User::getId, ids)));
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/getMarketList")
@ApiOperation("获取营销用户列表")
@@ -452,6 +477,7 @@ public class UserController extends BaseController {
/**
* 获取同级部门用户,以及下级部门所有用户
*
* @author cdf
* @date 2023/7/31
*/
@@ -468,6 +494,7 @@ public class UserController extends BaseController {
/**
* 根据部门ids查询用户信息
*
* @param deptId
* @return
*/
@@ -478,9 +505,9 @@ public class UserController extends BaseController {
public HttpResult<List<User>> getUserByDeptIds(@RequestBody List<String> deptId) {
String methodDescribe = getMethodDescribe("getUserByDeptIds");
List<User> users = userService.list(new LambdaQueryWrapper<User>()
.in(User::getDeptId,deptId)
.eq(User::getSmsNotice,1)
.eq(User::getState,1)
.in(User::getDeptId, deptId)
.eq(User::getSmsNotice, 1)
.eq(User::getState, 1)
);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
@@ -492,14 +519,25 @@ public class UserController extends BaseController {
public HttpResult<List<User>> getUserInfoByDeptIds(@RequestBody List<String> deptId) {
String methodDescribe = getMethodDescribe("getUserInfoByDeptIds");
List<User> users = userService.list(new LambdaQueryWrapper<User>()
.in(User::getDeptId,deptId)
.in(User::getDeptId, deptId)
.eq(User::getState, DataStateEnum.ENABLE.getCode())
);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@PostMapping("/getUserIdByRoleId")
@ApiOperation("根据角色id查询用户id")
@ApiImplicitParam(name = "roleId", value = "用户角色id", required = true)
public HttpResult<List<String>> getUserIdByRoleId(@RequestBody List<String> roleId) {
String methodDescribe = getMethodDescribe("getUserInfoByRoleId");
List<String> userId = userService.getUserIdByRoleId(roleId);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, userId, methodDescribe);
}
/**
* 获取同级部门用户,以及下级部门所有用户
*
* @author cdf
* @date 2023/7/31
*/
@@ -516,6 +554,7 @@ public class UserController extends BaseController {
/**
* 根据角色类型获取用户 角色类型 type0:超级管理员1:管理员2:普通用户' 3:'审核角色',
*
* @author cdf
* @date 2024/3/29
*/
@@ -528,5 +567,18 @@ public class UserController extends BaseController {
List<User> users = userService.getUserByRoleType(roleType);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, users, methodDescribe);
}
/**
* 查询所有用户
*/
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
@GetMapping("/simpleList")
@ApiOperation("查询所有用户作为下拉框")
public HttpResult<Object> simpleList() {
String methodDescribe = getMethodDescribe("simpleList");
List<User> result = userService.simpleList();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
}

View File

@@ -1,5 +1,6 @@
package com.njcn.user.service;
import cn.hutool.core.lang.tree.Tree;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.system.pojo.dto.AreaTreeDTO;
@@ -247,4 +248,7 @@ public interface IDeptService extends IService<Dept> {
* @Date: 2023/12/11 14:50
*/
List<Dept> getAllDept();
List<Tree<String>> orgTreeSelector();
}

View File

@@ -93,4 +93,7 @@ public interface IRoleService extends IService<Role> {
*/
Role getRoleByCode(String code);
List<Role> allRoleList();
List<Role> simpleList();
}

View File

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.user.pojo.param.UserInfoParm;
import com.njcn.user.pojo.param.UserParam;
import com.njcn.user.pojo.po.Role;
import com.njcn.user.pojo.po.User;
import com.njcn.user.pojo.dto.UserDTO;
import com.njcn.user.pojo.vo.UserVO;
@@ -190,4 +191,9 @@ public interface IUserService extends IService<User> {
List<User> getUserByRoleType(Integer roleType);
List<UserVO> listAllUserByDeptId(String deptId);
List<String> getUserIdByRoleId(List<String> roleId);
List<User> simpleList();
}

View File

@@ -2,6 +2,9 @@ package com.njcn.user.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -65,8 +68,6 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
private final CommLedgerDeptClient commLedgerDeptClient;
@Override
public Page<DeptVO> listDept(DeptParam.QueryParam queryParam) {
QueryWrapper<DeptVO> queryWrapper = new QueryWrapper<>();
@@ -82,7 +83,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy()));
} else {
//没有排序参数默认根据sort字段排序没有排序字段的根据updateTime更新时间排序
queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), "sys_dept.sort");
queryWrapper.orderBy(true, true, "sys_dept.sort");
}
}
queryWrapper.ne("sys_dept.state", DataStateEnum.DELETED.getCode());
@@ -105,7 +106,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
} else {
String pids = "," + deptParam.getPid();
String pid = this.baseMapper.getIdString(deptParam.getPid());
if(StrUtil.isBlank(deptParam.getPid())){
if (StrUtil.isBlank(deptParam.getPid())) {
throw new BusinessException(UserResponseEnum.DEPT_PID_EXCEPTION);
}
@@ -114,7 +115,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
}
//默认为正常状态
dept.setState(DataStateEnum.ENABLE.getCode());
if (StrUtil.isBlank(dept.getCode())){
if (StrUtil.isBlank(dept.getCode())) {
dept.setCode(dept.getId());
}
return this.save(dept);
@@ -129,7 +130,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
Dept deptTem = this.getDeptById(updateParam.getId());
this.updateById(dept);
if(!updateParam.getName().equals(deptTem.getName())){
if (!updateParam.getName().equals(deptTem.getName())) {
//修改了部门名称需要修改台账信息中的单位部门
commLedgerDeptClient.update(dept);
}
@@ -181,20 +182,20 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
@Override
public List<DeptTreeVO> existMonitorDeptTree() {
List<Dept> deptTem = commLedgerDeptClient.existMonitorDeptTree().getData();
if(CollectionUtil.isEmpty(deptTem)){
if (CollectionUtil.isEmpty(deptTem)) {
throw new BusinessException("当前没有部门存在监测点");
}
List<String> useMonitorIds = deptTem.stream().map(Dept::getId).collect(Collectors.toList());
List<String> resultDeptId = deptTem.stream().map(item->item.getPids().split(",")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
List<String> resultDeptId = deptTem.stream().map(item -> item.getPids().split(",")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
resultDeptId.addAll(useMonitorIds);
List<Integer> deptType = WebUtil.filterDeptType();
String deptIndex = RequestUtil.getDeptIndex();
List<DeptTreeVO> deptList = this.baseMapper.getDeptTree(deptIndex, deptType);
List<DeptTreeVO> finalDeptList = deptList.stream().filter(item->resultDeptId.contains(item.getId())).collect(Collectors.toList());
List<DeptTreeVO> lastList = finalDeptList.stream().peek(item->{
if(useMonitorIds.contains(item.getId())){
List<DeptTreeVO> finalDeptList = deptList.stream().filter(item -> resultDeptId.contains(item.getId())).collect(Collectors.toList());
List<DeptTreeVO> lastList = finalDeptList.stream().peek(item -> {
if (useMonitorIds.contains(item.getId())) {
item.setLevel(2);
}
}).collect(Collectors.toList());
@@ -225,8 +226,8 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
List<String> areaTreeVO = deptArea();
HttpResult<List<AreaTreeDTO>> areaTreeDTOS = areaFeignClient.areaDeptTree(id, type);
List<AreaTreeDTO> list = areaTreeDTOS.getData();
if(CollectionUtils.isEmpty(list)){
return new ArrayList<>();
if (CollectionUtils.isEmpty(list)) {
return new ArrayList<>();
}
for (AreaTreeDTO areaTreeVOList : list) {
if (areaTreeVO.contains(areaTreeVOList.getId())) {
@@ -251,14 +252,14 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
List<AreaTreeDTO> result = new ArrayList<>();
List<String> deptBindAreaList = deptArea();
List<Area> areaList = areaFeignClient.getPidAreaList(id, type).getData();
areaList.forEach(item->{
areaList.forEach(item -> {
AreaTreeDTO areaTreeDTO = new AreaTreeDTO();
areaTreeDTO.setId(item.getId());
areaTreeDTO.setPid(item.getPid());
if(deptBindAreaList.contains(item.getId())){
if (deptBindAreaList.contains(item.getId())) {
areaTreeDTO.setName(item.getName() + "(已被绑定)");
areaTreeDTO.setIsFalse(1);
}else {
} else {
areaTreeDTO.setName(item.getName());
areaTreeDTO.setIsFalse(0);
}
@@ -340,7 +341,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
@Override
public Dept getDeptByCode(String deptCode) {
LambdaQueryWrapper<Dept> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Dept::getCode,deptCode).eq(Dept::getState,DataStateEnum.ENABLE.getCode());
lambdaQueryWrapper.eq(Dept::getCode, deptCode).eq(Dept::getState, DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectOne(lambdaQueryWrapper);
}
@@ -406,7 +407,7 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
@Override
public List<String> getDepSonSelfCodetByDeptId(String id) {
Dept dept = this.getDeptById(id);
if(Objects.isNull(dept)){
if (Objects.isNull(dept)) {
throw new BusinessException(UserResponseEnum.DEPT_MISSING);
}
List<String> sonIds = this.baseMapper.getDeptSonSlfeIds(id);
@@ -418,11 +419,11 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
public List<String> getDepSonSelfCodetByCode(String code) {
List<String> codes = new ArrayList<>();
Dept dept = this.getDeptByCode(code);
if(Objects.isNull(dept)){
if (Objects.isNull(dept)) {
throw new BusinessException(UserResponseEnum.DEPT_MISSING);
}
List<DeptDTO> deptList = this.baseMapper.getDeptDescendantIndexes(dept.getId(), Stream.of(0,1).collect(Collectors.toList()));
if(CollectionUtil.isNotEmpty(deptList)){
List<DeptDTO> deptList = this.baseMapper.getDeptDescendantIndexes(dept.getId(), Stream.of(0, 1).collect(Collectors.toList()));
if (CollectionUtil.isNotEmpty(deptList)) {
codes = deptList.stream().map(DeptDTO::getCode).collect(Collectors.toList());
codes.add(dept.getCode());
}
@@ -441,10 +442,10 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
@Override
public List<Dept> getDirectSonSelf(String deptId) {
LambdaQueryWrapper<Dept> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.and (Wrapper-> Wrapper.
eq (Dept::getPid,deptId).
or ().
eq(Dept::getId,deptId)).eq (Dept::getState, DataStateEnum.ENABLE.getCode());
lambdaQueryWrapper.and(Wrapper -> Wrapper.
eq(Dept::getPid, deptId).
or().
eq(Dept::getId, deptId)).eq(Dept::getState, DataStateEnum.ENABLE.getCode());
// lambdaQueryWrapper.eq(Dept::getPid,deptId).eq(Dept::getState, DataStateEnum.ENABLE.getCode()).
// or().eq(Dept::getId,deptId);
return this.list(lambdaQueryWrapper);
@@ -453,14 +454,14 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
@Override
public List<Dept> getSpecialDeptList() {
LambdaQueryWrapper<Dept> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Dept::getSpecialType,1).eq(Dept::getState, DataStateEnum.ENABLE.getCode());
lambdaQueryWrapper.eq(Dept::getSpecialType, 1).eq(Dept::getState, DataStateEnum.ENABLE.getCode());
return this.list(lambdaQueryWrapper);
}
@Override
public Dept getRootDept(){
public Dept getRootDept() {
LambdaQueryWrapper<Dept> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Dept::getState,DataStateEnum.ENABLE.getCode()).eq(Dept::getPid,'0');
lambdaQueryWrapper.eq(Dept::getState, DataStateEnum.ENABLE.getCode()).eq(Dept::getPid, '0');
return this.getOne(lambdaQueryWrapper);
}
@@ -474,23 +475,23 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
*/
@Override
public List<DeptDTO> getDepSonDetailByDeptId(String deptId) {
List<Dept> result = new ArrayList<> ();
List<Dept> result = new ArrayList<>();
LambdaQueryWrapper<Dept> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq (Dept::getPid, deptId).
eq (Dept::getState,DataStateEnum.ENABLE.getCode());
result = this.baseMapper.selectList (lambdaQueryWrapper);
if(CollectionUtils.isEmpty (result)){
lambdaQueryWrapper.eq(Dept::getPid, deptId).
eq(Dept::getState, DataStateEnum.ENABLE.getCode());
result = this.baseMapper.selectList(lambdaQueryWrapper);
if (CollectionUtils.isEmpty(result)) {
LambdaQueryWrapper<Dept> deptLambdaQueryWrapper = new LambdaQueryWrapper<>();
deptLambdaQueryWrapper.eq (Dept::getId, deptId).
eq (Dept::getState,DataStateEnum.ENABLE.getCode());
result = this.baseMapper.selectList (deptLambdaQueryWrapper);
deptLambdaQueryWrapper.eq(Dept::getId, deptId).
eq(Dept::getState, DataStateEnum.ENABLE.getCode());
result = this.baseMapper.selectList(deptLambdaQueryWrapper);
}
List<DeptDTO> collect = result.stream ( ).map (temp -> {
DeptDTO deptDTO = new DeptDTO ( );
BeanUtils.copyProperties (temp, deptDTO);
List<DeptDTO> collect = result.stream().map(temp -> {
DeptDTO deptDTO = new DeptDTO();
BeanUtils.copyProperties(temp, deptDTO);
return deptDTO;
}).collect (Collectors.toList ( ));
}).collect(Collectors.toList());
return collect;
}
@@ -503,16 +504,26 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
@Override
public List<Dept> getDeptByCodeList(List<String> list) {
return this.lambdaQuery().in(Dept::getCode,list).eq(Dept::getState,DataStateEnum.ENABLE.getCode()).list();
return this.lambdaQuery().in(Dept::getCode, list).eq(Dept::getState, DataStateEnum.ENABLE.getCode()).list();
}
@Override
public List<Dept> getDeptInfoListByIds(List<String> deptIds) {
return this.lambdaQuery().in(Dept::getId,deptIds).eq(Dept::getState,DataStateEnum.ENABLE.getCode()).list();
return this.lambdaQuery().in(Dept::getId, deptIds).eq(Dept::getState, DataStateEnum.ENABLE.getCode()).list();
}
@Override
public List<Dept> getAllDept() {
return this.lambdaQuery().eq(Dept::getState,DataStateEnum.ENABLE.getCode()).list();
return this.lambdaQuery().eq(Dept::getState, DataStateEnum.ENABLE.getCode()).list();
}
@Override
public List<Tree<String>> orgTreeSelector() {
List<Dept> deptList = this.lambdaQuery().eq(Dept::getState, DataStateEnum.ENABLE.getCode()).list();
List<TreeNode<String>> treeNodeList = deptList.stream().map(dept ->
new TreeNode<>(dept.getId(), dept.getPid(), dept.getName(), dept.getSort()))
.collect(Collectors.toList());
return TreeUtil.build(treeNodeList, "0");
}
}

View File

@@ -204,6 +204,20 @@ public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements IR
return this.lambdaQuery().eq(Role::getCode,code).eq(Role::getState,1).one();
}
@Override
public List<Role> allRoleList() {
QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
queryWrapper.ne("sys_role.state", DataStateEnum.DELETED.getCode());
return this.baseMapper.selectList(queryWrapper);
}
@Override
public List<Role> simpleList() {
LambdaQueryWrapper<Role> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.select(Role::getId,Role::getName).eq(Role::getState,DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(lambdaQueryWrapper);
}
/**
* 校验参数,检查是否存在相同编码的角色代码
*/

View File

@@ -100,18 +100,18 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
}
List<String> roleNames = roleService.getRoleNameByUserId(user.getId());
UserSet userSet = userSetService.lambdaQuery().eq(UserSet::getUserId, user.getId()).one();
return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType(),user.getHeadSculpture());
return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType(), user.getHeadSculpture());
}
@Override
public UserDTO loadUserByPhone(String phone) {
User user = getUserByPhone(phone,false,null);
User user = getUserByPhone(phone, false, null);
if (Objects.isNull(user)) {
return null;
}
List<String> roleNames = roleService.getRoleNameByUserId(user.getId());
UserSet userSet = userSetService.lambdaQuery().eq(UserSet::getUserId, user.getId()).one();
return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType(),user.getHeadSculpture());
return new UserDTO(user.getId(), user.getLoginName(), user.getName(), user.getPassword(), roleNames, userSet.getSecretKey(), userSet.getStandBy(), user.getDeptId(), user.getType(), user.getHeadSculpture());
}
@Override
@@ -256,10 +256,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
return null;
}
BeanUtil.copyProperties(user, userVO);
if (!Objects.isNull(user.getDeptId())){
if (!Objects.isNull(user.getDeptId())) {
Dept dept = deptService.getDeptById(user.getDeptId());
//非自定义部门
if (Objects.equals(dept.getType(),0)){
if (Objects.equals(dept.getType(), 0)) {
String areaId = deptService.getAreaIdByDeptId(user.getDeptId());
userVO.setAreaId(areaId);
userVO.setAreaName(areaFeignClient.selectIdArea(areaId).getData().getName());
@@ -284,13 +284,12 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
Integer type = user.getType();
if (Objects.equals(UserType.SUPER_ADMINISTRATOR, type)) {
type = UserType.ADMINISTRATOR;
//fixme 存在web用户和App用户目前先通过管理员的登录名来区分开
} else if (Objects.equals(UserType.ADMINISTRATOR, type) && !Objects.equals(user.getLoginName(),"njcnyw")) {
//fixme 存在web用户和App用户目前先通过管理员的登录名来区分开
} else if (Objects.equals(UserType.ADMINISTRATOR, type) && !Objects.equals(user.getLoginName(), "njcnyw")) {
type = UserType.USER;
} else if (Objects.equals(UserType.ADMINISTRATOR, type) && Objects.equals(user.getLoginName(),"njcnyw")) {
} else if (Objects.equals(UserType.ADMINISTRATOR, type) && Objects.equals(user.getLoginName(), "njcnyw")) {
type = UserType.APP;
}
else if (Objects.equals(UserType.USER, type) || Objects.equals(UserType.APP, type)) {
} else if (Objects.equals(UserType.USER, type) || Objects.equals(UserType.APP, type)) {
return page;
}
if (ObjectUtil.isNotNull(queryParam)) {
@@ -356,7 +355,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
@Override
public boolean updatePassword(String id, String password) {
String secretPassword = userSetService.updatePassword(id, password,true);
String secretPassword = userSetService.updatePassword(id, password, true);
User user = lambdaQuery().eq(User::getId, id).one();
user.setPassword(secretPassword);
user.setPwdValidity(LocalDateTime.now());
@@ -431,7 +430,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
String fileName = methodDescribe + ".xlsx";
String targetDir = generalInfo.getBusinessTempPath() + File.separator + RequestUtil.getUserIndex();
File parentDir = new File(targetDir);
if(!parentDir.exists()){
if (!parentDir.exists()) {
parentDir.mkdirs();
}
File excel = new File(targetDir, fileName);
@@ -488,14 +487,14 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
@Override
public List<User> getUserListByDeptId(String deptId) {
LambdaQueryWrapper<Dept> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(Dept::getPids,deptId).select(Dept::getId);
lambdaQueryWrapper.like(Dept::getPids, deptId).select(Dept::getId);
List<String> deptIds = new ArrayList<>();
deptIds.add(deptId);
List<Dept> deptList = deptService.list(lambdaQueryWrapper);
if(CollectionUtil.isNotEmpty(deptIds)){
if (CollectionUtil.isNotEmpty(deptIds)) {
deptIds.addAll(deptList.stream().map(Dept::getId).distinct().collect(Collectors.toList()));
}
return this.list(new LambdaQueryWrapper<User>().in(User::getDeptId,deptIds).select(User::getId,User::getName,User::getLoginName));
return this.list(new LambdaQueryWrapper<User>().in(User::getDeptId, deptIds).select(User::getId, User::getName, User::getLoginName));
}
@Override
@@ -528,6 +527,42 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
return userRoleMapper.getUserByRoleType(roleType);
}
@Override
public List<UserVO> listAllUserByDeptId(String deptId) {
//查询所有状态正常的用户信息目前只要id和name
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
if (StrUtil.isNotBlank(deptId)) {
lambdaQueryWrapper.eq(User::getDeptId, deptId);
}
lambdaQueryWrapper.eq(User::getState, DataStateEnum.ENABLE.getCode())
.select(User::getName,User::getId);
List<User> users = this.baseMapper.selectList(lambdaQueryWrapper);
if (CollectionUtil.isEmpty(users)) {
return new ArrayList<>();
} else {
return BeanUtil.copyToList(users, UserVO.class);
}
}
@Override
public List<String> getUserIdByRoleId(List<String> roleId) {
LambdaQueryWrapper<UserRole> userRoleLambdaQueryWrapper = new LambdaQueryWrapper<>();
userRoleLambdaQueryWrapper.in(UserRole::getRoleId,roleId);
List<UserRole> userRole = userRoleService.list(userRoleLambdaQueryWrapper);
if(CollectionUtil.isEmpty(userRole)){
return new ArrayList<>();
}else{
return userRole.stream().map(UserRole::getUserId).distinct().collect(Collectors.toList());
}
}
@Override
public List<User> simpleList() {
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
userLambdaQueryWrapper.select(User::getId,User::getName).eq(User::getState,DataStateEnum.ENABLE.getCode());
return this.baseMapper.selectList(userLambdaQueryWrapper);
}
/**
* 根据登录名查询用户
*
@@ -576,8 +611,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
*/
private void judgeIp(@NotNull User user, UserStrategy userStrategy) {
String ipSection = user.getLimitIpStart() + "-" + user.getLimitIpEnd();
log.error("用户实际ip:"+RequestUtil.getRealIp());
log.error("用户限制ip:"+ipSection);
log.error("用户实际ip:" + RequestUtil.getRealIp());
log.error("用户限制ip:" + ipSection);
if (RequestUtil.getRealIp().equalsIgnoreCase(LogInfo.UNKNOWN_IP)) {
//feign接口可能获取的IP是空的
throw new BusinessException(UserResponseEnum.INVALID_IP);