!212 工作流支持会签、或签,可自定义任务分配方式

Merge pull request !212 from 芋道源码/feature/bpm-back
This commit is contained in:
芋道源码 2022-07-08 01:47:13 +00:00 committed by Gitee
commit 6d30d56633
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
248 changed files with 1084 additions and 5404 deletions

2
.gitignore vendored
View File

@ -45,3 +45,5 @@ nbdist/
### JRebel ### ### JRebel ###
rebel.xml rebel.xml
application-my.yaml

View File

@ -23,7 +23,7 @@
* 权限认证使用 Spring Security & Token & Redis支持多终端、多种用户的认证系统。 * 权限认证使用 Spring Security & Token & Redis支持多终端、多种用户的认证系统。
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能。 * 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能。
* 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装。 * 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装。
* 工作流使用 Activiti + Flowable支持动态表单、在线设计流程、多种任务分配方式。 * 工作流使用 Flowable支持动态表单、在线设计流程、会签 / 或签、多种任务分配方式。
* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。 * 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款。 * 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款。
* 集成阿里云、腾讯云、云片等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务。 * 集成阿里云、腾讯云、云片等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务。
@ -167,7 +167,7 @@ ps核心功能已经实现正在对接微信小程序中...
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | | [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | | [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
| [Activiti](https://github.com/Activiti/Activiti) | 工作流引擎 | 7.1.0.M6 | [文档](TODO) | | [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.0 | [文档](https://doc.iocoder.cn/bpm/) |
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | | [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) | | [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |

View File

@ -587,7 +587,6 @@
<artifactId>justauth-spring-boot-starter</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) --> <artifactId>justauth-spring-boot-starter</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
<version>${justauth.version}</version> <version>${justauth.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>

View File

@ -38,7 +38,6 @@
<module>yudao-spring-boot-starter-biz-data-permission</module> <module>yudao-spring-boot-starter-biz-data-permission</module>
<module>yudao-spring-boot-starter-biz-error-code</module> <module>yudao-spring-boot-starter-biz-error-code</module>
<module>yudao-spring-boot-starter-activiti</module>
<module>yudao-spring-boot-starter-flowable</module> <module>yudao-spring-boot-starter-flowable</module>
</modules> </modules>

View File

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-spring-boot-starter-activiti</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>Activiti 拓展</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- 工作流相关 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-image-generator</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,45 +0,0 @@
package cn.iocoder.yudao.framework.activiti.config;
import cn.iocoder.yudao.framework.activiti.core.web.ActivitiWebFilter;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.activiti.spring.SpringProcessEngineConfiguration;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class YudaoActivitiConfiguration {
/**
* Activiti 流程图的生成器目前管理后台的流程图 svg通过它绘制生成
*/
@Bean
public ProcessDiagramGenerator processDiagramGenerator() {
return new DefaultProcessDiagramGenerator();
}
@Bean
public FilterRegistrationBean<ActivitiWebFilter> activitiWebFilter() {
FilterRegistrationBean<ActivitiWebFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new ActivitiWebFilter());
registrationBean.setOrder(WebFilterOrderEnum.ACTIVITI_FILTER);
return registrationBean;
}
/**
* ProcessEngineConfigurationConfigurer 实现类设置事务管理器保证 ACT_ 表和自己的表的事务一致性
*/
@Bean
public ProcessEngineConfigurationConfigurer processEngineConfigurationConfigurer(
PlatformTransactionManager platformTransactionManager) {
return processEngineConfiguration -> processEngineConfiguration.setTransactionManager(platformTransactionManager);
}
}

View File

@ -1,109 +0,0 @@
package cn.iocoder.yudao.framework.activiti.core.util;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import com.alibaba.ttl.TransmittableThreadLocal;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.util.io.BytesStreamSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Activiti 工具类
*
* @author 芋道源码
*/
public class ActivitiUtils {
static {
setAuthenticationThreadLocal();
}
// ========== Authentication 相关 ==========
/**
* 反射修改 Authentication authenticatedUserIdThreadLocal 静态变量使用 TTL 线程变量
* 目的保证 @Async 等异步执行时变量丢失的问题
*/
private static void setAuthenticationThreadLocal() {
ReflectUtil.setFieldValue(Authentication.class, "authenticatedUserIdThreadLocal",
new TransmittableThreadLocal<String>());
}
public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId));
}
public static void clearAuthenticatedUserId() {
Authentication.setAuthenticatedUserId(null);
}
public static boolean equals(String userIdStr, Long userId) {
return Objects.equals(userId, NumberUtils.parseLong(userIdStr));
}
// ========== BPMN XML 相关 ==========
/**
* 构建对应的 BPMN Model
*
* @param bpmnBytes 原始的 BPMN XML 字节数组
* @return BPMN Model
*/
public static BpmnModel buildBpmnModel(byte[] bpmnBytes) {
// 转换成 BpmnModel 对象
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToBpmnModel(new BytesStreamSource(bpmnBytes), true, true);
}
/**
* 获得 BPMN 流程中指定的元素们
*
* @param model
* @param clazz 指定元素例如说{@link org.activiti.bpmn.model.UserTask}{@link org.activiti.bpmn.model.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 String getBpmnXml(BpmnModel model) {
if (model == null) {
return null;
}
return StrUtil.utf8Str(getBpmnBytes(model));
}
public static byte[] getBpmnBytes(BpmnModel model) {
if (model == null) {
return new byte[0];
}
BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model);
}
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) {
// 由于 BpmnModel 未提供 equals 方法所以只能转成字节数组进行比较
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel));
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.framework.activiti.core.web;
import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Activiti Web 过滤器 userId 设置到 {@link org.activiti.engine.impl.identity.Authentication}
*
* @author 芋道源码
*/
public class ActivitiWebFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
// 设置工作流的用户
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (userId != null) {
ActivitiUtils.setAuthenticatedUserId(userId);
}
// 过滤
chain.doFilter(request, response);
} finally {
// 清理
ActivitiUtils.clearAuthenticatedUserId();
}
}
}

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.iocoder.yudao.framework.activiti.config.YudaoActivitiConfiguration

View File

@ -9,8 +9,15 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
/**
* Flowable 相关的工具方法
*
* @author 芋道源码
*/
public class FlowableUtils { public class FlowableUtils {
// ========== User 相关的工具方法 ==========
public static void setAuthenticatedUserId(Long userId) { public static void setAuthenticatedUserId(Long userId) {
Authentication.setAuthenticatedUserId(String.valueOf(userId)); Authentication.setAuthenticatedUserId(String.valueOf(userId));
} }
@ -19,6 +26,8 @@ public class FlowableUtils {
Authentication.setAuthenticatedUserId(null); Authentication.setAuthenticatedUserId(null);
} }
// ========== BPMN 相关的工具方法 ==========
/** /**
* 获得 BPMN 流程中指定的元素们 * 获得 BPMN 流程中指定的元素们
* *
@ -59,4 +68,15 @@ public class FlowableUtils {
BpmnXMLConverter converter = new BpmnXMLConverter(); BpmnXMLConverter converter = new BpmnXMLConverter();
return converter.convertToXML(model); return converter.convertToXML(model);
} }
// ========== Execution 相关的工具方法 ==========
public static String formatCollectionVariable(String activityId) {
return activityId + "_assignees";
}
public static String formatCollectionElementVariable(String activityId) {
return activityId + "_assignee";
}
} }

View File

@ -10,9 +10,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<modules> <modules>
<module>yudao-module-bpm-api</module> <module>yudao-module-bpm-api</module>
<module>yudao-module-bpm-base</module> <module>yudao-module-bpm-biz</module>
<module>yudao-module-bpm-biz-flowable</module>
<module>yudao-module-bpm-biz-activiti</module>
</modules> </modules>
<artifactId>yudao-module-bpm</artifactId> <artifactId>yudao-module-bpm</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
@ -23,10 +21,7 @@
例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等 例如说:流程定义、表单配置、审核中心(我的申请、我的待办、我的已办)等等
bpm 解释https://baike.baidu.com/item/BPM/1933 bpm 解释https://baike.baidu.com/item/BPM/1933
目前提供两套实现方案: 工作流基于 Flowable 6 实现,分成流程定义、流程表单、流程实例、流程任务等功能模块。
1. 基于 Activiti 7 实现的 yudao-module-bpm-biz-activiti
2. 基于 Flowable 6 实现的 yudao-module-bpm-biz-flowable
两套实现会存在共享的逻辑,所以会继承 yudao-module-bpm-base
</description> </description>
</project> </project>

View File

@ -13,15 +13,11 @@ import lombok.Getter;
public enum BpmTaskAssignRuleTypeEnum { public enum BpmTaskAssignRuleTypeEnum {
ROLE(10, "角色"), ROLE(10, "角色"),
DEPT_MEMBER(20, "部门的成员"), // 包括负责人 DEPT_MEMBER(20, "部门的成员"), // 包括负责人
DEPT_LEADER(21, "部门的负责人"), DEPT_LEADER(21, "部门的负责人"),
POST(22, "岗位"), POST(22, "岗位"),
USER(30, "用户"), USER(30, "用户"),
USER_GROUP(40, "用户组"), USER_GROUP(40, "用户组"),
SCRIPT(50, "自定义脚本"), // 例如说发起人所在部门的领导发起人所在部门的领导的领导 SCRIPT(50, "自定义脚本"), // 例如说发起人所在部门的领导发起人所在部门的领导的领导
; ;

View File

@ -14,7 +14,12 @@ import lombok.Getter;
public enum BpmProcessInstanceDeleteReasonEnum { public enum BpmProcessInstanceDeleteReasonEnum {
REJECT_TASK("不通过任务,原因:{}"), // 修改文案时需要注意 isRejectReason 方法 REJECT_TASK("不通过任务,原因:{}"), // 修改文案时需要注意 isRejectReason 方法
CANCEL_TASK("主动取消任务,原因:{}"); CANCEL_TASK("主动取消任务,原因:{}"),
// ========== 流程任务的独有原因 ==========
MULTI_TASK_END("系统自动取消,原因:多任务审批已经满足条件,无需审批该任务"), // 多实例满足 condition 而结束时其它任务实例任务会被取消对应的删除原因是 MI_END
;
private final String reason; private final String reason;
@ -34,4 +39,20 @@ public enum BpmProcessInstanceDeleteReasonEnum {
return StrUtil.startWith(reason, "不通过任务,原因:"); return StrUtil.startWith(reason, "不通过任务,原因:");
} }
/**
* Flowable 的删除原因翻译成对应的中文原因
*
* @param reason 原始原因
* @return 原因
*/
public static String translateReason(String reason) {
if (StrUtil.isEmpty(reason)) {
return reason;
}
switch (reason) {
case "MI_END": return MULTI_TASK_END.getReason();
default: return reason;
}
}
} }

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程实例的结果
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmProcessInstanceResultEnum {
PROCESS(1, "处理中"),
APPROVE(2, "通过"),
REJECT(3, "不通过"),
CANCEL(4, "已取消"),
// ========== 流程任务独有的状态 ==========
BACK(5, "退回/驳回");
/**
* 结果
*
* 如果新增时注意 {@link #isEndResult(Integer)} 是否需要变更
*/
private final Integer result;
/**
* 描述
*/
private final String desc;
/**
* 判断该结果是否已经处于 End 最终结果
*
* 主要用于一些结果更新的逻辑如果已经是最终结果就不再进行更新
*
* @param result 结果
* @return 是否
*/
public static boolean isEndResult(Integer result) {
return ObjectUtils.equalsAny(result, APPROVE.getResult(), REJECT.getResult(), CANCEL.getResult(), BACK.getResult());
}
}

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task;

View File

@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.lang.Nullable;
import java.util.List;
@Mapper
public interface BpmTaskAssignRuleMapper extends BaseMapperX<BpmTaskAssignRuleDO> {
default List<BpmTaskAssignRuleDO> selectListByProcessDefinitionId(String processDefinitionId,
@Nullable String taskDefinitionKey) {
return selectList(new LambdaQueryWrapperX<BpmTaskAssignRuleDO>()
.eq(BpmTaskAssignRuleDO::getProcessDefinitionId, processDefinitionId)
.eqIfPresent(BpmTaskAssignRuleDO::getTaskDefinitionKey, taskDefinitionKey));
}
default List<BpmTaskAssignRuleDO> selectListByModelId(String modelId) {
return selectList(new LambdaQueryWrapperX<BpmTaskAssignRuleDO>()
.eq(BpmTaskAssignRuleDO::getModelId, modelId)
.eq(BpmTaskAssignRuleDO::getProcessDefinitionId, BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL));
}
default BpmTaskAssignRuleDO selectListByModelIdAndTaskDefinitionKey(String modelId,
String taskDefinitionKey) {
return selectOne(new LambdaQueryWrapperX<BpmTaskAssignRuleDO>()
.eq(BpmTaskAssignRuleDO::getModelId, modelId)
.eq(BpmTaskAssignRuleDO::getProcessDefinitionId, BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL)
.eq(BpmTaskAssignRuleDO::getTaskDefinitionKey, taskDefinitionKey));
}
}

View File

@ -1,34 +0,0 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.task;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceMyPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BpmProcessInstanceExtMapper extends BaseMapperX<BpmProcessInstanceExtDO> {
default PageResult<BpmProcessInstanceExtDO> selectPage(Long userId, BpmProcessInstanceMyPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<BpmProcessInstanceExtDO>()
.eqIfPresent(BpmProcessInstanceExtDO::getStartUserId, userId)
.likeIfPresent(BpmProcessInstanceExtDO::getName, reqVO.getName())
.eqIfPresent(BpmProcessInstanceExtDO::getProcessDefinitionId, reqVO.getProcessDefinitionId())
.eqIfPresent(BpmProcessInstanceExtDO::getCategory, reqVO.getCategory())
.eqIfPresent(BpmProcessInstanceExtDO::getStatus, reqVO.getStatus())
.eqIfPresent(BpmProcessInstanceExtDO::getResult, reqVO.getResult())
.betweenIfPresent(BpmProcessInstanceExtDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
.orderByDesc(BpmProcessInstanceExtDO::getId));
}
default BpmProcessInstanceExtDO selectByProcessInstanceId(String processDefinitionId) {
return selectOne(BpmProcessInstanceExtDO::getProcessInstanceId, processDefinitionId);
}
default void updateByProcessInstanceId(BpmProcessInstanceExtDO updateObj) {
update(updateObj, new LambdaQueryWrapperX<BpmProcessInstanceExtDO>()
.eq(BpmProcessInstanceExtDO::getProcessInstanceId, updateObj.getProcessInstanceId()));
}
}

View File

@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程实例的结果
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum BpmProcessInstanceResultEnum {
PROCESS(1, "处理中"),
APPROVE(2, "通过"),
REJECT(3, "不通过"),
CANCEL(4, "已取消");
/**
* 结果
*/
private final Integer result;
/**
* 描述
*/
private final String desc;
}

View File

@ -1,10 +0,0 @@
/**
* bpm 包下业务流程管理Business Process Management我们放工作流的功能
* 例如说流程定义表单配置审核中心我的申请我的待办我的已办等等
*
* bpm 解释https://baike.baidu.com/item/BPM/1933
*
* 1. Controller URL /bpm/ 开头避免和其它 Module 冲突
* 2. DataObject 表名 bpm_ 开头方便在数据库中区分
*/
package cn.iocoder.yudao.module.bpm;

View File

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-bpm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm-biz-activiti</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
bpm-activiti 模块,基于 Activiti 7 实现工作流
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-bpm-base</artifactId>
<version>${revision}</version>
</dependency>
<!-- 工作流相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-activiti</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,27 +0,0 @@
package cn.iocoder.yudao.module.bpm.api.task;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* Activiti 流程实例 Api 实现类
*
* @author 芋道源码
*/
@Service
@Validated
public class BpmProcessInstanceApiImpl implements BpmProcessInstanceApi {
@Resource
private BpmProcessInstanceService processInstanceService;
@Override
public String createProcessInstance(Long userId, BpmProcessInstanceCreateReqDTO reqDTO) {
return processInstanceService.createProcessInstance(userId, reqDTO);
}
}

View File

@ -1,96 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.io.IoUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 流程模型")
@RestController
@RequestMapping("/bpm/model")
@Validated
public class BpmModelController {
@Resource
private BpmModelService bpmModelService;
@GetMapping("/page")
@ApiOperation(value = "获得模型分页")
public CommonResult<PageResult<BpmModelPageItemRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
return success(bpmModelService.getModelPage(pageVO));
}
@GetMapping("/get")
@ApiOperation("获得模型")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:model:query')")
public CommonResult<BpmModelRespVO> getModel(@RequestParam("id") String id) {
BpmModelRespVO model = bpmModelService.getModel(id);
return success(model);
}
@PostMapping("/create")
@ApiOperation(value = "新建模型")
@PreAuthorize("@ss.hasPermission('bpm:model:create')")
public CommonResult<String> createModel(@Valid @RequestBody BpmModelCreateReqVO createRetVO) {
return success(bpmModelService.createModel(createRetVO, null));
}
@PostMapping("/import")
@ApiOperation(value = "导入模型")
@PreAuthorize("@ss.hasPermission('bpm:model:import')")
public CommonResult<String> importModel(@Valid BpmModeImportReqVO importReqVO) throws IOException {
BpmModelCreateReqVO createReqVO = BpmModelConvert.INSTANCE.convert(importReqVO);
// 读取文件
String bpmnXml = IoUtils.readUtf8(importReqVO.getBpmnFile().getInputStream(), false);
return success(bpmModelService.createModel(createReqVO, bpmnXml));
}
@PutMapping("/update")
@ApiOperation(value = "修改模型")
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
public CommonResult<Boolean> updateModel(@Valid @RequestBody BpmModelUpdateReqVO modelVO) {
bpmModelService.updateModel(modelVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除模型")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:model:delete')")
public CommonResult<Boolean> deleteModel(@RequestParam("id") String id) {
bpmModelService.deleteModel(id);
return success(true);
}
@PostMapping("/deploy")
@ApiOperation(value = "部署模型")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:model:deploy')")
public CommonResult<Boolean> deployModel(@RequestParam("id") String id) {
bpmModelService.deployModel(id);
return success(true);
}
@PutMapping("/update-state")
@ApiOperation(value = "修改模型的状态", notes = "实际更新的部署的流程定义的状态")
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
public CommonResult<Boolean> updateModelState(@Valid @RequestBody BpmModelUpdateStateReqVO reqVO) {
bpmModelService.updateModelState(reqVO.getId(), reqVO.getState());
return success(true);
}
}

View File

@ -1,4 +0,0 @@
### 请求 /bpm/process-definition/list 接口 => 成功
GET {{baseUrl}}/bpm/process-definition/list?suspensionState=1
tenant-id: 1
Authorization: Bearer {{token}}

View File

@ -1,60 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 流程定义")
@RestController
@RequestMapping("/bpm/process-definition")
@Validated
public class BpmProcessDefinitionController {
@Resource
private BpmProcessDefinitionService bpmDefinitionService;
@GetMapping ("/page")
@ApiOperation(value = "获得流程定义分页")
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
public CommonResult<PageResult<BpmProcessDefinitionPageItemRespVO>> getProcessDefinitionPage(
BpmProcessDefinitionPageReqVO pageReqVO) {
return success(bpmDefinitionService.getProcessDefinitionPage(pageReqVO));
}
@GetMapping ("/list")
@ApiOperation(value = "获得流程定义列表")
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
public CommonResult<List<BpmProcessDefinitionRespVO>> getProcessDefinitionList(
BpmProcessDefinitionListReqVO listReqVO) {
return success(bpmDefinitionService.getProcessDefinitionList(listReqVO));
}
@GetMapping ("/get-bpmn-xml")
@ApiOperation(value = "获得流程定义的 BPMN XML")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:process-definition:query')")
public CommonResult<String> getProcessDefinitionBpmnXML(@RequestParam("id") String id) {
String bpmnXML = bpmDefinitionService.getProcessDefinitionBpmnXML(id);
return success(bpmnXML);
}
}

View File

@ -1,4 +0,0 @@
### 请求 /bpm/task-assign-rule/list 接口 => 成功
GET {{baseUrl}}/bpm/task-assign-rule/list?processDefinitionId=leave:9:59689ba0-7284-11ec-965c-a2380e71991a
tenant-id: 1
Authorization: Bearer {{token}}

View File

@ -1,59 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 任务分配规则")
@RestController
@RequestMapping("/bpm/task-assign-rule")
@Validated
public class BpmTaskAssignRuleController {
@Resource
private BpmTaskAssignRuleService taskAssignRuleService;
@GetMapping("/list")
@ApiOperation(value = "获得任务分配规则列表")
@ApiImplicitParams({
@ApiImplicitParam(name = "modelId", value = "模型编号", example = "1024", dataTypeClass = String.class),
@ApiImplicitParam(name = "processDefinitionId", value = "流程定义的编号", example = "2048", dataTypeClass = String.class)
})
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:query')")
public CommonResult<List<BpmTaskAssignRuleRespVO>> getTaskAssignRuleList(
@RequestParam(value = "modelId", required = false) String modelId,
@RequestParam(value = "processDefinitionId", required = false) String processDefinitionId) {
return success(taskAssignRuleService.getTaskAssignRuleList(modelId, processDefinitionId));
}
@PostMapping("/create")
@ApiOperation(value = "创建任务分配规则")
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:create')")
public CommonResult<Long> createTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleCreateReqVO reqVO) {
return success(taskAssignRuleService.createTaskAssignRule(reqVO));
}
@PutMapping("/update")
@ApiOperation(value = "更新任务分配规则")
@PreAuthorize("@ss.hasPermission('bpm:task-assign-rule:update')")
public CommonResult<Boolean> updateTaskAssignRule(@Valid @RequestBody BpmTaskAssignRuleUpdateReqVO reqVO) {
taskAssignRuleService.updateTaskAssignRule(reqVO);
return success(true);
}
}

View File

@ -1,55 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 流程活动实例")
@RestController
@RequestMapping("/bpm/activity")
@Validated
public class BpmActivityController {
@Resource
private BpmActivityService activityService;
@GetMapping("/list")
@ApiOperation(value = "生成指定流程实例的高亮流程图",
notes = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
@ApiImplicitParam(name = "processInstanceId", value = "流程实例的编号", required = true, dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<List<BpmActivityRespVO>> getActivityList(
@RequestParam("processInstanceId") String processInstanceId) {
return success(activityService.getActivityListByProcessInstanceId(processInstanceId));
}
@GetMapping("/generate-highlight-diagram")
@ApiOperation(value = "生成指定流程实例的高亮流程图",
notes = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
@ApiImplicitParam(name = "processInstanceId", value = "流程实例的编号", required = true, dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public void generateHighlightDiagram(@RequestParam("processInstanceId") String processInstanceId,
HttpServletResponse response) throws IOException {
byte[] bytes = activityService.generateHighlightDiagram(processInstanceId);
ServletUtils.writeAttachment(response, StrUtil.format("流程图-{}.svg", processInstanceId), bytes);
}
}

View File

@ -1,29 +0,0 @@
### 请求 /bpm/process-instance/create 接口 => 成功
POST {{baseUrl}}/bpm/process-instance/create
Content-Type: application/json
tenant-id: 1
Authorization: Bearer {{token}}
{
"processDefinitionId": "gateway_test:2:00e52d8e-701b-11ec-aca9-a2380e71991a",
"variables": {
"a": 1,
"b": "2"
}
}
### 请求 /bpm/process-instance/cancel 接口 => 成功
DELETE {{baseUrl}}/bpm/process-instance/cancel
Content-Type: application/json
tenant-id: 1
Authorization: Bearer {{token}}
{
"id": "b9220387-7088-11ec-bcae-a2380e71991a",
"reason": "我就取消"
}
### 请求 /bpm/process-instance/get 接口 => 成功
GET {{baseUrl}}/bpm/process-instance/get?id=537cceb3-768c-11ec-afcd-a2380e71991a
tenant-id: 1
Authorization: Bearer {{token}}

View File

@ -1,60 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "管理后台 - 流程实例") // 流程实例通过流程定义创建的一次申请
@RestController
@RequestMapping("/bpm/process-instance")
@Validated
public class BpmProcessInstanceController {
@Resource
private BpmProcessInstanceService processInstanceService;
@PostMapping("/create")
@ApiOperation("新建流程实例")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
public CommonResult<String> createProcessInstance(@Valid @RequestBody BpmProcessInstanceCreateReqVO createReqVO) {
return success(processInstanceService.createProcessInstance(getLoginUserId(), createReqVO));
}
@DeleteMapping("/cancel")
@ApiOperation(value = "取消流程实例", notes = "撤回发起的流程")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:cancel')")
public CommonResult<Boolean> cancelProcessInstance(@Valid @RequestBody BpmProcessInstanceCancelReqVO cancelReqVO) {
processInstanceService.cancelProcessInstance(getLoginUserId(), cancelReqVO);
return success(true);
}
@GetMapping("/my-page")
@ApiOperation(value = "获得我的实例分页列表", notes = "在【我的流程】菜单中,进行调用")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
public CommonResult<PageResult<BpmProcessInstancePageItemRespVO>> getMyProcessInstancePage(
@Valid BpmProcessInstanceMyPageReqVO pageReqVO) {
return success(processInstanceService.getMyProcessInstancePage(getLoginUserId(), pageReqVO));
}
@GetMapping("/get")
@ApiOperation(value = "获得指定流程实例", notes = "在【流程详细】界面中,进行调用")
@ApiImplicitParam(name = "id", value = "流程实例的编号", required = true, dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
public CommonResult<BpmProcessInstanceRespVO> getProcessInstance(@RequestParam("id") String id) {
return success(processInstanceService.getProcessInstanceVO(id));
}
}

View File

@ -1,14 +0,0 @@
### 请求 /bpm/task/todo-page 接口 => 成功
GET {{baseUrl}}/bpm/task/todo-page
tenant-id: 1
Authorization: Bearer {{token}}
### 请求 /bpm/task/done-page 接口 => 成功
GET {{baseUrl}}/bpm/task/done-page?pageSize=100
tenant-id: 1
Authorization: Bearer {{token}}
### 请求 /bpm/task/list-by-process-instance-id 接口 => 成功
GET {{baseUrl}}/bpm/task/list-by-process-instance-id?processInstanceId=537cceb3-768c-11ec-afcd-a2380e71991a
tenant-id: 1
Authorization: Bearer {{token}}

View File

@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.bpm.controller.app;

View File

@ -1,6 +0,0 @@
/**
* 提供 RESTful API 给前端
* 1. admin 提供给管理后台 yudao-ui-admin 前端项目
* 2. app 提供给用户 APP yudao-ui-app 前端项目它的 Controller VO 都要添加 App 前缀用于和管理后台进行区分
*/
package cn.iocoder.yudao.module.bpm.controller;

View File

@ -1,142 +0,0 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import org.activiti.engine.impl.persistence.entity.SuspensionState;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
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 List<BpmModelPageItemRespVO> convertList(List<Model> list, Map<Long, BpmFormDO> formMap,
Map<String, Deployment> deploymentMap,
Map<String, ProcessDefinition> processDefinitionMap) {
return CollectionUtils.convertList(list, model -> {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
return convert(model, form, deployment, processDefinition);
});
}
default BpmModelPageItemRespVO convert(Model model, BpmFormDO form, Deployment deployment, ProcessDefinition processDefinition) {
BpmModelPageItemRespVO modelRespVO = new BpmModelPageItemRespVO();
modelRespVO.setId(model.getId());
modelRespVO.setCreateTime(model.getCreateTime());
// 通用 copy
copyTo(model, modelRespVO);
// Form
if (form != null) {
modelRespVO.setFormId(form.getId());
modelRespVO.setFormName(form.getName());
}
// ProcessDefinition
modelRespVO.setProcessDefinition(this.convert(processDefinition));
if (modelRespVO.getProcessDefinition() != null) {
modelRespVO.getProcessDefinition().setSuspensionState(processDefinition.isSuspended() ?
SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
modelRespVO.getProcessDefinition().setDeploymentTime(deployment.getDeploymentTime());
}
return modelRespVO;
}
default BpmModelRespVO convert(Model model) {
BpmModelRespVO modelRespVO = new BpmModelRespVO();
modelRespVO.setId(model.getId());
modelRespVO.setCreateTime(model.getCreateTime());
// 通用 copy
copyTo(model, modelRespVO);
return modelRespVO;
}
default void copyTo(Model model, BpmModelBaseVO to) {
to.setName(model.getName());
to.setKey(model.getKey());
to.setCategory(model.getCategory());
// metaInfo
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
copyTo(metaInfo, to);
}
void copyTo(BpmModelMetaInfoRespDTO from, @MappingTarget BpmModelBaseVO to);
default BpmProcessDefinitionCreateReqDTO convert2(Model model, BpmFormDO form) {
BpmProcessDefinitionCreateReqDTO createReqDTO = new BpmProcessDefinitionCreateReqDTO();
createReqDTO.setModelId(model.getId());
createReqDTO.setName(model.getName());
createReqDTO.setKey(model.getKey());
createReqDTO.setCategory(model.getCategory());
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
// metaInfo
copyTo(metaInfo, createReqDTO);
// form
if (form != null) {
createReqDTO.setFormConf(form.getConf());
createReqDTO.setFormFields(form.getFields());
}
return createReqDTO;
}
void copyTo(BpmModelMetaInfoRespDTO from, @MappingTarget BpmProcessDefinitionCreateReqDTO to);
default void copy(Model model, BpmModelCreateReqVO bean) {
model.setName(bean.getName());
model.setKey(bean.getKey());
model.setMetaInfo(buildMetaInfoStr(null, bean.getDescription(), null, null,
null, null));
}
default void copy(Model model, BpmModelUpdateReqVO bean) {
model.setName(bean.getName());
model.setCategory(bean.getCategory());
model.setMetaInfo(buildMetaInfoStr(JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class),
bean.getDescription(), bean.getFormType(), bean.getFormId(),
bean.getFormCustomCreatePath(), bean.getFormCustomViewPath()));
}
default String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, String description, Integer formType,
Long formId, String formCustomCreatePath, String formCustomViewPath) {
if (metaInfo == null) {
metaInfo = new BpmModelMetaInfoRespDTO();
}
// 只有非空才进行设置避免更新时的覆盖
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);
}
BpmModelPageItemRespVO.ProcessDefinition convert(ProcessDefinition bean);
BpmModelCreateReqVO convert(BpmModeImportReqVO bean);
}

View File

@ -1,83 +0,0 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import org.activiti.engine.impl.persistence.entity.SuspensionState;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Named;
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 List<BpmProcessDefinitionPageItemRespVO> convertList(List<ProcessDefinition> list, Map<String, Deployment> deploymentMap,
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap, Map<Long, BpmFormDO> formMap) {
return CollectionUtils.convertList(list, definition -> {
Deployment deployment = definition.getDeploymentId() != null ? deploymentMap.get(definition.getDeploymentId()) : null;
BpmProcessDefinitionExtDO definitionDO = processDefinitionDOMap.get(definition.getId());
BpmFormDO form = definitionDO != null ? formMap.get(definitionDO.getFormId()) : null;
return convert(definition, deployment, definitionDO, form);
});
}
default BpmProcessDefinitionPageItemRespVO convert(ProcessDefinition bean, Deployment deployment,
BpmProcessDefinitionExtDO processDefinitionExtDO, BpmFormDO form) {
BpmProcessDefinitionPageItemRespVO respVO = convert(bean);
respVO.setSuspensionState(bean.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
if (deployment != null) {
respVO.setDeploymentTime(deployment.getDeploymentTime());
}
if (form != null) {
respVO.setFormName(form.getName());
}
// 复制通用属性
copyTo(processDefinitionExtDO, respVO);
return respVO;
}
BpmProcessDefinitionPageItemRespVO convert(ProcessDefinition bean);
BpmProcessDefinitionExtDO convert2(BpmProcessDefinitionCreateReqDTO bean);
default List<BpmProcessDefinitionRespVO> convertList3(List<ProcessDefinition> list,
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap) {
return CollectionUtils.convertList(list, processDefinition -> {
BpmProcessDefinitionRespVO respVO = convert3(processDefinition);
BpmProcessDefinitionExtDO processDefinitionExtDO = processDefinitionDOMap.get(processDefinition.getId());
// 复制通用属性
copyTo(processDefinitionExtDO, respVO);
return respVO;
});
}
@Mapping(source = "suspended", target = "suspensionState", qualifiedByName = "convertSuspendedToSuspensionState")
BpmProcessDefinitionRespVO convert3(ProcessDefinition bean);
@Named("convertSuspendedToSuspensionState")
default Integer convertSuspendedToSuspensionState(boolean suspended) {
return suspended ? SuspensionState.SUSPENDED.getStateCode() :
SuspensionState.ACTIVE.getStateCode();
}
@Mapping(source = "from.id", target = "to.id", ignore = true)
void copyTo(BpmProcessDefinitionExtDO from, @MappingTarget BpmProcessDefinitionRespVO to);
}

View File

@ -1,42 +0,0 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import org.activiti.bpmn.model.UserTask;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
@Mapper
public interface BpmTaskAssignRuleConvert {
BpmTaskAssignRuleConvert INSTANCE = Mappers.getMapper(BpmTaskAssignRuleConvert.class);
default List<BpmTaskAssignRuleRespVO> convertList(List<UserTask> tasks, List<BpmTaskAssignRuleDO> rules) {
Map<String, BpmTaskAssignRuleDO> ruleMap = CollectionUtils.convertMap(rules, BpmTaskAssignRuleDO::getTaskDefinitionKey);
// UserTask 为主维度原因是流程图编辑后一些规则实际就没用了
return CollectionUtils.convertList(tasks, task -> {
BpmTaskAssignRuleRespVO respVO = convert(ruleMap.get(task.getId()));
if (respVO == null) {
respVO = new BpmTaskAssignRuleRespVO();
respVO.setTaskDefinitionKey(task.getId());
}
respVO.setTaskDefinitionName(task.getName());
return respVO;
});
}
BpmTaskAssignRuleRespVO convert(BpmTaskAssignRuleDO bean);
BpmTaskAssignRuleDO convert(BpmTaskAssignRuleCreateReqVO bean);
BpmTaskAssignRuleDO convert(BpmTaskAssignRuleUpdateReqVO bean);
List<BpmTaskAssignRuleDO> convertList2(List<BpmTaskAssignRuleRespVO> list);
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
import org.activiti.engine.history.HistoricActivityInstance;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* BPM 活动 Convert
*
* @author 芋道源码
*/
@Mapper
public interface BpmActivityConvert {
BpmActivityConvert INSTANCE = Mappers.getMapper(BpmActivityConvert.class);
List<BpmActivityRespVO> convertList(List<HistoricActivityInstance> list);
@Mappings({
@Mapping(source = "activityId", target = "key"),
@Mapping(source = "activityType", target = "type")
})
BpmActivityRespVO convert(HistoricActivityInstance bean);
}

View File

@ -1,135 +0,0 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstancePageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
* 流程实例 Convert
*
* @author 芋道源码
*/
@Mapper
public interface BpmProcessInstanceConvert {
BpmProcessInstanceConvert INSTANCE = Mappers.getMapper(BpmProcessInstanceConvert.class);
default BpmProcessInstanceExtDO convert3(ProcessInstance instance, ProcessDefinition definition) {
BpmProcessInstanceExtDO ext = new BpmProcessInstanceExtDO();
copyTo(instance, ext);
copyTo(definition, ext);
return ext;
}
@Mappings({
@Mapping(source = "from.id", target = "id", ignore = true),
@Mapping(source = "from.startTime", target = "createTime"),
})
void copyTo(ProcessInstance from, @MappingTarget BpmProcessInstanceExtDO to);
@Mapping(source = "from.id", target = "id", ignore = true)
void copyTo(ProcessDefinition from, @MappingTarget BpmProcessInstanceExtDO to);
default PageResult<BpmProcessInstancePageItemRespVO> convertPage(PageResult<BpmProcessInstanceExtDO> page,
Map<String, List<Task>> taskMap) {
List<BpmProcessInstancePageItemRespVO> list = convertList(page.getList());
list.forEach(respVO -> respVO.setTasks(convertList2(taskMap.get(respVO.getId()))));
return new PageResult<>(list, page.getTotal());
}
List<BpmProcessInstancePageItemRespVO> convertList(List<BpmProcessInstanceExtDO> list);
List<BpmProcessInstancePageItemRespVO.Task> convertList2(List<Task> tasks);
@Mapping(source = "processInstanceId", target = "id")
BpmProcessInstancePageItemRespVO convert(BpmProcessInstanceExtDO bean);
@Mappings({
@Mapping(source = "id", target = "processInstanceId"),
@Mapping(source = "id", target = "id", ignore = true),
@Mapping(source = "startDate", target = "createTime"),
@Mapping(source = "initiator", target = "startUserId"),
@Mapping(source = "status", target = "status", ignore = true)
})
BpmProcessInstanceExtDO convert(org.activiti.api.process.model.ProcessInstance bean);
default BpmProcessInstanceRespVO convert2(HistoricProcessInstance processInstance, BpmProcessInstanceExtDO processInstanceExt,
ProcessDefinition processDefinition, BpmProcessDefinitionExtDO processDefinitionExt,
String bpmnXml, AdminUserRespDTO startUser, DeptRespDTO dept) {
BpmProcessInstanceRespVO respVO = convert2(processInstance);
copyTo(processInstanceExt, respVO);
// definition
respVO.setProcessDefinition(convert2(processDefinition));
copyTo(processDefinitionExt, respVO.getProcessDefinition());
respVO.getProcessDefinition().setBpmnXml(bpmnXml);
// user
if (startUser != null) {
respVO.setStartUser(convert2(startUser));
if (dept != null) {
respVO.getStartUser().setDeptName(dept.getName());
}
}
return respVO;
}
BpmProcessInstanceRespVO convert2(HistoricProcessInstance bean);
@Mapping(source = "from.id", target = "to.id", ignore = true)
void copyTo(BpmProcessInstanceExtDO from, @MappingTarget BpmProcessInstanceRespVO to);
BpmProcessInstanceRespVO.ProcessDefinition convert2(ProcessDefinition bean);
@Mapping(source = "from.id", target = "to.id", ignore = true)
void copyTo(BpmProcessDefinitionExtDO from, @MappingTarget BpmProcessInstanceRespVO.ProcessDefinition to);
BpmProcessInstanceRespVO.User convert2(AdminUserRespDTO bean);
default BpmProcessInstanceResultEvent convert(Object source, ProcessInstance instance, Integer result) {
BpmProcessInstanceResultEvent event = new BpmProcessInstanceResultEvent(source);
event.setId(instance.getId());
event.setProcessDefinitionKey(instance.getProcessDefinitionKey());
event.setBusinessKey(instance.getBusinessKey());
event.setResult(result);
return event;
}
default BpmProcessInstanceResultEvent convert(Object source, HistoricProcessInstance instance, Integer result) {
BpmProcessInstanceResultEvent event = new BpmProcessInstanceResultEvent(source);
event.setId(instance.getId());
event.setProcessDefinitionKey(instance.getProcessDefinitionKey());
event.setBusinessKey(instance.getBusinessKey());
event.setResult(result);
return event;
}
default BpmMessageSendWhenProcessInstanceRejectReqDTO convert(ProcessInstance processInstance, String reason) {
BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO = new BpmMessageSendWhenProcessInstanceRejectReqDTO();
copyTo(processInstance, reqDTO);
reqDTO.setReason(reason);
return reqDTO;
}
@Mapping(source = "name", target = "processInstanceName")
void copyTo(ProcessInstance from, @MappingTarget BpmMessageSendWhenProcessInstanceRejectReqDTO to);
@Mappings({
@Mapping(source = "id", target = "processInstanceId"),
@Mapping(source = "name", target = "processInstanceName"),
@Mapping(source = "initiator", target = "startUserId")
})
BpmMessageSendWhenProcessInstanceApproveReqDTO convert2(org.activiti.api.process.model.ProcessInstance processInstance);
}

View File

@ -1,149 +0,0 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskDonePageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskTodoPageItemRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.persistence.entity.SuspensionState;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.Map;
/**
* Bpm 任务 Convert
*
* @author 芋道源码
*/
@Mapper
public interface BpmTaskConvert {
BpmTaskConvert INSTANCE = Mappers.getMapper(BpmTaskConvert.class);
default List<BpmTaskTodoPageItemRespVO> convertList1(List<Task> tasks, Map<String, ProcessInstance> processInstanceMap,
Map<Long, AdminUserRespDTO> userMap) {
return CollectionUtils.convertList(tasks, task -> {
BpmTaskTodoPageItemRespVO respVO = convert1(task);
ProcessInstance processInstance = processInstanceMap.get(task.getProcessInstanceId());
if (processInstance != null) {
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
respVO.setProcessInstance(convert(processInstance, startUser));
}
return respVO;
});
}
@Mapping(source = "suspended", target = "suspensionState", qualifiedByName = "convertSuspendedToSuspensionState")
BpmTaskTodoPageItemRespVO convert1(Task bean);
@Named("convertSuspendedToSuspensionState")
default Integer convertSuspendedToSuspensionState(boolean suspended) {
return suspended ? SuspensionState.SUSPENDED.getStateCode() :
SuspensionState.ACTIVE.getStateCode();
}
default List<BpmTaskDonePageItemRespVO> convertList2(List<HistoricTaskInstance> tasks, Map<String, BpmTaskExtDO> bpmTaskExtDOMap,
Map<String, HistoricProcessInstance> historicProcessInstanceMap,
Map<Long, AdminUserRespDTO> userMap) {
return CollectionUtils.convertList(tasks, task -> {
BpmTaskDonePageItemRespVO respVO = convert2(task);
BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
copyTo(taskExtDO, respVO);
HistoricProcessInstance processInstance = historicProcessInstanceMap.get(task.getProcessInstanceId());
if (processInstance != null) {
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
respVO.setProcessInstance(convert(processInstance, startUser));
}
return respVO;
});
}
BpmTaskDonePageItemRespVO convert2(HistoricTaskInstance bean);
@Mappings({
@Mapping(source = "id", target = "taskId"),
@Mapping(source = "assignee", target = "assigneeUserId"),
@Mapping(source = "createdDate", target = "createTime"),
@Mapping(target = "id", ignore = true)
})
BpmTaskExtDO convert(org.activiti.api.task.model.Task bean);
default List<BpmTaskRespVO> convertList3(List<HistoricTaskInstance> tasks, Map<String, BpmTaskExtDO> bpmTaskExtDOMap,
HistoricProcessInstance processInstance, Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
return CollectionUtils.convertList(tasks, task -> {
BpmTaskRespVO respVO = convert3(task);
BpmTaskExtDO taskExtDO = bpmTaskExtDOMap.get(task.getId());
copyTo(taskExtDO, respVO);
if (processInstance != null) {
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
respVO.setProcessInstance(convert(processInstance, startUser));
}
AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
if (assignUser != null) {
respVO.setAssigneeUser(convert3(assignUser));
DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
if (dept != null) {
respVO.getAssigneeUser().setDeptName(dept.getName());
}
}
return respVO;
});
}
@Mapping(source = "taskDefinitionKey", target = "definitionKey")
BpmTaskRespVO convert3(HistoricTaskInstance bean);
BpmTaskRespVO.User convert3(AdminUserRespDTO bean);
@Mapping(target = "id", ignore = true)
void copyTo(BpmTaskExtDO from, @MappingTarget BpmTaskDonePageItemRespVO to);
@Mappings({
@Mapping(source = "processInstance.id", target = "id"),
@Mapping(source = "processInstance.name", target = "name"),
@Mapping(source = "processInstance.startUserId", target = "startUserId"),
@Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
@Mapping(source = "startUser.nickname", target = "startUserNickname")
})
BpmTaskTodoPageItemRespVO.ProcessInstance convert(ProcessInstance processInstance, AdminUserRespDTO startUser);
@Mappings({
@Mapping(source = "processInstance.id", target = "id"),
@Mapping(source = "processInstance.name", target = "name"),
@Mapping(source = "processInstance.startUserId", target = "startUserId"),
@Mapping(source = "processInstance.processDefinitionId", target = "processDefinitionId"),
@Mapping(source = "startUser.nickname", target = "startUserNickname")
})
BpmTaskTodoPageItemRespVO.ProcessInstance convert(HistoricProcessInstance processInstance, AdminUserRespDTO startUser);
default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser, org.activiti.api.task.model.Task task) {
BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
copyTo(processInstance, reqDTO);
copyTo(startUser, reqDTO);
copyTo(task, reqDTO);
return reqDTO;
}
@Mapping(source = "name", target = "processInstanceName")
void copyTo(ProcessInstance from, @MappingTarget BpmMessageSendWhenTaskCreatedReqDTO to);
@Mappings({
@Mapping(source = "id", target = "startUserId"),
@Mapping(source = "nickname", target = "startUserNickname")
})
void copyTo(AdminUserRespDTO from, @MappingTarget BpmMessageSendWhenTaskCreatedReqDTO to);
@Mappings({
@Mapping(source = "id", target = "taskId"),
@Mapping(source = "name", target = "taskName"),
@Mapping(source = "assignee", target = "assigneeUserId")
})
void copyTo(org.activiti.api.task.model.Task task, @MappingTarget BpmMessageSendWhenTaskCreatedReqDTO to);
}

View File

@ -1,80 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.config;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.BpmActivityBehaviorFactory;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.identity.EmptyUserGroupManager;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.listener.BpmTackActivitiEventListener;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.activiti.api.runtime.shared.identity.UserGroupManager;
import org.activiti.spring.boot.ProcessEngineConfigurationConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collections;
import java.util.List;
import static org.activiti.spring.boot.ProcessEngineAutoConfiguration.BEHAVIOR_FACTORY_MAPPING_CONFIGURER;
/**
* BPM 模块的 Activiti 配置类
*/
@Configuration
public class BpmActivitiConfiguration {
/**
* 空用户组的 Bean
*/
@Bean
public UserGroupManager userGroupManager() {
return new EmptyUserGroupManager();
}
/**
* BPM 模块的 ProcessEngineConfigurationConfigurer 实现类主要设置各种监听器
*/
@Bean
public ProcessEngineConfigurationConfigurer bpmProcessEngineConfigurationConfigurer(
BpmTackActivitiEventListener taskActivitiEventListener) {
return configuration -> {
// 注册监听器例如说 BpmActivitiEventListener
configuration.setEventListeners(Collections.singletonList(taskActivitiEventListener));
};
}
/**
* 用于设置自定义的 ActivityBehaviorFactory 实现的 ProcessEngineConfigurationConfigurer 实现类
*
* 目的覆盖 {@link org.activiti.spring.boot.ProcessEngineAutoConfiguration}
* defaultActivityBehaviorFactoryMappingConfigurer 方法创建的 Bean
*/
@Bean(name = BEHAVIOR_FACTORY_MAPPING_CONFIGURER)
public ProcessEngineConfigurationConfigurer defaultActivityBehaviorFactoryMappingConfigurer(
BpmActivityBehaviorFactory bpmActivityBehaviorFactory) {
return configuration -> {
// 设置 ActivityBehaviorFactory 实现类用于流程任务的审核人的自定义
configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory);
};
}
@Bean
public BpmActivityBehaviorFactory bpmActivityBehaviorFactory(BpmTaskAssignRuleService taskRuleService,
BpmUserGroupService userGroupService,
PermissionApi permissionApi,
DeptApi deptApi,
AdminUserApi adminUserApi,
List<BpmTaskAssignScript> scripts) {
BpmActivityBehaviorFactory bpmActivityBehaviorFactory = new BpmActivityBehaviorFactory();
bpmActivityBehaviorFactory.setBpmTaskRuleService(taskRuleService);
bpmActivityBehaviorFactory.setUserGroupService(userGroupService);
bpmActivityBehaviorFactory.setAdminUserApi(adminUserApi);
bpmActivityBehaviorFactory.setPermissionApi(permissionApi);
bpmActivityBehaviorFactory.setDeptApi(deptApi);
bpmActivityBehaviorFactory.setScripts(scripts);
return bpmActivityBehaviorFactory;
}
}

View File

@ -1,61 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
import java.util.List;
/**
* 自定义的 ActivityBehaviorFactory 实现类目的如下
* 1. 自定义 {@link #createUserTaskActivityBehavior(UserTask)}实现自定义的流程任务的 assignee 负责人的分配
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
@Setter
private BpmTaskAssignRuleService bpmTaskRuleService;
@Setter
private BpmUserGroupService userGroupService;
@Setter
private PermissionApi permissionApi;
@Setter
private DeptApi deptApi;
@Setter
private AdminUserApi adminUserApi;
@Setter
private List<BpmTaskAssignScript> scripts;
@Override
public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
BpmUserTaskActivityBehavior userTaskActivityBehavior = new BpmUserTaskActivityBehavior(userTask);
userTaskActivityBehavior.setBpmTaskRuleService(bpmTaskRuleService);
userTaskActivityBehavior.setPermissionApi(permissionApi);
userTaskActivityBehavior.setDeptApi(deptApi);
userTaskActivityBehavior.setUserGroupService(userGroupService);
userTaskActivityBehavior.setAdminUserApi(adminUserApi);
userTaskActivityBehavior.setScripts(scripts);
return userTaskActivityBehavior;
}
// TODO 芋艿并行任务 ParallelMultiInstanceBehavior
// TODO 芋艿并行任务 SequentialMultiInstanceBehavior
}

View File

@ -1,196 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.el.ExpressionManager;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntityManager;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
/**
* 自定义的流程任务的 assignee 负责人的分配
* 第一步获得对应的分配规则
* 第二步根据分配规则计算出分配任务的候选人如果找不到则直接报业务异常不继续执行后续的流程
* 第三步随机选择一个候选人则选择作为 assignee 负责人
*
* @author 芋道源码
*/
@Slf4j
public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
@Setter
private BpmTaskAssignRuleService bpmTaskRuleService;
@Setter
private BpmUserGroupService userGroupService;
@Setter
private DeptApi deptApi;
@Setter
private AdminUserApi adminUserApi;
@Setter
private PermissionApi permissionApi;
/**
* 任务分配脚本
*/
private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();
public BpmUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}
public void setScripts(List<BpmTaskAssignScript> scripts) {
this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
}
@Override
protected void handleAssignments(TaskEntityManager taskEntityManager,
String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups,
TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution) {
// 第一步获得任务的规则
BpmTaskAssignRuleDO rule = getTaskRule(task);
// 第二步获得任务的候选用户们
Set<Long> candidateUserIds = calculateTaskCandidateUsers(task, rule);
// 第三步设置一个作为负责人
Long assigneeUserId = chooseTaskAssignee(candidateUserIds);
taskEntityManager.changeTaskAssignee(task, String.valueOf(assigneeUserId));
}
private BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
List<BpmTaskAssignRuleDO> taskRules = bpmTaskRuleService.getTaskAssignRuleListByProcessDefinitionId(task.getProcessDefinitionId(),
task.getTaskDefinitionKey());
if (CollUtil.isEmpty(taskRules)) {
throw new ActivitiException(StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则",
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey()));
}
if (taskRules.size() > 1) {
throw new ActivitiException(StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})",
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), taskRules.size()));
}
return taskRules.get(0);
}
private Long chooseTaskAssignee(Set<Long> candidateUserIds) {
// TODO 芋艿未来可以优化下改成轮询的策略
int index = RandomUtil.randomInt(candidateUserIds.size());
return CollUtil.get(candidateUserIds, index);
}
@VisibleForTesting
Set<Long> calculateTaskCandidateUsers(TaskEntity task, BpmTaskAssignRuleDO rule) {
Set<Long> assigneeUserIds = null;
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByRole(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByPost(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
}
// 移除被禁用的用户
removeDisableUsers(assigneeUserIds);
// 如果候选人为空抛出异常 TODO 芋艿没候选人的策略选择1 - 挂起2 - 直接结束3 - 强制一个兜底人
if (CollUtil.isEmpty(assigneeUserIds)) {
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]",
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
}
return assigneeUserIds;
}
private Set<Long> calculateTaskCandidateUsersByRole(TaskEntity task, BpmTaskAssignRuleDO rule) {
return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
}
private Set<Long> calculateTaskCandidateUsersByDeptMember(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions());
return convertSet(users, AdminUserRespDTO::getId);
}
private Set<Long> calculateTaskCandidateUsersByDeptLeader(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<DeptRespDTO> depts = deptApi.getDepts(rule.getOptions());
return convertSet(depts, DeptRespDTO::getLeaderUserId);
}
private Set<Long> calculateTaskCandidateUsersByPost(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
return convertSet(users, AdminUserRespDTO::getId);
}
private Set<Long> calculateTaskCandidateUsersByUser(TaskEntity task, BpmTaskAssignRuleDO rule) {
return rule.getOptions();
}
private Set<Long> calculateTaskCandidateUsersByUserGroup(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
Set<Long> userIds = new HashSet<>();
userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds()));
return userIds;
}
private Set<Long> calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
// 获得对应的脚本
List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
rule.getOptions().forEach(id -> {
BpmTaskAssignScript script = scriptMap.get(id);
if (script == null) {
throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id);
}
scripts.add(script);
});
// 逐个计算任务
Set<Long> userIds = new HashSet<>();
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
return userIds;
}
@VisibleForTesting
void removeDisableUsers(Set<Long> assigneeUserIds) {
if (CollUtil.isEmpty(assigneeUserIds)) {
return;
}
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
assigneeUserIds.removeIf(id -> {
AdminUserRespDTO user = userMap.get(id);
return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
});
}
}

View File

@ -1,7 +0,0 @@
/**
* 拓展 {@link org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior} 实现基于 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO} 实现自定义的任务分配规则
* 原因BPMN 默认的 assigncandidateUserscandidateGroups 拓展起来有一定的难度所以选择放弃它们使用自己定义的规则
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior;

View File

@ -1,34 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import java.util.Set;
/**
* Bpm 任务分配的自定义 Script 脚本
* 使用场景
* 1. 设置审批人为发起人
* 2. 设置审批人为发起人的 Leader
* 3. 甚至审批人为发起人的 Leader Leader
*
* @author 芋道源码
*/
public interface BpmTaskAssignScript {
/**
* 基于流程任务获得任务的候选用户们
*
* @param task 任务
* @return 候选人用户的编号数组
*/
Set<Long> calculateTaskCandidateUsers(TaskEntity task);
/**
* 获得枚举值
*
* @return 枚举值
*/
BpmTaskRuleScriptEnum getEnum();
}

View File

@ -1,63 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.springframework.util.Assert;
import javax.annotation.Resource;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static java.util.Collections.emptySet;
/**
* 分配给发起人的 Leader 审批的 Script 实现类
* 目前 Leader 的定义是
*
* @author 芋道源码
*/
public abstract class BpmTaskAssignLeaderAbstractScript implements BpmTaskAssignScript {
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
protected Set<Long> calculateTaskCandidateUsers(TaskEntity task, int level) {
Assert.isTrue(level > 0, "level 必须大于 0");
// 获得发起人
Long startUserId = Long.parseLong(task.getProcessInstance().getStartUserId());
// 获得对应 leve 的部门
DeptRespDTO dept = null;
for (int i = 0; i < level; i++) {
// 获得 level 对应的部门
if (dept == null) {
dept = getStartUserDept(startUserId);
if (dept == null) { // 找不到发起人的部门所以无法使用该规则
return emptySet();
}
} else {
DeptRespDTO parentDept = deptApi.getDept(dept.getParentId());
if (parentDept == null) { // 找不到父级部门所以只好结束寻找原因是例如说级别比较高的人所在部门层级比较少
break;
}
dept = parentDept;
}
}
return dept.getLeaderUserId() != null ? asSet(dept.getLeaderUserId()) : emptySet();
}
private DeptRespDTO getStartUserDept(Long startUserId) {
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId);
if (startUser.getDeptId() == null) { // 找不到部门所以无法使用该规则
return null;
}
return deptApi.getDept(startUser.getDeptId());
}
}

View File

@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 分配给发起人的一级 Leader 审批的 Script 实现类
*
* @author 芋道源码
*/
@Component
public class BpmTaskAssignLeaderX1Script extends BpmTaskAssignLeaderAbstractScript {
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
return calculateTaskCandidateUsers(task, 1);
}
@Override
public BpmTaskRuleScriptEnum getEnum() {
return BpmTaskRuleScriptEnum.LEADER_X1;
}
}

View File

@ -1,29 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 分配给发起人的二级 Leader 审批的 Script 实现类
*
* @author 芋道源码
*/
@Component
public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
return calculateTaskCandidateUsers(task, 2);
}
@Override
public BpmTaskRuleScriptEnum getEnum() {
return BpmTaskRuleScriptEnum.LEADER_X2;
}
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.impl;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import cn.iocoder.yudao.module.bpm.framework.activiti.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.springframework.stereotype.Component;
import java.util.Set;
/**
* 分配给发起人审批的 Script 实现类
*
* @author 芋道源码
*/
@Component
public class BpmTaskAssignStartUserScript implements BpmTaskAssignScript {
@Override
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {
Long userId = Long.parseLong(task.getProcessInstance().getStartUserId());
return SetUtils.asSet(userId);
}
@Override
public BpmTaskRuleScriptEnum getEnum() {
return BpmTaskRuleScriptEnum.START_USER;
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.identity;
import org.activiti.api.runtime.shared.identity.UserGroupManager;
import java.util.Collections;
import java.util.List;
/**
* 空的 UserGroupManager 实现类用于禁用 Activiti 自带的用户组实现
* 原因是我们使用了自己实现的任务分配规则所以不需要 Activiti
* 如果不去禁用会存在一些场景下会去查询用户所在的用户组导致报错
*
* @author 芋道源码
*/
public class EmptyUserGroupManager implements UserGroupManager {
@Override
public List<String> getUserGroups(String s) {
return Collections.emptyList();
}
@Override
public List<String> getUserRoles(String s) {
return Collections.emptyList();
}
@Override
public List<String> getGroups() {
return Collections.emptyList();
}
@Override
public List<String> getUsers() {
return Collections.emptyList();
}
}

View File

@ -1,58 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import org.activiti.api.model.shared.event.RuntimeEvent;
import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.events.ProcessRuntimeEvent;
import org.activiti.api.process.runtime.events.ProcessCancelledEvent;
import org.activiti.api.process.runtime.events.listener.ProcessRuntimeEventListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 监听 {@link ProcessInstance} 的开始与完成创建与更新对应的 {@link BpmProcessInstanceExtDO} 记录
*
* @author 芋道源码
*/
@Component
public class BpmProcessInstanceEventListener<T extends RuntimeEvent<?, ?>>
implements ProcessRuntimeEventListener<T> {
@Resource
@Lazy // 解决循环依赖
private BpmProcessInstanceService processInstanceService;
@Override
@SuppressWarnings("unchecked")
public void onEvent(T rawEvent) {
// 由于 ProcessRuntimeEventListener 无法保证只监听 ProcessRuntimeEvent 事件所以通过这样的方式
if (!(rawEvent instanceof ProcessRuntimeEvent)) {
return;
}
ProcessRuntimeEvent<ProcessInstance> event = (ProcessRuntimeEvent<ProcessInstance>) rawEvent;
// 创建时插入拓展表
if (event.getEventType() == ProcessRuntimeEvent.ProcessEvents.PROCESS_CREATED) {
processInstanceService.createProcessInstanceExt(event.getEntity());
return;
}
// 取消时更新拓展表为取消
if (event.getEventType() == ProcessRuntimeEvent.ProcessEvents.PROCESS_CANCELLED) {
processInstanceService.updateProcessInstanceExtCancel(event.getEntity(),
((ProcessCancelledEvent) event).getCause());
return;
}
// 完成时更新拓展表为已完成
if (event.getEventType() == ProcessRuntimeEvent.ProcessEvents.PROCESS_COMPLETED) {
processInstanceService.updateProcessInstanceExtComplete(event.getEntity());
return;
}
// 其它事件进行更新拓展表
processInstanceService.updateProcessInstanceExt(event.getEntity());
}
}

View File

@ -1,52 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import org.activiti.api.task.runtime.events.listener.TaskEventListener;
import org.activiti.engine.delegate.event.ActivitiEvent;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.delegate.event.impl.ActivitiEntityEventImpl;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.repository.ProcessDefinition;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 监听 {@link TaskEntity} 相关的事件设置相关属性
* 目的解决 {@link TaskEventListener} 无法解决的场景
*
* @author 芋道源码
*/
@Component
public class BpmTackActivitiEventListener implements ActivitiEventListener {
@Resource
@Lazy // 解决循环依赖
private BpmProcessDefinitionService processDefinitionService;
@Override
public void onEvent(ActivitiEvent event) {
// Task 创建时设置其分类解决 TaskService 未提供 name 的设置方法
if (ActivitiEventType.TASK_CREATED == event.getType()) {
TaskEntity task = ((TaskEntity) ((ActivitiEntityEventImpl) event).getEntity());
if (StrUtil.isNotEmpty(task.getCategory())) {
return;
}
// 设置 name
ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition2(task.getProcessDefinitionId());
if (processDefinition == null) {
return;
}
task.setCategory(processDefinition.getCategory());
}
}
@Override
public boolean isFailOnException() {
return true;
}
}

View File

@ -1,63 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import org.activiti.api.model.shared.event.RuntimeEvent;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.events.TaskRuntimeEvent;
import org.activiti.api.task.runtime.events.listener.TaskRuntimeEventListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 监听 {@link Task} 的开始与完成创建与更新对应的 {@link BpmTaskExtDO} 记录
*
* @author 芋道源码
*/
@Component
public class BpmTaskEventListener<T extends RuntimeEvent<?, ?>>
implements TaskRuntimeEventListener<T> {
@Resource
@Lazy // 解决循环依赖
private BpmTaskService taskService;
@Override
@SuppressWarnings("unchecked")
public void onEvent(T rawEvent) {
// 由于 TaskRuntimeEventListener 无法保证只监听 TaskRuntimeEvent 事件所以通过这样的方式
if (!(rawEvent instanceof TaskRuntimeEvent)) {
return;
}
TaskRuntimeEvent<Task> event = (TaskRuntimeEvent<Task>) rawEvent;
// 创建时插入拓展表
if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_CREATED) {
taskService.createTaskExt(event.getEntity());
return;
}
// 取消时更新拓展表为取消
if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_CANCELLED) {
taskService.updateTaskExtCancel(event.getEntity());
return;
}
// 完成时更新拓展表为已完成要注意在调用 delete ProcessInstance 才会触发该逻辑
if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_COMPLETED) {
taskService.updateTaskExtComplete(event.getEntity());
return;
}
// 审核人修改时进行拓展表并额外发送通知
if (event.getEventType() == TaskRuntimeEvent.TaskEvents.TASK_ASSIGNED) {
taskService.updateTaskExtAssign(event.getEntity());
return;
}
// 其它事件进行更新拓展表
taskService.updateTaskExt(event.getEntity());
}
}

View File

@ -1,7 +0,0 @@
/**
* 自定义各种 Activiti 的监听器实现流程示例流程任务的拓展表信息的同步
* 例如说{@link org.activiti.api.task.model.Task} 新建时我们也要新建对应的 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO} 记录
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bpm.framework.activiti.core.listener;

View File

@ -1,6 +0,0 @@
/**
* 属于 bpm 模块的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bpm.framework;

View File

@ -1,77 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import org.activiti.bpmn.model.BpmnModel;
import javax.validation.Valid;
/**
* 流程模型接口
*
* @author yunlongn
*/
public interface BpmModelService {
/**
* 获得流程模型分页
*
* @param pageVO 分页查询
* @return 流程模型分页
*/
PageResult<BpmModelPageItemRespVO> getModelPage(BpmModelPageReqVO pageVO);
/**
* 创建流程模型
*
* @param modelVO 创建信息
* @param bpmnXml BPMN XML
* @return 创建的流程模型的编号
*/
String createModel(@Valid BpmModelCreateReqVO modelVO, String bpmnXml);
/**
* 获得流程模块
*
* @param id 编号
* @return 流程模型
*/
BpmModelRespVO getModel(String id);
/**
* 修改流程模型
*
* @param updateReqVO 更新信息
*/
void updateModel(@Valid BpmModelUpdateReqVO updateReqVO);
/**
* 将流程模型部署成一个流程定义
*
* @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 id 流程模型编号
* @return BPMN Model
*/
BpmnModel getBpmnModel(String id);
}

View File

@ -1,282 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.persistence.entity.SuspensionState;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.activiti.engine.repository.ProcessDefinition;
import org.springframework.context.annotation.Lazy;
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 java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* 流程定义实现
* 主要进行 Activiti {@link Model} 的维护
*
* @author yunlongn
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmModelServiceImpl implements BpmModelService {
@Resource
private RepositoryService repositoryService;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
private BpmFormService bpmFormService;
@Resource
private BpmTaskAssignRuleService taskAssignRuleService;
@Override
public PageResult<BpmModelPageItemRespVO> getModelPage(BpmModelPageReqVO pageVO) {
ModelQuery modelQuery = repositoryService.createModelQuery();
if (StrUtil.isNotBlank(pageVO.getKey())) {
modelQuery.modelKey(pageVO.getKey());
}
if (StrUtil.isNotBlank(pageVO.getName())) {
modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配
}
if (StrUtil.isNotBlank(pageVO.getCategory())) {
modelQuery.modelCategory(pageVO.getCategory());
}
// 执行查询
List<Model> models = modelQuery.orderByCreateTime().desc()
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
// 获得 Form Map
Set<Long> formIds = CollectionUtils.convertSet(models, model -> {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
return metaInfo != null ? metaInfo.getFormId() : null;
});
Map<Long, BpmFormDO> formMap = bpmFormService.getFormMap(formIds);
// 获得 Deployment Map
Set<String> deploymentIds = new HashSet<>();
models.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);
// 拼接结果
long modelCount = modelQuery.count();
return new PageResult<>(BpmModelConvert.INSTANCE.convertList(models, formMap, deploymentMap, processDefinitionMap), modelCount);
}
@Override
public BpmModelRespVO getModel(String id) {
Model model = repositoryService.getModel(id);
if (model == null) {
return null;
}
BpmModelRespVO modelRespVO = BpmModelConvert.INSTANCE.convert(model);
// 拼接 bpmn XML
byte[] bpmnBytes = repositoryService.getModelEditorSource(id);
modelRespVO.setBpmnXml(StrUtil.utf8Str(bpmnBytes));
return modelRespVO;
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作所以开启事务
public String createModel(BpmModelCreateReqVO createReqVO, String bpmnXml) {
checkKeyNCName(createReqVO.getKey());
// 校验流程标识已经存在
Model keyModel = this.getModelByKey(createReqVO.getKey());
if (keyModel != null) {
throw exception(MODEL_KEY_EXISTS, createReqVO.getKey());
}
// 创建流程定义
Model model = repositoryService.newModel();
BpmModelConvert.INSTANCE.copy(model, createReqVO);
// 保存流程定义
repositoryService.saveModel(model);
// 保存 BPMN XML
saveModelBpmnXml(model, bpmnXml);
return model.getId();
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作所以开启事务
public void updateModel(BpmModelUpdateReqVO updateReqVO) {
// 校验流程模型存在
Model model = repositoryService.getModel(updateReqVO.getId());
if (model == null) {
throw exception(MODEL_NOT_EXISTS);
}
// 修改流程定义
BpmModelConvert.INSTANCE.copy(model, updateReqVO);
// 更新模型
repositoryService.saveModel(model);
// 更新 BPMN XML
saveModelBpmnXml(model, updateReqVO.getBpmnXml());
}
private void saveModelBpmnXml(Model model, String bpmnXml) {
if (StrUtil.isEmpty(bpmnXml)) {
return;
}
repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作所以开启事务
public void deployModel(String id) {
// 校验流程模型存在
Model model = repositoryService.getModel(id);
if (ObjectUtils.isEmpty(model)) {
throw exception(MODEL_NOT_EXISTS);
}
// 校验流程图
byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
if (bpmnBytes == null) {
throw exception(MODEL_NOT_EXISTS);
}
// TODO 芋艿校验流程图的有效性例如说是否有开始的元素是否有结束的元素
// 校验表单已配
BpmFormDO form = checkFormConfig(model.getMetaInfo());
// 校验任务分配规则已配置
taskAssignRuleService.checkTaskAssignRuleAllConfig(id);
// 校验模型是否发生修改如果未修改则不允许创建
BpmProcessDefinitionCreateReqDTO definitionCreateReqDTO = BpmModelConvert.INSTANCE.convert2(model, form).setBpmnBytes(bpmnBytes);
if (processDefinitionService.isProcessDefinitionEquals(definitionCreateReqDTO)) { // 流程定义的信息相等
ProcessDefinition oldProcessInstance = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
if (oldProcessInstance != null && taskAssignRuleService.isTaskAssignRulesEquals(model.getId(), oldProcessInstance.getId())) {
throw exception(MODEL_DEPLOY_FAIL_TASK_INFO_EQUALS);
}
}
// 创建流程定义
String definitionId = processDefinitionService.createProcessDefinition(definitionCreateReqDTO);
// 将老的流程定义进行挂起也就是说只有最新部署的流程定义才可以发起任务
updateProcessDefinitionSuspended(model.getDeploymentId());
// 更新 model deploymentId进行关联
ProcessDefinition definition = processDefinitionService.getProcessDefinition(definitionId);
model.setDeploymentId(definition.getDeploymentId());
repositoryService.saveModel(model);
// 复制任务分配规则
taskAssignRuleService.copyTaskAssignRules(id, definition.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteModel(String id) {
// 校验流程模型存在
Model model = repositoryService.getModel(id);
if (model == null) {
throw exception(MODEL_NOT_EXISTS);
}
// 执行删除
repositoryService.deleteModel(id);
// 禁用流程实例
updateProcessDefinitionSuspended(model.getDeploymentId());
}
private void updateProcessDefinitionSuspended(String deploymentId) {
if (StrUtil.isEmpty(deploymentId)) {
return;
}
ProcessDefinition oldDefinition = processDefinitionService.getProcessDefinitionByDeploymentId(deploymentId);
if (oldDefinition == null) {
return;
}
if(oldDefinition.isSuspended()) {
return;
}
processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
}
@Override
public void updateModelState(String id, Integer state) {
// 校验流程模型存在
Model model = repositoryService.getModel(id);
if (model == null) {
throw exception(MODEL_NOT_EXISTS);
}
// 校验流程定义存在
ProcessDefinition definition = processDefinitionService.getProcessDefinitionByDeploymentId(model.getDeploymentId());
if (definition == null) {
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
}
// 更新状态
processDefinitionService.updateProcessDefinitionState(definition.getId(), state);
}
@Override
public BpmnModel getBpmnModel(String id) {
byte[] bpmnBytes = repositoryService.getModelEditorSource(id);
if (ArrayUtil.isEmpty(bpmnBytes)) {
return null;
}
return ActivitiUtils.buildBpmnModel(bpmnBytes);
}
private Model getModelByKey(String key) {
return repositoryService.createModelQuery().modelKey(key).singleResult();
}
private void checkKeyNCName(String key) {
if (!ValidationUtils.isXmlNCName(key)) {
throw exception(MODEL_KEY_VALID);
}
}
/**
* 校验流程表单已配置
*
* @param metaInfoStr 流程模型 metaInfo 字段
* @return 流程表单
*/
private BpmFormDO checkFormConfig(String metaInfoStr) {
BpmModelMetaInfoRespDTO metaInfo = JsonUtils.parseObject(metaInfoStr, BpmModelMetaInfoRespDTO.class);
if (metaInfo == null || metaInfo.getFormType() == null) {
throw exception(MODEL_DEPLOY_FAIL_FORM_NOT_CONFIG);
}
// 校验表单存在
if (Objects.equals(metaInfo.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) {
BpmFormDO form = bpmFormService.getForm(metaInfo.getFormId());
if (form == null) {
throw exception(FORM_NOT_EXISTS);
}
return form;
}
return null;
}
}

View File

@ -1,161 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 流程定义接口
*
* @author yunlong.li
* @author ZJQ
* @author 芋道源码
*/
public interface BpmProcessDefinitionService {
/**
* 获得流程定义分页
*
* @param pageReqVO 分页入参
* @return 流程定义 Page
*/
PageResult<BpmProcessDefinitionPageItemRespVO> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageReqVO);
/**
* 创建流程定义
*
* @param createReqDTO 创建信息
* @return 流程编号
*/
String createProcessDefinition(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO);
/**
* 更新流程定义状态
*
* @param id 流程定义的编号
* @param state 状态
*/
void updateProcessDefinitionState(String id, Integer state);
/**
* 获得流程定义对应的 BPMN XML
*
* @param id 流程定义编号
* @return BPMN XML
*/
String getProcessDefinitionBpmnXML(String id);
/**
* 获得需要创建的流程定义是否和当前激活的流程定义相等
*
* @param createReqDTO 创建信息
* @return 是否相等
*/
boolean isProcessDefinitionEquals(@Valid BpmProcessDefinitionCreateReqDTO createReqDTO);
/**
* 获得编号对应的 BpmProcessDefinitionExtDO
*
* @param id 编号
* @return 流程定义拓展
*/
BpmProcessDefinitionExtDO getProcessDefinitionExt(String id);
/**
* 获得流程定义列表
*
* @param listReqVO 列表入参
* @return 流程定义列表
*/
List<BpmProcessDefinitionRespVO> getProcessDefinitionList(BpmProcessDefinitionListReqVO listReqVO);
/**
* 获得 Bpmn 模型
*
* @param processDefinitionId 流程定义的编号
* @return Bpmn 模型
*/
BpmnModel getBpmnModel(String processDefinitionId);
/**
* 获得编号对应的 ProcessDefinition
*
* @param id 编号
* @return 流程定义
*/
ProcessDefinition getProcessDefinition(String id);
/**
* 获得编号对应的 ProcessDefinition
*
* 相比 {@link #getProcessDefinition(String)} 方法category 的取值是正确
*
* @param id 编号
* @return 流程定义
*/
ProcessDefinition getProcessDefinition2(String id);
/**
* 获得流程定义标识对应的激活的流程定义
*
* @param key 流程定义的标识
* @return 流程定义
*/
ProcessDefinition getActiveProcessDefinition(String key);
/**
* 获得 id 对应的 Deployment
*
* @param id 部署编号
* @return 流程部署
*/
Deployment getDeployment(String id);
/**
* 获得 ids 对应的 Deployment 数组
*
* @param ids 部署编号的数组
* @return 流程部署的数组
*/
List<Deployment> getDeployments(Set<String> ids);
/**
* 获得 ids 对应的 Deployment Map
*
* @param ids 部署编号的数组
* @return 流程部署 Map
*/
default Map<String, Deployment> getDeploymentMap(Set<String> ids) {
return CollectionUtils.convertMap(getDeployments(ids), Deployment::getId);
}
/**
* 获得 deploymentId 对应的 ProcessDefinition
*
* @param deploymentId 部署编号
* @return 流程定义
*/
ProcessDefinition getProcessDefinitionByDeploymentId(String deploymentId);
/**
* 获得 deploymentIds 对应的 ProcessDefinition 数组
*
* @param deploymentIds 部署编号的数组
* @return 流程定义的数组
*/
List<ProcessDefinition> getProcessDefinitionListByDeploymentIds(Set<String> deploymentIds);
}

View File

@ -1,267 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert;
import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionExtMapper;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.persistence.entity.SuspensionState;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_KEY_NOT_MATCH;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NAME_NOT_MATCH;
import static java.util.Collections.emptyList;
/**
* 流程定义实现
* 主要进行 Activiti {@link ProcessDefinition} {@link Deployment} 的维护
*
* @author yunlongn
* @author ZJQ
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService {
private static final String BPMN_FILE_SUFFIX = ".bpmn";
@Resource
private RepositoryService repositoryService;
@Resource
private BpmFormService formService;
@Resource
private BpmProcessDefinitionExtMapper processDefinitionMapper;
@Override
public PageResult<BpmProcessDefinitionPageItemRespVO> getProcessDefinitionPage(BpmProcessDefinitionPageReqVO pageVO) {
ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
if (StrUtil.isNotBlank(pageVO.getKey())) {
definitionQuery.processDefinitionKey(pageVO.getKey());
}
// 执行查询
List<ProcessDefinition> processDefinitions = definitionQuery.orderByProcessDefinitionVersion().desc()
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
if (CollUtil.isEmpty(processDefinitions)) {
return new PageResult<>(emptyList(), definitionQuery.count());
}
// 获得 Deployment Map
Set<String> deploymentIds = new HashSet<>();
processDefinitions.forEach(definition -> addIfNotNull(deploymentIds, definition.getDeploymentId()));
Map<String, Deployment> deploymentMap = getDeploymentMap(deploymentIds);
// 获得 BpmProcessDefinitionDO Map
List<BpmProcessDefinitionExtDO> processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(
convertList(processDefinitions, ProcessDefinition::getId));
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap = convertMap(processDefinitionDOs,
BpmProcessDefinitionExtDO::getProcessDefinitionId);
// 获得 Form Map
Set<Long> formIds = convertSet(processDefinitionDOs, BpmProcessDefinitionExtDO::getFormId);
Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
// 拼接结果
long definitionCount = definitionQuery.count();
return new PageResult<>(BpmProcessDefinitionConvert.INSTANCE.convertList(processDefinitions, deploymentMap,
processDefinitionDOMap, formMap), definitionCount);
}
@Override
public List<BpmProcessDefinitionRespVO> getProcessDefinitionList(BpmProcessDefinitionListReqVO listReqVO) {
// 拼接查询条件
ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), listReqVO.getSuspensionState())) {
definitionQuery.suspended();
} else if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), listReqVO.getSuspensionState())) {
definitionQuery.active();
}
// 执行查询
List<ProcessDefinition> processDefinitions = definitionQuery.list();
if (CollUtil.isEmpty(processDefinitions)) {
return Collections.emptyList();
}
// 获得 BpmProcessDefinitionDO Map
List<BpmProcessDefinitionExtDO> processDefinitionDOs = processDefinitionMapper.selectListByProcessDefinitionIds(
convertList(processDefinitions, ProcessDefinition::getId));
Map<String, BpmProcessDefinitionExtDO> processDefinitionDOMap = convertMap(processDefinitionDOs,
BpmProcessDefinitionExtDO::getProcessDefinitionId);
// 执行查询并返回
return BpmProcessDefinitionConvert.INSTANCE.convertList3(processDefinitions, processDefinitionDOMap);
}
@Override
public String getProcessDefinitionBpmnXML(String id) {
BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
if (bpmnModel == null) {
return null;
}
return ActivitiUtils.getBpmnXml(bpmnModel);
}
@Override
public BpmnModel getBpmnModel(String processDefinitionId) {
return repositoryService.getBpmnModel(processDefinitionId);
}
@Override
public ProcessDefinition getProcessDefinition(String id) {
return repositoryService.getProcessDefinition(id);
}
@Override
public ProcessDefinition getProcessDefinition2(String id) {
return repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();
}
@Override
public ProcessDefinition getActiveProcessDefinition(String key) {
return repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).active().singleResult();
}
@Override
public BpmProcessDefinitionExtDO getProcessDefinitionExt(String id) {
return processDefinitionMapper.selectByProcessDefinitionId(id);
}
@Override
public Deployment getDeployment(String id) {
if (StrUtil.isEmpty(id)) {
return null;
}
return repositoryService.createDeploymentQuery().deploymentId(id).singleResult();
}
@Override
public List<Deployment> getDeployments(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 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 boolean isProcessDefinitionEquals(BpmProcessDefinitionCreateReqDTO createReqDTO) {
// 校验 namedescription 是否更新
ProcessDefinition oldProcessDefinition = getActiveProcessDefinition(createReqDTO.getKey());
if (oldProcessDefinition == null) {
return false;
}
BpmProcessDefinitionExtDO oldProcessDefinitionExt = getProcessDefinitionExt(oldProcessDefinition.getId());
if (!StrUtil.equals(createReqDTO.getName(), oldProcessDefinition.getName())
|| !StrUtil.equals(createReqDTO.getDescription(), oldProcessDefinitionExt.getDescription())
|| !StrUtil.equals(createReqDTO.getCategory(), oldProcessDefinition.getCategory())) {
return false;
}
// 校验 form 信息是否更新
if (!ObjectUtil.equal(createReqDTO.getFormType(), oldProcessDefinitionExt.getFormType())
|| !ObjectUtil.equal(createReqDTO.getFormId(), oldProcessDefinitionExt.getFormId())
|| !ObjectUtil.equal(createReqDTO.getFormConf(), oldProcessDefinitionExt.getFormConf())
|| !ObjectUtil.equal(createReqDTO.getFormFields(), oldProcessDefinitionExt.getFormFields())
|| !ObjectUtil.equal(createReqDTO.getFormCustomCreatePath(), oldProcessDefinitionExt.getFormCustomCreatePath())
|| !ObjectUtil.equal(createReqDTO.getFormCustomViewPath(), oldProcessDefinitionExt.getFormCustomViewPath())) {
return false;
}
// 校验 BPMN XML 信息
BpmnModel newModel = ActivitiUtils.buildBpmnModel(createReqDTO.getBpmnBytes());
BpmnModel oldModel = getBpmnModel(oldProcessDefinition.getId());
if (!ActivitiUtils.equals(oldModel, newModel)) {
return false;
}
// 最终发现都一致则返回 true
return true;
}
@Override
@Transactional(rollbackFor = Exception.class) // 因为进行多个 activiti 操作所以开启事务
public String createProcessDefinition(BpmProcessDefinitionCreateReqDTO createReqDTO) {
// 创建 Deployment 部署
Deployment deploy = repositoryService.createDeployment()
.key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory())
.addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
.deploy();
// 设置 ProcessDefinition category 分类
ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult();
repositoryService.setProcessDefinitionCategory(definition.getId(), createReqDTO.getCategory());
// 注意 1ProcessDefinition key name 是通过 BPMN 中的 <bpmn2:process /> id name 决定
// 注意 2目前该项目的设计上需要保证 ModelDeploymentProcessDefinition 使用相同的 key保证关联性
// 否则会导致 ProcessDefinition 的分页无法查询到
if (!Objects.equals(definition.getKey(), createReqDTO.getKey())) {
throw exception(PROCESS_DEFINITION_KEY_NOT_MATCH, createReqDTO.getKey(), definition.getKey());
}
if (!Objects.equals(definition.getName(), createReqDTO.getName())) {
throw exception(PROCESS_DEFINITION_NAME_NOT_MATCH, createReqDTO.getName(), definition.getName());
}
// 插入拓展表
BpmProcessDefinitionExtDO definitionDO = BpmProcessDefinitionConvert.INSTANCE.convert2(createReqDTO)
.setProcessDefinitionId(definition.getId());
processDefinitionMapper.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);
}
}

View File

@ -1,211 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmTaskAssignRuleConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmTaskAssignRuleMapper;
import cn.iocoder.yudao.module.bpm.enums.DictTypeConstants;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.UserTask;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* BPM 任务分配规则 Service 实现类
*/
@Service
@Validated
@Slf4j
public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
@Resource
private BpmTaskAssignRuleMapper taskRuleMapper;
@Resource
@Lazy // 解决循环依赖
private BpmModelService modelService;
@Resource
@Lazy // 解决循环依赖
private BpmProcessDefinitionService processDefinitionService;
@Resource
private BpmUserGroupService userGroupService;
@Resource
private RoleApi roleApi;
@Resource
private DeptApi deptApi;
@Resource
private PostApi postApi;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DictDataApi dictDataApi;
@Override
public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId,
String taskDefinitionKey) {
return taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId, taskDefinitionKey);
}
@Override
public List<BpmTaskAssignRuleDO> getTaskAssignRuleListByModelId(String modelId) {
return taskRuleMapper.selectListByModelId(modelId);
}
@Override
public List<BpmTaskAssignRuleRespVO> getTaskAssignRuleList(String modelId, String processDefinitionId) {
// 获得规则
List<BpmTaskAssignRuleDO> rules = Collections.emptyList();
BpmnModel model = null;
if (StrUtil.isNotEmpty(modelId)) {
rules = getTaskAssignRuleListByModelId(modelId);
model = modelService.getBpmnModel(modelId);
} else if (StrUtil.isNotEmpty(processDefinitionId)) {
rules = getTaskAssignRuleListByProcessDefinitionId(processDefinitionId, null);
model = processDefinitionService.getBpmnModel(processDefinitionId);
}
if (model == null) {
return Collections.emptyList();
}
// 获得用户任务只有用户任务才可以设置分配规则
List<UserTask> userTasks = ActivitiUtils.getBpmnModelElements(model, UserTask.class);
if (CollUtil.isEmpty(userTasks)) {
return Collections.emptyList();
}
// 转换数据
return BpmTaskAssignRuleConvert.INSTANCE.convertList(userTasks, rules);
}
@Override
public Long createTaskAssignRule(BpmTaskAssignRuleCreateReqVO reqVO) {
// 校验参数
validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
// 校验是否已经配置
BpmTaskAssignRuleDO existRule = taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(
reqVO.getModelId(), reqVO.getTaskDefinitionKey());
if (existRule != null) {
throw exception(TASK_ASSIGN_RULE_EXISTS, reqVO.getModelId(), reqVO.getTaskDefinitionKey());
}
// 存储
BpmTaskAssignRuleDO rule = BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO)
.setProcessDefinitionId(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL); // 只有流程模型才允许新建
taskRuleMapper.insert(rule);
return rule.getId();
}
@Override
public void updateTaskAssignRule(BpmTaskAssignRuleUpdateReqVO reqVO) {
// 校验参数
validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
// 校验是否存在
BpmTaskAssignRuleDO existRule = taskRuleMapper.selectById(reqVO.getId());
if (existRule == null) {
throw exception(TASK_ASSIGN_RULE_NOT_EXISTS);
}
// 只允许修改流程模型的规则
if (!Objects.equals(BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL, existRule.getProcessDefinitionId())) {
throw exception(TASK_UPDATE_FAIL_NOT_MODEL);
}
// 执行更新
taskRuleMapper.updateById(BpmTaskAssignRuleConvert.INSTANCE.convert(reqVO));
}
@Override
public boolean isTaskAssignRulesEquals(String modelId, String processDefinitionId) {
// 调用 VO 接口的原因是过滤掉流程模型不需要的规则保持和 copyTaskAssignRules 方法的一致性
List<BpmTaskAssignRuleRespVO> modelRules = getTaskAssignRuleList(modelId, null);
List<BpmTaskAssignRuleRespVO> processInstanceRules = getTaskAssignRuleList(null, processDefinitionId);
if (modelRules.size() != processInstanceRules.size()) {
return false;
}
// 遍历匹配对应的规则
Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap = CollectionUtils.convertMap(processInstanceRules,
BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
if (processInstanceRule == null) {
return false;
}
if (!ObjectUtil.equals(modelRule.getType(), processInstanceRule.getType())
|| !ObjectUtil.equal(modelRule.getOptions(), processInstanceRule.getOptions())) {
return false;
}
}
return true;
}
@Override
public void copyTaskAssignRules(String fromModelId, String toProcessDefinitionId) {
List<BpmTaskAssignRuleRespVO> rules = getTaskAssignRuleList(fromModelId, null);
if (CollUtil.isEmpty(rules)) {
return;
}
// 开始复制
List<BpmTaskAssignRuleDO> newRules = BpmTaskAssignRuleConvert.INSTANCE.convertList2(rules);
newRules.forEach(rule -> rule.setProcessDefinitionId(toProcessDefinitionId).setId(null)
.setCreateTime(null).setUpdateTime(null));
taskRuleMapper.insertBatch(newRules);
}
@Override
public void checkTaskAssignRuleAllConfig(String id) {
// 一个用户任务都没配置所以无需配置规则
List<BpmTaskAssignRuleRespVO> taskAssignRules = getTaskAssignRuleList(id, null);
if (CollUtil.isEmpty(taskAssignRules)) {
return;
}
// 校验未配置规则的任务
taskAssignRules.forEach(rule -> {
if (CollUtil.isEmpty(rule.getOptions())) {
throw exception(MODEL_DEPLOY_FAIL_TASK_ASSIGN_RULE_NOT_CONFIG, rule.getTaskDefinitionName());
}
});
}
private void validTaskAssignRuleOptions(Integer type, Set<Long> options) {
if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
roleApi.validRoles(options);
} else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
deptApi.validDepts(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
postApi.validPosts(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER.getType())) {
adminUserApi.validUsers(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_GROUP.getType())) {
userGroupService.validUserGroups(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
CollectionUtils.convertSet(options, String::valueOf));
} else {
throw new IllegalArgumentException(StrUtil.format("未知的规则类型({})", type));
}
}
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
import java.util.List;
/**
* BPM 活动实例 Service 接口
*
* @author 芋道源码
*/
public interface BpmActivityService {
/**
* 获得指定流程实例的活动实例列表
*
* @param processInstanceId 流程实例的编号
* @return 活动实例列表
*/
List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId);
/**
* 生成指定流程实例的高亮流程图只高亮进行中的任务
*
* 友情提示非该方法的注释如果想实现更高级的高亮流程图当前节点红色 + 完成节点为绿色可参考如下内容
* 博客一https://blog.csdn.net/qq_40109075/article/details/110939639
* 博客二https://gitee.com/tony2y/RuoYi-flowable/blob/master/ruoyi-flowable/src/main/java/com/ruoyi/flowable/flow/CustomProcessDiagramGenerator.java
* 这里不实现的原理需要自定义实现 ProcessDiagramGenerator ProcessDiagramCanvas代码量有点大
*
* 如果你想实现高亮已完成的任务可参考 https://blog.csdn.net/qiuxinfa123/article/details/119579863 博客不过测试下来貌似不太对~
*
* @param processInstanceId 流程实例的编号
* @return 图的字节数组
*/
byte[] generateHighlightDiagram(String processInstanceId);
}

View File

@ -1,84 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.io.IoUtil;
import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.HistoryService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* BPM 活动实例 Service 实现类
*
* @author 芋道源码
*/
@Service
@Slf4j
@Validated
public class BpmActivityServiceImpl implements BpmActivityService {
private static final String FONT_NAME = "宋体";
@Resource
private ProcessDiagramGenerator processDiagramGenerator;
@Resource
private HistoryService historyService;
@Resource
private BpmProcessInstanceService processInstanceService;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
private BpmTaskService taskService;
@Override
public List<BpmActivityRespVO> getActivityListByProcessInstanceId(String processInstanceId) {
List<HistoricActivityInstance> activityList = historyService.createHistoricActivityInstanceQuery()
.processInstanceId(processInstanceId).list();
return BpmActivityConvert.INSTANCE.convertList(activityList);
}
@Override
public byte[] generateHighlightDiagram(String processInstanceId) {
// 获得流程实例
HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
if (processInstance == null) {
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
}
// 获得流程定义的 BPMN 模型
BpmnModel bpmnModel = processDefinitionService.getBpmnModel(processInstance.getProcessDefinitionId());
if (bpmnModel == null) {
throw exception(PROCESS_DEFINITION_BPMN_MODEL_NOT_EXISTS);
}
// 如果流程已经结束则无进行中的任务无法高亮
// 如果流程未结束才需要高亮
List<String> highLightedActivities = Collections.emptyList();
if (processInstance.getEndTime() == null) {
List<Task> tasks = taskService.getTasksByProcessInstanceId(processInstanceId);
highLightedActivities = CollectionUtils.convertList(tasks, Task::getTaskDefinitionKey);
}
// 生成高亮流程图
InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, highLightedActivities, Collections.emptyList(),
FONT_NAME, FONT_NAME, FONT_NAME);
return IoUtil.readBytes(inputStream);
}
}

View File

@ -1,165 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.runtime.ProcessInstance;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 流程实例 Service 接口
*
* @author 芋道源码
*/
public interface BpmProcessInstanceService {
/**
* 创建流程实例提供给前端
*
* @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 cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO);
/**
* 删除流程实例
*
* @param id 流程编号
* @param reason 删除原因可选 {@link BpmProcessInstanceDeleteReasonEnum}
*/
@Deprecated
void deleteProcessInstance(String id, String reason);
/**
* 获得流程实例的分页
*
* @param userId 用户编号
* @param pageReqVO 分页请求
* @return 流程实例的分页
*/
PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId,
@Valid BpmProcessInstanceMyPageReqVO pageReqVO);
/**
* 获得流程实例 VO 信息
*
* @param id 流程实例的编号
* @return 流程实例
*/
BpmProcessInstanceRespVO getProcessInstanceVO(String id);
/**
* 获得流程实例
*
* @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 CollectionUtils.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 CollectionUtils.convertMap(getHistoricProcessInstances(ids), HistoricProcessInstance::getId);
}
/**
* 创建 ProcessInstance 拓展记录
*
* @param instance 流程任务
*/
void createProcessInstanceExt(org.activiti.api.process.model.ProcessInstance instance);
/**
* 更新 ProcessInstance 拓展记录
*
* @param instance 流程任务
*/
void updateProcessInstanceExt(org.activiti.api.process.model.ProcessInstance instance);
/**
* 更新 ProcessInstance 拓展记录为取消
*
* @param instance 流程任务
* @param reason 取消原因
*/
void updateProcessInstanceExtCancel(org.activiti.api.process.model.ProcessInstance instance, String reason);
/**
* 更新 ProcessInstance 拓展记录为完成
*
* @param instance 流程任务
*/
void updateProcessInstanceExtComplete(org.activiti.api.process.model.ProcessInstance instance);
/**
* 更新 ProcessInstance 拓展记录为不通过
*
* @param id 流程编号
* @param reason 理由例如说审批不通过时需要传递该值
*/
void updateProcessInstanceExtReject(String id, String reason);
}

View File

@ -1,310 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* 流程实例 Service 实现类
*
* ProcessDefinition & ProcessInstance & Execution & Task 的关系
* 1. https://blog.csdn.net/bobozai86/article/details/105210414
*
* HistoricProcessInstance & ProcessInstance 的关系
* 1.https://my.oschina.net/843294669/blog/719024
* 简单来说前者 = 历史 + 运行中的流程实例后者仅是运行中的流程实例
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {
@Resource
private RuntimeService runtimeService;
@Resource
private HistoryService historyService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
@Lazy // 解决循环依赖
private BpmTaskService taskService;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
private BpmMessageService messageService;
@Resource
private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher;
@Resource
private BpmProcessInstanceExtMapper processInstanceExtMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public String createProcessInstance(Long userId, BpmProcessInstanceCreateReqVO createReqVO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getProcessDefinition(createReqVO.getProcessDefinitionId());
// 发起流程
return createProcessInstance0(userId, definition, createReqVO.getVariables(), null);
}
@Override
public String createProcessInstance(Long userId, BpmProcessInstanceCreateReqDTO createReqDTO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
// 发起流程
return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey());
}
private String createProcessInstance0(Long userId, ProcessDefinition definition,
Map<String, Object> variables, String businessKey) {
// 校验流程定义
if (definition == null) {
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
}
if (definition.isSuspended()) {
throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
}
// 创建流程实例
ProcessInstance instance = runtimeService.startProcessInstanceById(definition.getId(), businessKey, variables);
// 设置流程名字
runtimeService.setProcessInstanceName(instance.getId(), definition.getName());
// 补全流程实例的拓展表
processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId())
.setFormVariables(variables));
// 添加初始的评论 TODO 芋艿在思考下
// Task task = taskService.createTaskQuery().processInstanceId(instance.getId()).singleResult();
// if (task != null) {
// SysUserDO user = userService.getUser(userId);
// Assert.notNull(user, "用户({})不存在", userId);
// String type = "normal";
// taskService.addComment(task.getId(), instance.getProcessInstanceId(), type,
// String.format("%s 发起流程申请", user.getNickname()));
// }
return instance.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelProcessInstance(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) {
// 校验流程实例存在
ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
}
// 只能取消自己的
if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
}
// 通过删除流程实例实现流程实例的取消
runtimeService.deleteProcessInstance(cancelReqVO.getId(),
BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()));
}
@Override
public void deleteProcessInstance(String id, String reason) {
runtimeService.deleteProcessInstance(id, reason);
}
@Override
public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId,
BpmProcessInstanceMyPageReqVO pageReqVO) {
// 通过 BpmProcessInstanceExtDO 先查询到对应的分页
PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return new PageResult<>(pageResult.getTotal());
}
// 获得流程 Task Map
List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId);
Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
// 转换返回
return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap);
}
@Override
public BpmProcessInstanceRespVO getProcessInstanceVO(String id) {
// 获得流程实例
HistoricProcessInstance processInstance = getHistoricProcessInstance(id);
if (processInstance == null) {
return null;
}
BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id);
Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id);
// 获得流程定义
ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(
processInstance.getProcessDefinitionId());
Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId());
BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt(
processInstance.getProcessDefinitionId());
Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id);
String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId());
// 获得 User
AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId()));
DeptRespDTO dept = null;
if (startUser != null) {
dept = deptApi.getDept(startUser.getDeptId());
}
// 拼接结果
return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt,
processDefinition, processDefinitionExt, bpmnXml, startUser, dept);
}
@Override
public List<ProcessInstance> getProcessInstances(Set<String> ids) {
return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
}
/**
* 获得历史的流程实例
*
* @param id 流程实例的编号
* @return 历史的流程实例
*/
@Override
public HistoricProcessInstance getHistoricProcessInstance(String id) {
return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
}
@Override
public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public void createProcessInstanceExt(org.activiti.api.process.model.ProcessInstance instance) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId());
// 插入 BpmProcessInstanceExtDO 对象
BpmProcessInstanceExtDO instanceExtDO = BpmProcessInstanceConvert.INSTANCE.convert(instance)
.setCategory(definition.getCategory())
.setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus())
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
processInstanceExtMapper.insert(instanceExtDO);
}
@Override
public void updateProcessInstanceExt(org.activiti.api.process.model.ProcessInstance instance) {
BpmProcessInstanceExtDO instanceExtDO = BpmProcessInstanceConvert.INSTANCE.convert(instance);
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
}
@Override
public void updateProcessInstanceExtCancel(org.activiti.api.process.model.ProcessInstance instance, String reason) {
// 判断是否为 Reject 不通过如果是则不进行更新
if (BpmProcessInstanceDeleteReasonEnum.isRejectReason(reason)) {
return;
}
// 需要主动查询因为 instance 只有 id 属性
// 另外此时如果去查询 ProcessInstance 的话字段是不全的所以去查询了 HistoricProcessInstance
HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
// 更新拓展表
BpmProcessInstanceExtDO instanceExtDO = BpmProcessInstanceConvert.INSTANCE.convert(instance)
.setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime所以这里设置
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.CANCEL.getResult());
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
@Override
public void updateProcessInstanceExtComplete(org.activiti.api.process.model.ProcessInstance instance) {
// 需要主动查询因为 instance 只有 id 属性
// 另外此时如果去查询 ProcessInstance 的话字段是不全的所以去查询了 HistoricProcessInstance
HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
// 更新拓展表
BpmProcessInstanceExtDO instanceExtDO = BpmProcessInstanceConvert.INSTANCE.convert(instance)
.setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime所以这里设置
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全说明审批通过
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程被通过的消息
messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2(instance));
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
@Transactional(rollbackFor = Exception.class)
public void updateProcessInstanceExtReject(String id, String reason) {
// 需要主动查询因为 instance 只有 id 属性
ProcessInstance processInstance = getProcessInstance(id);
// 删除流程实例以实现驳回任务时取消整个审批流程
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason)));
// 更新 status + result
// 注意不能和上面的逻辑更换位置因为 deleteProcessInstance 会触发流程的取消进而调用 updateProcessInstanceExtCancel 方法
// 设置 result BpmProcessInstanceStatusEnum.CANCEL显然和 result 不一定是一致的
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id)
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.REJECT.getResult());
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程被不通过的消息
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert(processInstance, reason));
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
}

View File

@ -1,159 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import org.activiti.engine.task.Task;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* 流程任务实例 Service 接口
*
* @author jason
* @author 芋道源码
*/
public interface BpmTaskService {
/**
* 获得指定流程实例的 Running 进行中的流程任务列表
*
* @param processInstanceId 流程实例的编号
*/
List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId);
/**
* 获得指令流程实例的流程任务列表包括所有状态的
*
* @param processInstanceId 流程实例的编号
* @return 流程任务列表
*/
List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId);
/**
* 获得流程任务列表
*
* @param processInstanceId 流程实例的编号
* @return 流程任务列表
*/
List<Task> getTasksByProcessInstanceId(String processInstanceId);
/**
* 获得流程任务列表
*
* @param processInstanceIds 流程实例的编号数组
* @return 流程任务列表
*/
List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds);
/**
* 获得流程任务 Map
*
* @param processInstanceIds 流程实例的编号数组
* @return 流程任务 Map
*/
default Map<String, List<Task>> getTaskMapByProcessInstanceIds(List<String> processInstanceIds) {
return CollectionUtils.convertMultiMap(getTasksByProcessInstanceIds(processInstanceIds),
Task::getProcessInstanceId);
}
/**
* 获得待办的流程任务分页
*
* @param userId 用户编号
* @param pageReqVO 分页请求
* @return 流程任务分页
*/
PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageReqVO);
/**
* 获得已办的流程任务分页
*
* @param userId 用户编号
* @param pageReqVO 分页请求
* @return 流程任务分页
*/
PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageReqVO);
/**
* 将流程任务分配给指定用户
*
* @param userId 用户编号
* @param reqVO 分配请求
*/
void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO);
/**
* 将流程任务分配给指定用户
*
* @param id 流程任务编号
* @param userId 用户编号
*/
void updateTaskAssignee(String id, Long userId);
/**
* 通过任务
*
* @param userId 用户编号
* @param reqVO 通过请求
*/
void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO);
/**
* 不通过任务
*
* @param userId 用户编号
* @param reqVO 不通过请求
*/
void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO);
// ========== Task 拓展表相关 ==========
/**
* 创建 Task 拓展记录
*
* @param task 任务实体
*/
void createTaskExt(org.activiti.api.task.model.Task task);
/**
* 更新 Task 拓展记录
*
* @param task 任务实体
*/
void updateTaskExt(org.activiti.api.task.model.Task task);
/**
* 更新 Task 拓展记录并发送通知
*
* @param task 任务实体
*/
void updateTaskExtAssign(org.activiti.api.task.model.Task task);
/**
* 更新 Task 拓展记录为取消
*
* @param task 任务实体
*/
void updateTaskExtCancel(org.activiti.api.task.model.Task task);
/**
* 更新 Task 拓展记录为完成
*
* @param task 任务实体
*/
void updateTaskExtComplete(org.activiti.api.task.model.Task task);
/**
* 获得流程实例对应的 Task 拓展列表
*
* @param processInstanceId 流程实例的编号
* @return Task 拓展列表
*/
List<BpmTaskExtDO> getTaskExtListByProcessInstanceId(String processInstanceId);
}

View File

@ -1,319 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmTaskExtMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.framework.activiti.core.util.ActivitiUtils;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.activiti.engine.HistoryService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.history.HistoricTaskInstanceQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* 流程任务实例 Service 实现类
*
* @author jason
* @author 芋道源码
*/
@Slf4j
@Service
public class BpmTaskServiceImpl implements BpmTaskService {
@Resource
private TaskService taskService;
@Resource
private HistoryService historyService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
@Lazy // 解决循环依赖
private BpmProcessInstanceService processInstanceService;
@Resource
private BpmMessageService messageService;
@Resource
private BpmTaskExtMapper taskExtMapper;
@Override
public List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId) {
return taskService.createTaskQuery().processInstanceId(processInstanceId).list();
}
@Override
public List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId) {
// 获得任务列表
List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
.list();
if (CollUtil.isEmpty(tasks)) {
return Collections.emptyList();
}
// 获得 TaskExtDO Map
List<BpmTaskExtDO> bpmTaskExtDOs = taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
// 获得 ProcessInstance Map
HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
// 获得 User Map
Set<Long> userIds = convertSet(tasks, task -> NumberUtils.parseLong(task.getAssignee()));
userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
// 获得 Dept Map
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 拼接数据
return BpmTaskConvert.INSTANCE.convertList3(tasks, bpmTaskExtDOMap, processInstance, userMap, deptMap);
}
@Override
public List<Task> getTasksByProcessInstanceId(String processInstanceId) {
if (StrUtil.isEmpty(processInstanceId)) {
return Collections.emptyList();
}
return taskService.createTaskQuery().processInstanceId(processInstanceId).list();
}
@Override
public List<Task> getTasksByProcessInstanceIds(List<String> processInstanceIds) {
if (CollUtil.isEmpty(processInstanceIds)) {
return Collections.emptyList();
}
return taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).list();
}
@Override
public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(Long userId, BpmTaskTodoPageReqVO pageVO) {
// 查询待办任务
TaskQuery taskQuery = taskService.createTaskQuery()
.taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByTaskCreateTime().desc(); // 创建时间倒序
if (StrUtil.isNotBlank(pageVO.getName())) {
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
}
if (pageVO.getBeginCreateTime() != null) {
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
}
if (pageVO.getEndCreateTime() != null) {
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
}
// 执行查询
List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
if (CollUtil.isEmpty(tasks)) {
return PageResult.empty(taskQuery.count());
}
// 获得 ProcessInstance Map
Map<String, ProcessInstance> processInstanceMap = processInstanceService.getProcessInstanceMap(
convertSet(tasks, Task::getProcessInstanceId));
// 获得 User Map
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(processInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
// 拼接结果
return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap, userMap),
taskQuery.count());
}
@Override
public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(Long userId, BpmTaskDonePageReqVO pageVO) {
// 查询已办任务
HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
.finished() // 已完成
.taskAssignee(String.valueOf(userId)) // 分配给自己
.orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序
if (StrUtil.isNotBlank(pageVO.getName())) {
taskQuery.taskNameLike("%" + pageVO.getName() + "%");
}
if (pageVO.getBeginCreateTime() != null) {
taskQuery.taskCreatedAfter(pageVO.getBeginCreateTime());
}
if (pageVO.getEndCreateTime() != null) {
taskQuery.taskCreatedBefore(pageVO.getEndCreateTime());
}
// 执行查询
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
if (CollUtil.isEmpty(tasks)) {
return PageResult.empty(taskQuery.count());
}
// 获得 TaskExtDO Map
List<BpmTaskExtDO> bpmTaskExtDOs = taskExtMapper.selectListByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));
Map<String, BpmTaskExtDO> bpmTaskExtDOMap = convertMap(bpmTaskExtDOs, BpmTaskExtDO::getTaskId);
// 获得 ProcessInstance Map
Map<String, HistoricProcessInstance> historicProcessInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
// 获得 User Map
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(historicProcessInstanceMap.values(), instance -> Long.valueOf(instance.getStartUserId())));
// 拼接结果
return new PageResult<>(BpmTaskConvert.INSTANCE.convertList2(tasks, bpmTaskExtDOMap, historicProcessInstanceMap, userMap),
taskQuery.count());
}
@Override
public void updateTaskAssignee(Long userId, BpmTaskUpdateAssigneeReqVO reqVO) {
// 校验任务存在
Task task = getTask(reqVO.getId());
if (task == null) {
throw exception(TASK_COMPLETE_FAIL_NOT_EXISTS);
}
if (!ActivitiUtils.equals(task.getAssignee(), userId)) {
throw exception(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
}
// 更新负责人
updateTaskAssignee(task.getId(), reqVO.getAssigneeUserId());
}
@Override
public void updateTaskAssignee(String id, Long userId) {
taskService.setAssignee(id, String.valueOf(userId));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void approveTask(Long userId, BpmTaskApproveReqVO reqVO) {
// 校验任务存在
Task task = getTask(reqVO.getId());
if (task == null) {
throw exception(TASK_COMPLETE_FAIL_NOT_EXISTS);
}
if (!ActivitiUtils.equals(task.getAssignee(), userId)) {
throw exception(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
}
// 校验流程实例存在
ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
}
// 完成任务审批通过
taskService.complete(task.getId(), instance.getProcessVariables()); // TODO 芋艿variables 的选择
// 更新任务拓展表为通过
taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId())
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()).setReason(reqVO.getReason()));
// TODO 芋艿添加评论
// taskService.addComment(task.getId(), task.getProcessInstanceId(), reqVO.getComment());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) {
// 校验任务存在
Task task = getTask(reqVO.getId());
if (task == null) {
throw exception(TASK_COMPLETE_FAIL_NOT_EXISTS);
}
if (!ActivitiUtils.equals(task.getAssignee(), userId)) {
throw exception(TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF);
}
// 校验流程实例存在
ProcessInstance instance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
}
// 更新流程实例为不通过
processInstanceService.updateProcessInstanceExtReject(instance.getProcessInstanceId(), reqVO.getReason());
// 更新任务拓展表为不通过
taskExtMapper.updateByTaskId(new BpmTaskExtDO().setTaskId(task.getId())
.setResult(BpmProcessInstanceResultEnum.REJECT.getResult()).setReason(reqVO.getReason()));
// TODO 芋艿添加评论
// taskService.addComment(task.getId(), task.getProcessInstanceId(), reqVO.getComment());
}
private Task getTask(String id) {
return taskService.createTaskQuery().taskId(id).singleResult();
}
// ========== Task 拓展表相关 ==========
@Override
public void createTaskExt(org.activiti.api.task.model.Task task) {
// 插入 BpmTaskExtDO 记录
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert(task)
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
taskExtMapper.insert(taskExtDO);
}
@Override
public void updateTaskExt(org.activiti.api.task.model.Task task) {
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert(task);
taskExtMapper.updateByTaskId(taskExtDO);
}
@Override
public void updateTaskExtAssign(org.activiti.api.task.model.Task task) {
// 更新
updateTaskExt(task);
// 发送通知由于 Activiti 操作是在事务提交时批量执行操作所以直接查询会无法查询到 ProcessInstance所以这里是通过监听事务的提交来实现
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
ProcessInstance processInstance = processInstanceService.getProcessInstance(task.getProcessInstanceId());
AdminUserRespDTO startUser = adminUserApi.getUser(Long.valueOf(processInstance.getStartUserId()));
messageService.sendMessageWhenTaskAssigned(BpmTaskConvert.INSTANCE.convert(processInstance, startUser, task));
}
});
}
@Override
public void updateTaskExtCancel(org.activiti.api.task.model.Task task) {
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert(task)
.setEndTime(new Date()) // 由于 Task 里没有办法拿到 endTime所以这里设置
.setResult(BpmProcessInstanceResultEnum.CANCEL.getResult());
taskExtMapper.updateByTaskId(taskExtDO);
}
@Override
public void updateTaskExtComplete(org.activiti.api.task.model.Task task) {
BpmTaskExtDO taskExtDO = BpmTaskConvert.INSTANCE.convert(task)
.setEndTime(new Date()) // 此时不能使用 task completeData因为还是空的
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult());
taskExtMapper.updateByTaskId(taskExtDO);
}
@Override
public List<BpmTaskExtDO> getTaskExtListByProcessInstanceId(String processInstanceId) {
return taskExtMapper.selectListByProcessInstanceId(processInstanceId);
}
}

View File

@ -1,12 +0,0 @@
/**
* task 包下存放的都是 xxx 实例例如说
* 1. ProcessInstance ProcessDefinition 创建而来的实例
* 2. TaskInstance TaskDefinition 创建而来的实例
* 3. ActivityInstance BPMN 流程图的每个元素创建的实例
*
* 考虑到 Task Activity 可以比较明确表示名字所以对应的 Service 就没有使用 Instance 后缀~
* 嘿嘿其实也是实现到比较后面的阶段所以就暂时没去统一和修改了~
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bpm.service.task;

View File

@ -1,49 +0,0 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引
mybatis:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
# Resilience4j 配置项
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao.module

View File

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

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-bpm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm-biz-flowable</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
bpm-flowable 模块,基于 Flowable 6 实现工作流
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-bpm-base</artifactId>
<version>${revision}</version>
</dependency>
<!-- 工作流相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

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

View File

@ -1,79 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.TaskService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
@Api(tags = "管理后台 - 流程任务实例")
@RestController
@RequestMapping("/bpm/task")
@Validated
public class BpmTaskController {
@Resource
private BpmTaskService taskService;
@GetMapping("todo-page")
@ApiOperation("获取 Todo 待办任务分页")
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<PageResult<BpmTaskTodoPageItemRespVO>> getTodoTaskPage(@Valid BpmTaskTodoPageReqVO pageVO) {
return success(taskService.getTodoTaskPage(getLoginUserId(), pageVO));
}
@GetMapping("done-page")
@ApiOperation("获取 Done 已办任务分页")
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<PageResult<BpmTaskDonePageItemRespVO>> getDoneTaskPage(@Valid BpmTaskDonePageReqVO pageVO) {
return success(taskService.getDoneTaskPage(getLoginUserId(), pageVO));
}
@GetMapping("/list-by-process-instance-id")
@ApiOperation(value = "获得指定流程实例的任务列表", notes = "包括完成的、未完成的")
@ApiImplicitParam(name = "processInstanceId", value = "流程实例的编号", required = true, dataTypeClass = String.class)
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
@RequestParam("processInstanceId") String processInstanceId) {
return success(taskService.getTaskListByProcessInstanceId(processInstanceId));
}
@PutMapping("/approve")
@ApiOperation("通过任务")
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> approveTask(@Valid @RequestBody BpmTaskApproveReqVO reqVO) {
taskService.approveTask(getLoginUserId(), reqVO);
return success(true);
}
@PutMapping("/reject")
@ApiOperation("不通过任务")
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> rejectTask(@Valid @RequestBody BpmTaskRejectReqVO reqVO) {
taskService.rejectTask(getLoginUserId(), reqVO);
return success(true);
}
@PutMapping("/update-assignee")
@ApiOperation(value = "更新任务的负责人", notes = "用于【流程详情】的【转派】按钮")
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> updateTaskAssignee(@Valid @RequestBody BpmTaskUpdateAssigneeReqVO reqVO) {
taskService.updateTaskAssignee(getLoginUserId(), reqVO);
return success(true);
}
}

View File

@ -1,4 +0,0 @@
/**
* 占位
*/
package cn.iocoder.yudao.module.bpm.controller.app;

View File

@ -1,6 +0,0 @@
/**
* 提供 RESTful API 给前端
* 1. admin 提供给管理后台 yudao-ui-admin 前端项目
* 2. app 提供给用户 APP yudao-ui-app 前端项目它的 Controller VO 都要添加 App 前缀用于和管理后台进行区分
*/
package cn.iocoder.yudao.module.bpm.controller;

View File

@ -1,6 +0,0 @@
/**
* 提供 POJO 类的实体转换
*
* 目前使用 MapStruct 框架
*/
package cn.iocoder.yudao.module.bpm.convert;

View File

@ -1,55 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.flowable.engine.impl.bpmn.parser.factory.DefaultActivityBehaviorFactory;
import java.util.List;
/**
* 自定义的 ActivityBehaviorFactory 实现类目的如下
* 1. 自定义 {@link #createUserTaskActivityBehavior(UserTask)}实现自定义的流程任务的 assignee 负责人的分配
*
* @author 芋道源码
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BpmActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
@Setter
private BpmTaskAssignRuleService bpmTaskRuleService;
@Setter
private BpmUserGroupService userGroupService;
@Setter
private PermissionApi permissionApi;
@Setter
private DeptApi deptApi;
@Setter
private AdminUserApi adminUserApi;
@Setter
private List<BpmTaskAssignScript> scripts;
@Override
public UserTaskActivityBehavior createUserTaskActivityBehavior(UserTask userTask) {
BpmUserTaskActivityBehavior userTaskActivityBehavior = new BpmUserTaskActivityBehavior(userTask);
userTaskActivityBehavior.setBpmTaskRuleService(bpmTaskRuleService);
userTaskActivityBehavior.setPermissionApi(permissionApi);
userTaskActivityBehavior.setDeptApi(deptApi);
userTaskActivityBehavior.setUserGroupService(userGroupService);
userTaskActivityBehavior.setAdminUserApi(adminUserApi);
userTaskActivityBehavior.setScripts(scripts);
return userTaskActivityBehavior;
}
}

View File

@ -1,195 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
import cn.iocoder.yudao.module.bpm.service.definition.BpmTaskAssignRuleService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.api.FlowableException;
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.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_ASSIGN_SCRIPT_NOT_EXISTS;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.TASK_CREATE_FAIL_NO_CANDIDATE_USER;
/**
* 自定义的流程任务的 assignee 负责人的分配
* 第一步获得对应的分配规则
* 第二步根据分配规则计算出分配任务的候选人如果找不到则直接报业务异常不继续执行后续的流程
* 第三步随机选择一个候选人则选择作为 assignee 负责人
*
* @author 芋道源码
*/
@Slf4j
public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
@Setter
private BpmTaskAssignRuleService bpmTaskRuleService;
@Setter
private BpmUserGroupService userGroupService;
@Setter
private DeptApi deptApi;
@Setter
private AdminUserApi adminUserApi;
@Setter
private PermissionApi permissionApi;
/**
* 任务分配脚本
*/
private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();
public BpmUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}
public void setScripts(List<BpmTaskAssignScript> scripts) {
this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
}
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
// 第一步获得任务的规则
BpmTaskAssignRuleDO rule = getTaskRule(task);
// 第二步获得任务的候选用户们
Set<Long> candidateUserIds = calculateTaskCandidateUsers(task, rule);
// 第三步设置一个作为负责人
Long assigneeUserId = chooseTaskAssignee(candidateUserIds);
TaskHelper.changeTaskAssignee(task, String.valueOf(assigneeUserId));
}
private BpmTaskAssignRuleDO getTaskRule(TaskEntity task) {
List<BpmTaskAssignRuleDO> taskRules = bpmTaskRuleService.getTaskAssignRuleListByProcessDefinitionId(task.getProcessDefinitionId(),
task.getTaskDefinitionKey());
if (CollUtil.isEmpty(taskRules)) {
throw new FlowableException(StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则",
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey()));
}
if (taskRules.size() > 1) {
throw new FlowableException(StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})",
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), taskRules.size()));
}
return taskRules.get(0);
}
Set<Long> calculateTaskCandidateUsers(TaskEntity task, BpmTaskAssignRuleDO rule) {
Set<Long> assigneeUserIds = null;
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByRole(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptMember(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByDeptLeader(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.POST.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByPost(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUser(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER_GROUP.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByUserGroup(task, rule);
} else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
assigneeUserIds = calculateTaskCandidateUsersByScript(task, rule);
}
// 移除被禁用的用户
removeDisableUsers(assigneeUserIds);
// 如果候选人为空抛出异常 TODO 芋艿没候选人的策略选择1 - 挂起2 - 直接结束3 - 强制一个兜底人
if (CollUtil.isEmpty(assigneeUserIds)) {
log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]",
task.getId(), task.getProcessDefinitionId(), task.getTaskDefinitionKey(), toJsonString(rule));
throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
}
return assigneeUserIds;
}
private Set<Long> calculateTaskCandidateUsersByRole(TaskEntity task, BpmTaskAssignRuleDO rule) {
return permissionApi.getUserRoleIdListByRoleIds(rule.getOptions());
}
private Set<Long> calculateTaskCandidateUsersByDeptMember(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions());
return convertSet(users, AdminUserRespDTO::getId);
}
private Set<Long> calculateTaskCandidateUsersByDeptLeader(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<DeptRespDTO> depts = deptApi.getDepts(rule.getOptions());
return convertSet(depts, DeptRespDTO::getLeaderUserId);
}
private Set<Long> calculateTaskCandidateUsersByPost(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions());
return convertSet(users, AdminUserRespDTO::getId);
}
private Set<Long> calculateTaskCandidateUsersByUser(TaskEntity task, BpmTaskAssignRuleDO rule) {
return rule.getOptions();
}
private Set<Long> calculateTaskCandidateUsersByUserGroup(TaskEntity task, BpmTaskAssignRuleDO rule) {
List<BpmUserGroupDO> userGroups = userGroupService.getUserGroupList(rule.getOptions());
Set<Long> userIds = new HashSet<>();
userGroups.forEach(group -> userIds.addAll(group.getMemberUserIds()));
return userIds;
}
private Set<Long> calculateTaskCandidateUsersByScript(TaskEntity task, BpmTaskAssignRuleDO rule) {
// 获得对应的脚本
List<BpmTaskAssignScript> scripts = new ArrayList<>(rule.getOptions().size());
rule.getOptions().forEach(id -> {
BpmTaskAssignScript script = scriptMap.get(id);
if (script == null) {
throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id);
}
scripts.add(script);
});
// 逐个计算任务
Set<Long> userIds = new HashSet<>();
scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(task)));
return userIds;
}
private Long chooseTaskAssignee(Set<Long> candidateUserIds) {
// TODO 芋艿未来可以优化下改成轮询的策略
int index = RandomUtil.randomInt(candidateUserIds.size());
return CollUtil.get(candidateUserIds, index);
}
@VisibleForTesting
void removeDisableUsers(Set<Long> assigneeUserIds) {
if (CollUtil.isEmpty(assigneeUserIds)) {
return;
}
//TODO 芋艿 这里有数据权限的问题默认会加上数据权限 dept_id IN (deptId). 导致查询不到数据
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
assigneeUserIds.removeIf(id -> {
AdminUserRespDTO user = userMap.get(id);
return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
});
}
}

View File

@ -1,6 +0,0 @@
/**
* 属于 bpm 模块的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bpm.framework;

View File

@ -1,12 +0,0 @@
/**
* bpm 包下业务流程管理Business Process Management我们放工作流的功能基于 activiti 7 版本实现
* 例如说流程定义表单配置审核中心我的申请我的待办我的已办等等
*
* bpm 解释https://baike.baidu.com/item/BPM/1933
*
* 1. Controller URL /bpm/ 开头避免和其它 Module 冲突
* 2. DataObject 表名 bpm_ 开头方便在数据库中区分
*
* 注意由于 Bpm 模块下容易和其它模块重名所以类名都加载 Pay 的前缀~
*/
package cn.iocoder.yudao.module.bpm;

View File

@ -1,87 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleCreateReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.rule.BpmTaskAssignRuleUpdateReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import org.springframework.lang.Nullable;
import javax.validation.Valid;
import java.util.List;
/**
* BPM 任务分配规则 Service 接口
*
* @author 芋道源码
*/
public interface BpmTaskAssignRuleService {
/**
* 获得流程定义的任务分配规则数组
*
* @param processDefinitionId 流程定义的编号
* @param taskDefinitionKey 流程任务定义的 Key允许空
* @return 任务规则数组
*/
List<BpmTaskAssignRuleDO> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId,
@Nullable String taskDefinitionKey);
/**
* 获得流程模型的任务规则数组
*
* @param modelId 流程模型的编号
* @return 任务规则数组
*/
List<BpmTaskAssignRuleDO> getTaskAssignRuleListByModelId(String modelId);
/**
* 获得流程定义的任务分配规则数组
*
* @param modelId 流程模型的编号
* @param processDefinitionId 流程定义的编号
* @return 任务规则数组
*/
List<BpmTaskAssignRuleRespVO> getTaskAssignRuleList(String modelId, String processDefinitionId);
/**
* 创建任务分配规则
*
* @param reqVO 创建信息
* @return 规则编号
*/
Long createTaskAssignRule(@Valid BpmTaskAssignRuleCreateReqVO reqVO);
/**
* 更新任务分配规则
*
* @param reqVO 创建信息
*/
void updateTaskAssignRule(@Valid BpmTaskAssignRuleUpdateReqVO reqVO);
/**
* 判断指定流程模型和流程定义的分配规则是否相等
*
* @param modelId 流程模型编号
* @param processDefinitionId 流程定义编号
* @return 是否相等
*/
boolean isTaskAssignRulesEquals(String modelId, String processDefinitionId);
/**
* 将流程流程模型的任务分配规则复制一份给流程定义
* 目的每次流程模型部署时都会生成一个新的流程定义此时考虑到每次部署的流程不可变性所以需要复制一份给该流程定义
*
* @param fromModelId 流程模型编号
* @param toProcessDefinitionId 流程定义编号
*/
void copyTaskAssignRules(String fromModelId, String toProcessDefinitionId);
/**
* 校验流程模型的任务分配规则全部都配置了
* 目的如果有规则未配置会导致流程任务找不到负责人进而流程无法进行下去
*
* @param id 流程模型编号
*/
void checkTaskAssignRuleAllConfig(String id);
}

View File

@ -1,300 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceExtMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceDeleteReasonEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventPublisher;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import lombok.extern.slf4j.Slf4j;
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.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.context.annotation.Lazy;
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.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF;
/**
* 流程实例 Service 实现类
*
* ProcessDefinition & ProcessInstance & Execution & Task 的关系
* 1. https://blog.csdn.net/bobozai86/article/details/105210414
*
* HistoricProcessInstance & ProcessInstance 的关系
* 1.https://my.oschina.net/843294669/blog/719024
* 简单来说前者 = 历史 + 运行中的流程实例后者仅是运行中的流程实例
*
* @author 芋道源码
*/
@Service
@Validated
@Slf4j
public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService {
@Resource
private RuntimeService runtimeService;
@Resource
private BpmProcessInstanceExtMapper processInstanceExtMapper;
@Resource
@Lazy // 解决循环依赖
private BpmTaskService taskService;
@Resource
private BpmProcessDefinitionService processDefinitionService;
@Resource
private HistoryService historyService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Resource
private BpmProcessInstanceResultEventPublisher processInstanceResultEventPublisher;
@Resource
private BpmMessageService messageService;
@Override
public ProcessInstance getProcessInstance(String id) {
return runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();
}
@Override
public List<ProcessInstance> getProcessInstances(Set<String> ids) {
return runtimeService.createProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public PageResult<BpmProcessInstancePageItemRespVO> getMyProcessInstancePage(Long userId,
BpmProcessInstanceMyPageReqVO pageReqVO) {
// 通过 BpmProcessInstanceExtDO 先查询到对应的分页
PageResult<BpmProcessInstanceExtDO> pageResult = processInstanceExtMapper.selectPage(userId, pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return new PageResult<>(pageResult.getTotal());
}
// 获得流程 Task Map
List<String> processInstanceIds = convertList(pageResult.getList(), BpmProcessInstanceExtDO::getProcessInstanceId);
Map<String, List<Task>> taskMap = taskService.getTaskMapByProcessInstanceIds(processInstanceIds);
// 转换返回
return BpmProcessInstanceConvert.INSTANCE.convertPage(pageResult, taskMap);
}
@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);
}
@Override
public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getActiveProcessDefinition(createReqDTO.getProcessDefinitionKey());
// 发起流程
return createProcessInstance0(userId, definition, createReqDTO.getVariables(), createReqDTO.getBusinessKey());
}
@Override
public BpmProcessInstanceRespVO getProcessInstanceVO(String id) {
// 获得流程实例
HistoricProcessInstance processInstance = getHistoricProcessInstance(id);
if (processInstance == null) {
return null;
}
BpmProcessInstanceExtDO processInstanceExt = processInstanceExtMapper.selectByProcessInstanceId(id);
Assert.notNull(processInstanceExt, "流程实例拓展({}) 不存在", id);
// 获得流程定义
ProcessDefinition processDefinition = processDefinitionService
.getProcessDefinition(processInstance.getProcessDefinitionId());
Assert.notNull(processDefinition, "流程定义({}) 不存在", processInstance.getProcessDefinitionId());
BpmProcessDefinitionExtDO processDefinitionExt = processDefinitionService.getProcessDefinitionExt(
processInstance.getProcessDefinitionId());
Assert.notNull(processDefinitionExt, "流程定义拓展({}) 不存在", id);
String bpmnXml = processDefinitionService.getProcessDefinitionBpmnXML(processInstance.getProcessDefinitionId());
// 获得 User
AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId()));
DeptRespDTO dept = null;
if (startUser != null) {
dept = deptApi.getDept(startUser.getDeptId());
}
// 拼接结果
return BpmProcessInstanceConvert.INSTANCE.convert2(processInstance, processInstanceExt,
processDefinition, processDefinitionExt, bpmnXml, startUser, dept);
}
@Override
public void cancelProcessInstance(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
// 校验流程实例存在
ProcessInstance instance = getProcessInstance(cancelReqVO.getId());
if (instance == null) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS);
}
// 只能取消自己的
if (!Objects.equals(instance.getStartUserId(), String.valueOf(userId))) {
throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF);
}
// 通过删除流程实例实现流程实例的取消,
// 删除流程实例正则执行任务ACT_RU_TASK. 任务会被删除通过历史表查询
deleteProcessInstance(cancelReqVO.getId(),
BpmProcessInstanceDeleteReasonEnum.CANCEL_TASK.format(cancelReqVO.getReason()));
}
/**
* 获得历史的流程实例
*
* @param id 流程实例的编号
* @return 历史的流程实例
*/
@Override
public HistoricProcessInstance getHistoricProcessInstance(String id) {
return historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
}
@Override
public List<HistoricProcessInstance> getHistoricProcessInstances(Set<String> ids) {
return historyService.createHistoricProcessInstanceQuery().processInstanceIds(ids).list();
}
@Override
public void createProcessInstanceExt(ProcessInstance instance) {
// 获得流程定义
ProcessDefinition definition = processDefinitionService.getProcessDefinition2(instance.getProcessDefinitionId());
// 插入 BpmProcessInstanceExtDO 对象
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
.setProcessInstanceId(instance.getId())
.setProcessDefinitionId(definition.getId())
.setName(instance.getProcessDefinitionName())
.setStartUserId(Long.valueOf(instance.getStartUserId()))
.setCategory(definition.getCategory())
.setStatus(BpmProcessInstanceStatusEnum.RUNNING.getStatus())
.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
processInstanceExtMapper.insert(instanceExtDO);
}
@Override
public void updateProcessInstanceExtCancel(FlowableCancelledEvent event) {
// 判断是否为 Reject 不通过如果是则不进行更新
if (BpmProcessInstanceDeleteReasonEnum.isRejectReason((String)event.getCause())) {
return;
}
// 需要主动查询因为 instance 只有 id 属性
// 另外此时如果去查询 ProcessInstance 的话字段是不全的所以去查询了 HistoricProcessInstance
HistoricProcessInstance processInstance = getHistoricProcessInstance(event.getProcessInstanceId());
// 更新拓展表
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
.setProcessInstanceId(event.getProcessInstanceId())
.setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime所以这里设置
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.CANCEL.getResult());
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
@Override
public void updateProcessInstanceExtComplete(ProcessInstance instance) {
// 需要主动查询因为 instance 只有 id 属性
// 另外此时如果去查询 ProcessInstance 的话字段是不全的所以去查询了 HistoricProcessInstance
HistoricProcessInstance processInstance = getHistoricProcessInstance(instance.getId());
// 更新拓展表
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO()
.setProcessInstanceId(instance.getProcessInstanceId())
.setEndTime(new Date()) // 由于 ProcessInstance 里没有办法拿到 endTime所以这里设置
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); // 如果正常完全说明审批通过
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程被通过的消息
messageService.sendMessageWhenProcessInstanceApprove(BpmProcessInstanceConvert.INSTANCE.convert2ApprovedReq(instance));
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
@Transactional(rollbackFor = Exception.class)
public void updateProcessInstanceExtReject(String id, String reason) {
// 需要主动查询因为 instance 只有 id 属性
ProcessInstance processInstance = getProcessInstance(id);
// 删除流程实例以实现驳回任务时取消整个审批流程
deleteProcessInstance(id, StrUtil.format(BpmProcessInstanceDeleteReasonEnum.REJECT_TASK.format(reason)));
// 更新 status + result
// 注意不能和上面的逻辑更换位置因为 deleteProcessInstance 会触发流程的取消进而调用 updateProcessInstanceExtCancel 方法
// 设置 result BpmProcessInstanceStatusEnum.CANCEL显然和 result 不一定是一致的
BpmProcessInstanceExtDO instanceExtDO = new BpmProcessInstanceExtDO().setProcessInstanceId(id)
.setStatus(BpmProcessInstanceStatusEnum.FINISH.getStatus())
.setResult(BpmProcessInstanceResultEnum.REJECT.getResult());
processInstanceExtMapper.updateByProcessInstanceId(instanceExtDO);
// 发送流程被不通过的消息
messageService.sendMessageWhenProcessInstanceReject(BpmProcessInstanceConvert.INSTANCE.convert2RejectReq(processInstance, reason));
// 发送流程实例的状态事件
processInstanceResultEventPublisher.sendProcessInstanceResultEvent(
BpmProcessInstanceConvert.INSTANCE.convert(this, processInstance, instanceExtDO.getResult()));
}
private void deleteProcessInstance(String id, String reason) {
runtimeService.deleteProcessInstance(id, reason);
}
private String createProcessInstance0(Long userId, ProcessDefinition definition,
Map<String, Object> variables, String businessKey) {
// 校验流程定义
if (definition == null) {
throw exception(PROCESS_DEFINITION_NOT_EXISTS);
}
if (definition.isSuspended()) {
throw exception(PROCESS_DEFINITION_IS_SUSPENDED);
}
// 创建流程实例
ProcessInstance instance = runtimeService.startProcessInstanceById(definition.getId(), businessKey, variables);
// 设置流程名字
runtimeService.setProcessInstanceName(instance.getId(), definition.getName());
// 补全流程实例的拓展表
processInstanceExtMapper.updateByProcessInstanceId(new BpmProcessInstanceExtDO().setProcessInstanceId(instance.getId())
.setFormVariables(variables));
return instance.getId();
}
}

View File

@ -8,7 +8,7 @@
<version>${revision}</version> <version>${revision}</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bpm-base</artifactId> <artifactId>yudao-module-bpm-biz</artifactId>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<description> <description>
@ -26,6 +26,11 @@
<artifactId>yudao-module-system-api</artifactId> <artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-biz</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 --> <!-- 业务组件 -->
<dependency> <dependency>
@ -36,6 +41,10 @@
<groupId>cn.iocoder.boot</groupId> <groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId> <artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 --> <!-- Web 相关 -->
<dependency> <dependency>
@ -59,5 +68,10 @@
<groupId>cn.iocoder.boot</groupId> <groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId> <artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency> </dependency>
<!-- 工作流相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-flowable</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

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