mirror of
http://git.pig4cloud.com/pig/pigx.git
synced 2026-11-08 04:39:00 +08:00
feat(flow): 支持业务表单,提供业务表单示例
This commit is contained in:
1 parent
0d5326ff77
commit
cd1854e498
17 files changed
+2185
-128
No files matched your search
+58
@@ -0,0 +1,58 @@
|
||||
package com.pig4cloud.pigx.flow.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 流程实例参数数据传输对象
|
||||
* <p>
|
||||
* 该DTO用于启动流程实例或操作流程实例时传递参数。 包含流程定义信息、流程变量、发起人等核心信息。 主要用于流程引擎的实例创建和流程变量设置。
|
||||
* </p>
|
||||
*
|
||||
* @author pigx
|
||||
*/
|
||||
@Data
|
||||
public class ProcessInstanceParamDto {
|
||||
|
||||
/**
|
||||
* 流程定义ID
|
||||
* <p>
|
||||
* 要启动的流程定义的唯一标识。 对应已部署的流程模板ID,如:leave_process_v1。
|
||||
* </p>
|
||||
*/
|
||||
private String flowId;
|
||||
|
||||
/**
|
||||
* 流程变量集合
|
||||
* <p>
|
||||
* 存储流程执行过程中需要的各种变量,如: - 表单数据:申请金额、请假天数等 - 控制变量:审批级别、分支条件等 - 业务数据:关联的业务ID、业务类型等
|
||||
* 这些变量会在流程执行过程中被各个节点使用。
|
||||
* </p>
|
||||
*/
|
||||
private Map<String, Object> paramMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 流程发起人ID
|
||||
* <p>
|
||||
* 发起流程实例的用户ID。 用于记录流程的发起者,也可能作为第一个任务的执行人。
|
||||
* </p>
|
||||
*/
|
||||
private String startUserId;
|
||||
|
||||
/**
|
||||
* 流程实例ID
|
||||
* <p>
|
||||
* 已存在的流程实例ID,用于对已有流程实例进行操作。 如果是启动新流程,此字段为空; 如果是操作已有流程(如设置变量),则需要提供此ID。
|
||||
* </p>
|
||||
*/
|
||||
private String processInstanceId;
|
||||
|
||||
/** 状态 */
|
||||
private Integer status;
|
||||
|
||||
/** 完成原因 */
|
||||
private String finishReason;
|
||||
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package com.pig4cloud.pigx.flow.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 流程节点记录参数数据传输对象
|
||||
* <p>
|
||||
* 该DTO用于创建流程节点的执行记录。记录节点的执行信息, 包括节点类型、表单数据、执行上下文等。 主要用于流程历史记录的创建和流程轨迹的追踪。
|
||||
* </p>
|
||||
*
|
||||
* @author pigx
|
||||
*/
|
||||
@Data
|
||||
public class ProcessNodeRecordParamDto {
|
||||
|
||||
/**
|
||||
* 流程定义ID
|
||||
* <p>
|
||||
* 流程模板的唯一标识。 标识该记录所属的流程定义。
|
||||
* </p>
|
||||
*/
|
||||
private String flowId;
|
||||
|
||||
/**
|
||||
* 流程实例ID
|
||||
* <p>
|
||||
* 运行中的流程实例唯一标识。 用于关联到具体的流程实例。
|
||||
* </p>
|
||||
*/
|
||||
private String processInstanceId;
|
||||
|
||||
/**
|
||||
* 表单数据
|
||||
* <p>
|
||||
* JSON格式的表单数据,包含节点执行时的业务数据。 记录节点处理时的表单内容快照。
|
||||
* </p>
|
||||
*/
|
||||
private String data;
|
||||
|
||||
/**
|
||||
* 节点ID
|
||||
* <p>
|
||||
* 流程节点的唯一标识。 标识具体执行的是哪个节点。
|
||||
* </p>
|
||||
*/
|
||||
private String nodeId;
|
||||
|
||||
/**
|
||||
* 节点类型
|
||||
* <p>
|
||||
* 节点的类型标识,对应NodeTypeEnum: - ROOT: 发起人节点 - APPROVAL: 审批节点 - CC: 抄送节点 - CONDITION: 条件节点
|
||||
* - CONCURRENT: 并行节点 - DELAY: 延迟节点 - TRIGGER: 触发器节点 - EMPTY: 空节点 - END: 结束节点
|
||||
* </p>
|
||||
*/
|
||||
private String nodeType;
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
* <p>
|
||||
* 流程节点的显示名称,如"部门经理审批"、"财务审核"等。 用于在界面上展示,便于用户理解。
|
||||
* </p>
|
||||
*/
|
||||
private String nodeName;
|
||||
|
||||
/**
|
||||
* 执行ID
|
||||
* <p>
|
||||
* Flowable引擎的执行ID。 用于关联流程引擎中的执行实例。
|
||||
* </p>
|
||||
*/
|
||||
private String executionId;
|
||||
|
||||
/**
|
||||
* 任务ID
|
||||
* <p>
|
||||
* Flowable引擎生成的任务ID。 对于用户任务节点,记录对应的任务ID。
|
||||
* </p>
|
||||
*/
|
||||
private String taskId;
|
||||
|
||||
/**
|
||||
* 审批意见
|
||||
* <p>
|
||||
* 节点执行时的意见或备注。 通常用于审批节点记录审批意见。
|
||||
* </p>
|
||||
*/
|
||||
private String approveDesc;
|
||||
|
||||
/**
|
||||
* 任务类型
|
||||
* <p>
|
||||
* 标识任务的具体类型,用于区分不同的处理逻辑: - APPROVAL: 审批任务 - COPY: 抄送任务 - NOTIFY: 通知任务 - SYSTEM: 系统任务
|
||||
* </p>
|
||||
*/
|
||||
private String taskType;
|
||||
|
||||
/** 状态 */
|
||||
private Integer status;
|
||||
|
||||
/** 完成原因 */
|
||||
private String finishReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.pig4cloud.pigx.flow.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.pig4cloud.pigx.common.core.util.TenantTable;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 流程定义实体类
|
||||
* <p>
|
||||
* 该实体类用于存储工作流程的定义信息,包括流程的基本信息、表单配置、流程配置等。 每个流程定义可以创建多个流程实例,支持多租户隔离。
|
||||
* </p>
|
||||
*
|
||||
* @author Vincent
|
||||
* @since 2023-07-06
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@TenantTable
|
||||
@Accessors(chain = true)
|
||||
@TableName("process")
|
||||
public class Process {
|
||||
|
||||
/**
|
||||
* 流程唯一标识ID 用于唯一标识一个流程定义,在流程实例创建时使用
|
||||
*/
|
||||
@TableField("flow_id")
|
||||
private String flowId;
|
||||
|
||||
/**
|
||||
* 流程名称 流程的显示名称,用于在流程列表中展示
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 流程图标配置 存储流程的图标信息,支持自定义图标样式
|
||||
*/
|
||||
@TableField("logo")
|
||||
private String logo;
|
||||
|
||||
/**
|
||||
* 流程设置项 JSON格式存储流程的各种配置信息,如通知设置、权限设置等
|
||||
*/
|
||||
@TableField("settings")
|
||||
private String settings;
|
||||
|
||||
/**
|
||||
* 流程分组ID 关联process_group表,用于对流程进行分类管理
|
||||
*/
|
||||
@TableField("group_id")
|
||||
private Long groupId;
|
||||
|
||||
/** 表单配置 */
|
||||
@TableField("form_config")
|
||||
private String formConfig;
|
||||
|
||||
/**
|
||||
* 表单配置内容 JSON格式存储表单的结构定义,包括表单项类型、验证规则、默认值等
|
||||
*/
|
||||
@TableField("form_items")
|
||||
private String formItems;
|
||||
|
||||
/**
|
||||
* 流程配置内容 JSON格式存储流程的节点信息、流转条件、审批规则等核心流程定义
|
||||
*/
|
||||
@TableField("process")
|
||||
private String process;
|
||||
|
||||
/**
|
||||
* 流程备注说明 用于记录流程的用途、注意事项等补充信息
|
||||
*/
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 排序号 用于控制流程在列表中的显示顺序,数值越小越靠前
|
||||
*/
|
||||
@TableField("sort")
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 流程隐藏标识 0=正常显示,1=隐藏(隐藏的流程不在列表中显示,但仍可使用)
|
||||
*/
|
||||
@TableField("is_hidden")
|
||||
private String hidden;
|
||||
|
||||
/**
|
||||
* 流程停用标识 0=正常启用,1=停用(停用的流程不能创建新实例)
|
||||
*/
|
||||
@TableField("is_stop")
|
||||
private String stop;
|
||||
|
||||
/**
|
||||
* 流程管理员ID 主管理员用户ID,拥有该流程的完全管理权限
|
||||
*/
|
||||
@TableField("admin_id")
|
||||
private Long adminId;
|
||||
|
||||
/**
|
||||
* 流程全局唯一ID 用于流程的版本控制和跨系统识别
|
||||
*/
|
||||
@TableField("unique_id")
|
||||
private String uniqueId;
|
||||
|
||||
/**
|
||||
* 管理员列表 JSON格式存储多个管理员ID,支持多人共同管理流程
|
||||
*/
|
||||
@TableField("admin_list")
|
||||
private String adminList;
|
||||
|
||||
/**
|
||||
* 流程使用范围描述 显示该流程的适用部门、人员范围等信息
|
||||
*/
|
||||
@TableField("range_show")
|
||||
private String rangeShow;
|
||||
|
||||
/**
|
||||
* 主键ID 使用雪花算法生成的分布式ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 逻辑删除字段
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
package com.pig4cloud.pigx.flow.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto;
|
||||
import com.pig4cloud.pigx.flow.dto.TaskDto;
|
||||
import com.pig4cloud.pigx.flow.dto.TaskQueryParamDto;
|
||||
import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.common.engine.impl.identity.Authentication;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.engine.history.HistoricActivityInstance;
|
||||
import org.flowable.engine.history.HistoricActivityInstanceQuery;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.TaskQuery;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 流程引擎核心控制器
|
||||
* <p>
|
||||
* 该控制器是工作流引擎的核心接口,直接与Flowable引擎交互,提供流程生命周期管理功能: 1. 流程定义的创建和部署 - 将前端设计的流程模型转换为BPMN格式并部署到引擎
|
||||
* 2. 流程实例的启动和管理 - 启动流程实例、停止运行中的流程 3. 任务的查询和处理 - 查询待办任务、已办任务,执行任务审批
|
||||
* </p>
|
||||
* <p>
|
||||
* 该控制器主要供内部系统调用,通过封装Flowable原生API,简化流程操作。 所有操作都支持多租户隔离,确保数据安全。
|
||||
* </p>
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/flow")
|
||||
public class ProcessFlowController {
|
||||
|
||||
private final TaskService taskService;
|
||||
|
||||
private final HistoryService historyService;
|
||||
|
||||
private final RepositoryService repositoryService;
|
||||
|
||||
private final RuntimeService runtimeService;
|
||||
|
||||
/**
|
||||
* 启动流程实例
|
||||
* <p>
|
||||
* 根据流程定义ID启动一个新的流程实例。启动时可以传入流程变量, 这些变量会在整个流程执行过程中使用(如条件判断、人员分配等)。
|
||||
* </p>
|
||||
* @param processInstanceParamDto 启动参数,包含: - flowId: 流程定义ID - startUserId: 发起人用户ID -
|
||||
* paramMap: 流程变量Map,用于流程执行中的条件判断和数据传递
|
||||
* @return R 统一响应对象,包含启动成功的流程实例ID
|
||||
*/
|
||||
@PostMapping("/start")
|
||||
public R start(@RequestBody ProcessInstanceParamDto processInstanceParamDto) {
|
||||
Authentication.setAuthenticatedUserId(processInstanceParamDto.getStartUserId());
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(
|
||||
processInstanceParamDto.getFlowId(), processInstanceParamDto.getParamMap(),
|
||||
TenantContextHolder.getTenantId().toString());
|
||||
|
||||
String processInstanceId = processInstance.getProcessInstanceId();
|
||||
return R.ok(processInstanceId);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批任务(测试接口)
|
||||
* <p>
|
||||
* 该接口用于测试流程审批功能,完成指定的任务并设置审批结果。 实际业务中应使用TaskController中更完善的审批接口。
|
||||
* </p>
|
||||
* @param taskId 待审批的任务ID
|
||||
* @param approved 审批结果:true-通过,false-拒绝
|
||||
*/
|
||||
@PostMapping("/approve")
|
||||
public void approve(String taskId, boolean approved) {
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
variables.put("approved", approved);
|
||||
variables.put("ko", 10);
|
||||
variables.put("assigneeListSub", CollUtil.newArrayList("aa", "bb"));
|
||||
taskService.complete(taskId, variables);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询用户已办任务
|
||||
* <p>
|
||||
* 查询指定用户已经完成的历史任务,支持按时间范围过滤。 返回的任务信息包含任务处理时长、完成时间等历史数据。
|
||||
* </p>
|
||||
* @param taskQueryParamDto 查询参数,包含: - assign: 任务处理人ID - taskTime: 任务时间范围数组 [开始时间,
|
||||
* 结束时间] - current: 当前页码 - size: 每页大小
|
||||
* @return R 统一响应对象,包含分页的已办任务列表(TaskDto)
|
||||
*/
|
||||
@PostMapping("/queryCompletedTask")
|
||||
public R queryCompletedTask(@RequestBody TaskQueryParamDto taskQueryParamDto) {
|
||||
HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService
|
||||
.createHistoricActivityInstanceQuery();
|
||||
HistoricActivityInstanceQuery activityInstanceQuery = historicActivityInstanceQuery
|
||||
.taskAssignee(taskQueryParamDto.getAssign())
|
||||
.finished()
|
||||
.orderByHistoricActivityInstanceEndTime()
|
||||
.desc();
|
||||
|
||||
if (ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime())) {
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
ZonedDateTime zonedBeforeDateTime = taskQueryParamDto.getTaskTime()[0].atZone(zoneId);
|
||||
ZonedDateTime zonedAfterDateTime = taskQueryParamDto.getTaskTime()[1].atZone(zoneId);
|
||||
Date beforeDate = Date.from(zonedBeforeDateTime.toInstant());
|
||||
Date afterDate = Date.from(zonedAfterDateTime.toInstant());
|
||||
activityInstanceQuery.finishedBefore(afterDate).finishedAfter(beforeDate);
|
||||
}
|
||||
|
||||
List<HistoricActivityInstance> list = activityInstanceQuery
|
||||
.listPage((taskQueryParamDto.getCurrent() - 1) * taskQueryParamDto.getSize(), taskQueryParamDto.getSize());
|
||||
|
||||
long count = activityInstanceQuery.count();
|
||||
List<TaskDto> taskDtoList = new ArrayList<>();
|
||||
|
||||
for (HistoricActivityInstance historicActivityInstance : list) {
|
||||
String activityId = historicActivityInstance.getActivityId();
|
||||
String activityName = historicActivityInstance.getActivityName();
|
||||
String executionId = historicActivityInstance.getExecutionId();
|
||||
String taskId = historicActivityInstance.getTaskId();
|
||||
Date startTime = historicActivityInstance.getStartTime();
|
||||
Date endTime = historicActivityInstance.getEndTime();
|
||||
Long durationInMillis = historicActivityInstance.getDurationInMillis();
|
||||
String processInstanceId = historicActivityInstance.getProcessInstanceId();
|
||||
|
||||
String processDefinitionId = historicActivityInstance.getProcessDefinitionId();
|
||||
// 流程id
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
|
||||
TaskDto taskDto = new TaskDto();
|
||||
taskDto.setFlowId(flowId);
|
||||
taskDto.setTaskCreateTime(startTime);
|
||||
taskDto.setTaskEndTime(endTime);
|
||||
taskDto.setNodeId(activityId);
|
||||
taskDto.setExecutionId(executionId);
|
||||
taskDto.setProcessInstanceId(processInstanceId);
|
||||
taskDto.setDurationInMillis(durationInMillis);
|
||||
taskDto.setTaskId(taskId);
|
||||
taskDto.setAssign(historicActivityInstance.getAssignee());
|
||||
taskDto.setTaskName(activityName);
|
||||
|
||||
taskDtoList.add(taskDto);
|
||||
}
|
||||
|
||||
Page<TaskDto> pageResultDto = new Page<>();
|
||||
pageResultDto.setTotal(count);
|
||||
pageResultDto.setRecords(taskDtoList);
|
||||
return R.ok(pageResultDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询用户待办任务
|
||||
* <p>
|
||||
* 查询分配给指定用户的待处理任务,支持按流程名称和创建时间范围过滤。 待办任务是需要用户处理的活动任务,处理后任务将流转到下一节点。
|
||||
* </p>
|
||||
* @param taskQueryParamDto 查询参数,包含: - assign: 任务处理人ID - processName: 流程名称(可选,用于过滤) -
|
||||
* taskTime: 任务创建时间范围数组 [开始时间, 结束时间] - current: 当前页码 - size: 每页大小
|
||||
* @return R 统一响应对象,包含分页的待办任务列表(TaskDto)
|
||||
*/
|
||||
@PostMapping("/queryAssignTask")
|
||||
public R queryAssignTask(@RequestBody TaskQueryParamDto taskQueryParamDto) {
|
||||
|
||||
String assign = taskQueryParamDto.getAssign();
|
||||
|
||||
List<TaskDto> taskDtoList = new ArrayList<>();
|
||||
|
||||
int pageIndex = taskQueryParamDto.getCurrent() - 1;
|
||||
int pageSize = taskQueryParamDto.getSize();
|
||||
|
||||
Page<TaskDto> pageResultDto = new Page<>();
|
||||
TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assign).orderByTaskCreateTime().desc();
|
||||
if (StrUtil.isNotBlank(taskQueryParamDto.getProcessName())) {
|
||||
List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery()
|
||||
.processDefinitionName(taskQueryParamDto.getProcessName())
|
||||
.processInstanceTenantId(TenantContextHolder.getTenantId().toString())
|
||||
.list();
|
||||
if (CollUtil.isNotEmpty(processInstanceList)) {
|
||||
taskQuery.processInstanceIdIn(
|
||||
processInstanceList.stream().map(ProcessInstance::getProcessInstanceId).toList());
|
||||
}
|
||||
}
|
||||
|
||||
if (ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime())) {
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
ZonedDateTime zonedBeforeDateTime = taskQueryParamDto.getTaskTime()[0].atZone(zoneId);
|
||||
ZonedDateTime zonedAfterDateTime = taskQueryParamDto.getTaskTime()[1].atZone(zoneId);
|
||||
Date beforeDate = Date.from(zonedBeforeDateTime.toInstant());
|
||||
Date afterDate = Date.from(zonedAfterDateTime.toInstant());
|
||||
taskQuery.taskCreatedBefore(afterDate).taskCreatedAfter(beforeDate);
|
||||
}
|
||||
|
||||
taskQuery.listPage(pageIndex * pageSize, pageSize).forEach(task -> {
|
||||
String taskId = task.getId();
|
||||
String processInstanceId = task.getProcessInstanceId();
|
||||
log.debug("(taskId) {} processInstanceId={} executrionId={}", task.getName(), processInstanceId,
|
||||
task.getExecutionId());
|
||||
|
||||
String taskDefinitionKey = task.getTaskDefinitionKey();
|
||||
String processDefinitionId = task.getProcessDefinitionId();
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
|
||||
TaskDto taskDto = new TaskDto();
|
||||
taskDto.setFlowId(flowId);
|
||||
taskDto.setTaskCreateTime(task.getCreateTime());
|
||||
taskDto.setNodeId(taskDefinitionKey);
|
||||
taskDto.setProcessInstanceId(processInstanceId);
|
||||
taskDto.setTaskId(taskId);
|
||||
taskDto.setAssign(task.getAssignee());
|
||||
taskDto.setTaskName(task.getName());
|
||||
taskDtoList.add(taskDto);
|
||||
});
|
||||
|
||||
long count = taskQuery.count();
|
||||
|
||||
log.debug("当前有 {} 个任务:", count);
|
||||
|
||||
pageResultDto.setTotal(count);
|
||||
pageResultDto.setRecords(taskDtoList);
|
||||
|
||||
return R.ok(pageResultDto);
|
||||
}
|
||||
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.pig4cloud.pigx.flow.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import com.pig4cloud.pigx.common.core.util.TenantTable;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 请假表单
|
||||
*
|
||||
* @author pigx
|
||||
* @date 2025-08-08 09:32:41
|
||||
*/
|
||||
@Data
|
||||
@TenantTable
|
||||
@TableName("bpm_oa_leave")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "请假表单")
|
||||
public class BpmOaLeaveEntity extends Model<BpmOaLeaveEntity> {
|
||||
|
||||
/**
|
||||
* 请假表单主键
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
@Schema(description = "请假表单主键")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 申请人的用户编号
|
||||
*/
|
||||
@Schema(description = "申请人的用户编号")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 请假类型
|
||||
*/
|
||||
@Schema(description = "请假类型")
|
||||
private Integer leaveType;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
@Schema(description = "请假原因")
|
||||
private String leaveReason;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
@Schema(description = "开始时间")
|
||||
private LocalDateTime startTime;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
@Schema(description = "结束时间")
|
||||
private LocalDateTime endTime;
|
||||
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
@Schema(description = "请假天数")
|
||||
private Integer leaveDay;
|
||||
|
||||
/**
|
||||
* 请假结果
|
||||
*/
|
||||
@Schema(description = "请假结果")
|
||||
private Integer leaveStatus;
|
||||
|
||||
/**
|
||||
* 流程实例的编号
|
||||
*/
|
||||
@Schema(description = "流程实例的编号")
|
||||
private String processInstanceId;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "创建者")
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
@Schema(description = "更新者")
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "是否删除")
|
||||
private String delFlag;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
@Schema(description = "租户编号")
|
||||
private Long tenantId;
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.pig4cloud.pigx.flow.mapper;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pig4cloud.pigx.flow.entity.BpmOaLeaveEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface BpmOaLeaveMapper extends BaseMapper<BpmOaLeaveEntity> {
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package com.pig4cloud.pigx.flow.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.flow.dto.Node;
|
||||
import com.pig4cloud.pigx.flow.dto.ProcessNodeDataDto;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessNodeData;
|
||||
import com.pig4cloud.pigx.flow.mapper.ProcessNodeDataMapper;
|
||||
import com.pig4cloud.pigx.flow.service.IProcessNodeDataService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 流程节点数据服务实现类
|
||||
* <p>
|
||||
* 该类负责管理流程节点的详细数据,主要功能包括: 1. 保存流程节点的配置数据(审批人、表单权限、条件等) 2. 查询指定节点的详细配置信息 3.
|
||||
* 为流程执行提供节点配置数据支持
|
||||
*
|
||||
* 节点数据以JSON格式存储,包含节点类型、审批人配置、表单权限等信息
|
||||
* </p>
|
||||
*
|
||||
* @author Vincent
|
||||
* @since 2023-05-07
|
||||
*/
|
||||
@Service
|
||||
public class ProcessNodeDataServiceImpl extends ServiceImpl<ProcessNodeDataMapper, ProcessNodeData>
|
||||
implements IProcessNodeDataService {
|
||||
|
||||
/**
|
||||
* 保存流程节点数据
|
||||
* <p>
|
||||
* 将流程节点的配置信息保存到数据库。节点数据包括: - 节点类型(审批、抄送、条件等) - 审批人配置 - 表单权限设置 - 条件表达式等
|
||||
* </p>
|
||||
* @param processNodeDataDto 流程节点数据DTO,包含flowId、nodeId和data(JSON格式的节点配置)
|
||||
* @return R 保存结果
|
||||
*/
|
||||
@Override
|
||||
public R saveNodeData(ProcessNodeDataDto processNodeDataDto) {
|
||||
ProcessNodeData processNodeData = BeanUtil.copyProperties(processNodeDataDto, ProcessNodeData.class);
|
||||
baseMapper.delete(Wrappers.<ProcessNodeData>lambdaQuery()
|
||||
.eq(ProcessNodeData::getNodeId, processNodeData.getNodeId())
|
||||
.eq(ProcessNodeData::getFlowId, processNodeData.getFlowId()));
|
||||
baseMapper.insert(processNodeData);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取节点数据
|
||||
* <p>
|
||||
* 根据流程ID和节点ID查询节点的详细配置信息。 节点数据存储为JSON格式,查询后会自动转换为Node对象
|
||||
* </p>
|
||||
* @param flowId 流程ID
|
||||
* @param nodeId 节点ID
|
||||
* @return R<Node> 包含节点配置的Node对象,如果节点不存在则返回null
|
||||
*/
|
||||
@Override
|
||||
public R<Node> getNodeData(String flowId, String nodeId) {
|
||||
Optional<ProcessNodeData> processNodeDataOptional = this.lambdaQuery()
|
||||
.eq(ProcessNodeData::getFlowId, flowId)
|
||||
.eq(ProcessNodeData::getNodeId, nodeId)
|
||||
.oneOpt();
|
||||
|
||||
Node node = processNodeDataOptional
|
||||
.map(processNodeData -> JSONUtil.toBean(processNodeData.getData(), Node.class))
|
||||
.orElse(null);
|
||||
|
||||
return R.ok(node);
|
||||
}
|
||||
|
||||
}
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
package com.pig4cloud.pigx.flow.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pigx.common.core.constant.enums.YesNoEnum;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import com.pig4cloud.pigx.common.sequence.sequence.Sequence;
|
||||
import com.pig4cloud.pigx.flow.constant.FormTypeEnum;
|
||||
import com.pig4cloud.pigx.flow.dto.Node;
|
||||
import com.pig4cloud.pigx.flow.dto.NodeUser;
|
||||
import com.pig4cloud.pigx.flow.entity.Process;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessStarter;
|
||||
import com.pig4cloud.pigx.flow.mapper.ProcessMapper;
|
||||
import com.pig4cloud.pigx.flow.service.IProcessService;
|
||||
import com.pig4cloud.pigx.flow.service.IProcessStarterService;
|
||||
import com.pig4cloud.pigx.flow.support.utils.ModelUtil;
|
||||
import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
|
||||
import com.pig4cloud.pigx.flow.vo.FormItemVO;
|
||||
import com.pig4cloud.pigx.flow.vo.ProcessVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.bpmn.converter.BpmnXMLConverter;
|
||||
import org.flowable.bpmn.model.BpmnModel;
|
||||
import org.flowable.engine.RepositoryService;
|
||||
import org.flowable.engine.repository.Deployment;
|
||||
import org.flowable.engine.repository.ProcessDefinition;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 流程定义服务实现类
|
||||
* <p>
|
||||
* 实现流程定义的核心业务逻辑,包括流程的创建、编辑、查询、部署等功能。 该类是流程管理的核心实现,负责: - 流程定义的CRUD操作 - 流程表单的动态配置管理 -
|
||||
* 流程节点的解析和验证 - 与Flowable引擎的交互部署 - 流程权限的管理和控制
|
||||
* </p>
|
||||
*
|
||||
* @author cxygzl
|
||||
* @since 2023-05-25
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ProcessServiceImpl extends ServiceImpl<ProcessMapper, Process> implements IProcessService {
|
||||
|
||||
private final IProcessStarterService processStarterService;
|
||||
|
||||
private final RepositoryService repositoryService;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private final Sequence flowSequence;
|
||||
|
||||
/**
|
||||
* 获取流程详细信息
|
||||
* <p>
|
||||
* 根据流程ID获取完整的流程配置信息,包括: - 流程基本信息 - 表单配置(自动生成序列号字段的值) - 需要发起人选择执行人的节点列表 - 表单权限配置
|
||||
* </p>
|
||||
* @param flowId 流程定义ID
|
||||
* @return 包含流程详细信息的响应对象
|
||||
*/
|
||||
@Override
|
||||
public R<ProcessVO> getDetail(String flowId) {
|
||||
// 获取流程详情
|
||||
ProcessVO processVO = this.getProcessVO(flowId);
|
||||
return R.ok(processVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流程视图对象
|
||||
* <p>
|
||||
* 内部方法,用于构建流程的视图对象。主要处理: - 解析流程节点结构 - 查找需要发起人选择的节点 - 处理序列号类型的表单字段,自动生成下一个序号 -
|
||||
* 提取起始节点的表单权限配置
|
||||
* </p>
|
||||
* @param flowId 流程定义ID
|
||||
* @return 流程视图对象,包含所有展示所需的信息
|
||||
*/
|
||||
@SneakyThrows
|
||||
private ProcessVO getProcessVO(String flowId) {
|
||||
|
||||
Process oaForms = getByFlowId(flowId);
|
||||
String process = oaForms.getProcess();
|
||||
String formItems = oaForms.getFormItems();
|
||||
Node startNode = objectMapper.readValue(process, new TypeReference<>() {
|
||||
});
|
||||
|
||||
List<String> selectUserNodeId = NodeUtil.selectUserNodeId(startNode);
|
||||
|
||||
ProcessVO processVO = BeanUtil.copyProperties(oaForms, ProcessVO.class);
|
||||
processVO.setSelectUserNodeId(selectUserNodeId);
|
||||
|
||||
JSONArray formItemJsonArray = JSONUtil.parseArray(formItems);
|
||||
// 特殊字段复制
|
||||
for (Object object : formItemJsonArray) {
|
||||
if (object instanceof JSONObject) {
|
||||
JSONObject formVO = (JSONObject) object;
|
||||
JSONObject props = formVO.getJSONObject(FormItemVO.Fields.props);
|
||||
if (Objects.nonNull(props)
|
||||
&& FormTypeEnum.SEQUENCE.getType().equals(props.getStr(FormItemVO.Fields.type))) {
|
||||
String nextNo = flowSequence.nextNo();
|
||||
props.set(FormItemVO.Props.Fields.value, nextNo);
|
||||
formVO.set(FormItemVO.Props.Fields.value, nextNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processVO.setFormItems(formItemJsonArray.toStringPretty());
|
||||
processVO.setFormPerms(startNode.getFormPerms());
|
||||
return processVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据流程ID查询流程定义
|
||||
* <p>
|
||||
* 通过流程的唯一标识符查询流程定义实体。 注意:一个流程可能有多个版本,此方法返回当前有效版本。
|
||||
* </p>
|
||||
* @param flowId 流程定义ID
|
||||
* @return 流程定义实体,如果不存在返回null
|
||||
*/
|
||||
@Override
|
||||
public Process getByFlowId(String flowId) {
|
||||
return this.lambdaQuery().eq(Process::getFlowId, flowId).one();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据流程ID更新流程定义
|
||||
* <p>
|
||||
* 更新流程的配置信息,包括表单、节点、权限等。 更新不会影响已经运行的流程实例。
|
||||
* </p>
|
||||
* @param process 包含更新信息的流程实体
|
||||
*/
|
||||
@Override
|
||||
public void updateByFlowId(Process process) {
|
||||
this.lambdaUpdate().eq(Process::getFlowId, process.getFlowId()).update(process);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏流程定义
|
||||
* <p>
|
||||
* 将流程标记为隐藏状态,隐藏的流程: - 不在流程列表中显示 - 不能创建新的流程实例 - 已有的流程实例可以继续执行
|
||||
* </p>
|
||||
* @param flowId 要隐藏的流程ID
|
||||
*/
|
||||
@Override
|
||||
public void hide(String flowId) {
|
||||
this.lambdaUpdate()
|
||||
.set(Process::getHidden, YesNoEnum.YES.getCode())
|
||||
.eq(Process::getFlowId, flowId)
|
||||
.update(new Process());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的流程定义
|
||||
* <p>
|
||||
* 创建流程的完整流程: 1. 调用流程引擎创建BPMN模型 2. 如果是更新操作,隐藏旧版本 3. 保存流程定义到数据库 4. 设置流程发起人权限 5.
|
||||
* 生成流程使用范围描述
|
||||
* </p>
|
||||
* @param process 流程定义实体,包含流程的所有配置信息
|
||||
* @return 创建结果,成功返回流程ID,失败返回错误信息
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public R create(Process process) {
|
||||
String flowId;
|
||||
if (StrUtil.isNotBlank(process.getFlowId())) {
|
||||
flowId = process.getFlowId();
|
||||
}
|
||||
else {
|
||||
flowId = "P" + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN)
|
||||
+ RandomUtil.randomString(5).toUpperCase();
|
||||
}
|
||||
|
||||
BpmnModel bpmnModel = ModelUtil.buildBpmnModel(objectMapper.readValue(process.getProcess(), Node.class),
|
||||
process.getName(), flowId);
|
||||
{
|
||||
byte[] bpmnBytess = new BpmnXMLConverter().convertToXML(bpmnModel);
|
||||
String filename = "/tmp/flowable-deployment/" + flowId + ".bpmn20.xml";
|
||||
log.debug("部署时的模型文件:{}", filename);
|
||||
FileUtil.writeBytes(bpmnBytess, filename);
|
||||
}
|
||||
|
||||
Process oldProcess = this.getByFlowId(flowId);
|
||||
if (Objects.nonNull(oldProcess) && StrUtil.isNotBlank(oldProcess.getUniqueId())) {
|
||||
// 挂起原有流程定义
|
||||
String oldDeploymentId = oldProcess.getUniqueId();
|
||||
|
||||
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
|
||||
.deploymentId(oldDeploymentId)
|
||||
.singleResult();
|
||||
repositoryService.suspendProcessDefinitionById(processDefinition.getId(), true, null);
|
||||
}
|
||||
|
||||
Deployment deployment = repositoryService.createDeployment()
|
||||
.addBpmnModel(StrUtil.format("{}.bpmn20.xml", "pig"), bpmnModel)
|
||||
.tenantId(TenantContextHolder.getTenantId().toString())
|
||||
.deploy();
|
||||
|
||||
NodeUser nodeUser = objectMapper.readValue(process.getAdminList(), new TypeReference<List<NodeUser>>() {
|
||||
}).get(0);
|
||||
|
||||
// 更新流程
|
||||
if (StrUtil.isNotBlank(process.getFlowId()) && Objects.nonNull(oldProcess)) {
|
||||
this.hide(process.getFlowId());
|
||||
// 修改所有的管理员
|
||||
this.lambdaUpdate()
|
||||
.set(Process::getAdminId, nodeUser.getId())
|
||||
.eq(Process::getUniqueId, oldProcess.getUniqueId())
|
||||
.update(new Process());
|
||||
|
||||
}
|
||||
|
||||
Node startNode = objectMapper.readValue(process.getProcess(), Node.class);
|
||||
|
||||
List<NodeUser> nodeUserList = startNode.getNodeUserList();
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (CollUtil.isNotEmpty(nodeUserList)) {
|
||||
int index = 0;
|
||||
|
||||
for (NodeUser user : nodeUserList) {
|
||||
if (index > 0) {
|
||||
stringBuilder.append(",");
|
||||
}
|
||||
stringBuilder.append(user.getName());
|
||||
index++;
|
||||
if (index > 5) {
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Process p = new Process();
|
||||
|
||||
if (Objects.nonNull(oldProcess)) {
|
||||
p = oldProcess;
|
||||
}
|
||||
|
||||
p.setFlowId(flowId);
|
||||
p.setName(process.getName());
|
||||
p.setLogo(process.getLogo());
|
||||
p.setSettings(process.getSettings());
|
||||
p.setGroupId(process.getGroupId());
|
||||
p.setFormConfig(process.getFormConfig());
|
||||
p.setFormItems(process.getFormItems());
|
||||
p.setProcess(process.getProcess());
|
||||
p.setRemark(process.getRemark());
|
||||
p.setSort(0);
|
||||
p.setHidden("0");
|
||||
p.setStop("0");
|
||||
p.setAdminId(nodeUser.getId());
|
||||
p.setUniqueId(deployment.getId());
|
||||
p.setAdminList(process.getAdminList());
|
||||
p.setRangeShow(stringBuilder.toString());
|
||||
this.saveOrUpdate(p);
|
||||
|
||||
// 保存范围
|
||||
for (NodeUser nodeUserDto : nodeUserList) {
|
||||
ProcessStarter processStarter = new ProcessStarter();
|
||||
|
||||
processStarter.setProcessId(p.getId());
|
||||
processStarter.setTypeId(nodeUserDto.getId());
|
||||
processStarter.setType(nodeUserDto.getType());
|
||||
processStarterService.save(processStarter);
|
||||
}
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新流程状态或分组
|
||||
* <p>
|
||||
* 支持三种操作类型: - stop:停用流程(设置stop=1) - delete:删除流程(逻辑删除,设置hidden=1) - 其他:启用流程(设置stop=0)
|
||||
* 同时支持修改流程所属分组
|
||||
* </p>
|
||||
* @param flowId 流程定义ID
|
||||
* @param type 操作类型:stop(停用)、delete(删除)、其他(启用)
|
||||
* @param groupId 流程分组ID,可选参数
|
||||
* @return 操作结果
|
||||
*/
|
||||
@Override
|
||||
public R update(String flowId, String type, Long groupId) {
|
||||
Process process = new Process();
|
||||
process.setFlowId(flowId);
|
||||
process.setStop("stop".equals(type) ? "1" : "0");
|
||||
process.setHidden("delete".equals(type) ? "1" : "0");
|
||||
process.setGroupId(groupId);
|
||||
this.updateByFlowId(process);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
package com.pig4cloud.pigx.flow.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
|
||||
import com.pig4cloud.pigx.flow.constant.NodeStatusEnum;
|
||||
import com.pig4cloud.pigx.flow.dto.IndexPageStatistics;
|
||||
import com.pig4cloud.pigx.flow.dto.Node;
|
||||
import com.pig4cloud.pigx.flow.dto.TaskParamDto;
|
||||
import com.pig4cloud.pigx.flow.dto.TaskResultDto;
|
||||
import com.pig4cloud.pigx.flow.entity.Process;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessCopy;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessInstanceRecord;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessNodeRecordAssignUser;
|
||||
import com.pig4cloud.pigx.flow.service.*;
|
||||
import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.engine.history.HistoricActivityInstanceQuery;
|
||||
import org.flowable.engine.runtime.Execution;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.DelegationState;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.flowable.task.api.TaskQuery;
|
||||
import org.flowable.task.api.history.HistoricTaskInstance;
|
||||
import org.flowable.variable.api.history.HistoricVariableInstance;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 任务服务实现类
|
||||
* <p>
|
||||
* 该类是流程任务操作的核心服务,负责处理用户与流程任务的交互,主要功能包括: 1. 任务查询(待办任务详情、任务表单权限等) 2. 任务操作(完成、委托、转办、退回等) 3.
|
||||
* 任务统计(首页数据看板) 4. 流程控制(终止流程、加签等高级操作)
|
||||
*
|
||||
* 该服务整合了流程引擎的任务API,为前端提供统一的任务操作接口
|
||||
* </p>
|
||||
*
|
||||
* @author pigx code generator
|
||||
* @date 2023-10-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class TaskServiceImpl implements ITaskService {
|
||||
|
||||
private final IProcessService processService;
|
||||
|
||||
private final IProcessCopyService processCopyService;
|
||||
|
||||
private final IProcessNodeDataService nodeDataService;
|
||||
|
||||
private final IProcessNodeRecordAssignUserService processNodeRecordAssignUserService;
|
||||
|
||||
private final IProcessInstanceRecordService processInstanceRecordService;
|
||||
|
||||
private final IFlowableEngineService flowableEngineService;
|
||||
|
||||
private final TaskService taskService;
|
||||
|
||||
private final HistoryService historyService;
|
||||
|
||||
private final RuntimeService runtimeService;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 查询首页数据看板
|
||||
* <p>
|
||||
* 获取当前用户的任务统计数据,包括: 1. 待办任务数量 2. 已办任务数量 3. 我发起的流程数量 4. 抄送给我的数量
|
||||
* </p>
|
||||
* @return R 包含IndexPageStatistics对象,含有各项统计数据
|
||||
*/
|
||||
@Override
|
||||
public R queryTaskData() {
|
||||
|
||||
TaskQuery taskQuery = taskService.createTaskQuery();
|
||||
|
||||
// 待办数量
|
||||
long pendingNum = taskQuery.taskAssignee(String.valueOf(SecurityUtils.getUser().getId()))
|
||||
.taskTenantId(TenantContextHolder.getTenantId().toString())
|
||||
.count();
|
||||
// 已完成任务
|
||||
HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService
|
||||
.createHistoricActivityInstanceQuery();
|
||||
|
||||
long completedNum = historicActivityInstanceQuery.taskAssignee(String.valueOf(SecurityUtils.getUser().getId()))
|
||||
.tenantIdIn(List.of(TenantContextHolder.getTenantId().toString()))
|
||||
.finished()
|
||||
.count();
|
||||
|
||||
IndexPageStatistics indexPageStatistics = IndexPageStatistics.builder()
|
||||
.pendingNum(pendingNum)
|
||||
.completedNum(completedNum)
|
||||
.build();
|
||||
|
||||
// 获取抄送任务
|
||||
Long copyCount = processCopyService.lambdaQuery()
|
||||
.eq(ProcessCopy::getUserId, SecurityUtils.getUser().getId())
|
||||
.count();
|
||||
indexPageStatistics.setCopyNum(copyCount);
|
||||
return R.ok(indexPageStatistics);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询任务
|
||||
* <p>
|
||||
* 查询指定任务的详细信息,包括: 1. 任务的基本信息(节点名称、执行人等) 2. 流程表单定义和表单数据 3. 当前节点的表单权限配置 4.
|
||||
* 任务是否可委托、是否是委托任务等状态
|
||||
*
|
||||
* 对于已完成的任务,会从历史记录中获取任务数据
|
||||
* </p>
|
||||
* @param taskId 任务ID
|
||||
* @param view 是否是查看模式(true:只查看,false:可操作)
|
||||
* @return R 包含任务详情的Dict对象
|
||||
* @throws Exception 当解析JSON数据失败时抛出异常
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public R queryTask(String taskId, boolean view) {
|
||||
|
||||
long userId = SecurityUtils.getUser().getId();
|
||||
|
||||
Optional<Task> task = Optional.ofNullable(taskService.createTaskQuery()
|
||||
.taskId(taskId)
|
||||
.taskTenantId(TenantContextHolder.getTenantId().toString())
|
||||
.taskAssignee(String.valueOf(userId))
|
||||
.singleResult());
|
||||
TaskResultDto taskResultDto = new TaskResultDto();
|
||||
|
||||
if (task.isPresent()) {
|
||||
String processDefinitionId = task.get().getProcessDefinitionId();
|
||||
String taskDefinitionKey = task.get().getTaskDefinitionKey();
|
||||
DelegationState delegationState = task.get().getDelegationState();
|
||||
String processInstanceId = task.get().getProcessInstanceId();
|
||||
Object delegateVariable = taskService.getVariableLocal(taskId, "delegate");
|
||||
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
Map<String, Object> variables = taskService.getVariables(taskId);
|
||||
Map<String, Object> variableAll = new HashMap<>(variables);
|
||||
|
||||
taskResultDto.setFlowId(flowId);
|
||||
taskResultDto.setNodeId(taskDefinitionKey);
|
||||
taskResultDto.setCurrentTask(true);
|
||||
taskResultDto.setDelegate(Convert.toBool(delegateVariable, false));
|
||||
taskResultDto.setVariableAll(variableAll);
|
||||
taskResultDto.setProcessInstanceId(processInstanceId);
|
||||
taskResultDto.setDelegationState(delegationState == null ? null : delegationState.toString());
|
||||
}
|
||||
else {
|
||||
Optional<HistoricTaskInstance> historicTaskInstance = Optional
|
||||
.ofNullable(historyService.createHistoricTaskInstanceQuery()
|
||||
.taskId(taskId)
|
||||
.taskAssignee(String.valueOf(userId))
|
||||
.singleResult());
|
||||
|
||||
String processDefinitionId = historicTaskInstance.get().getProcessDefinitionId();
|
||||
String taskDefinitionKey = historicTaskInstance.get().getTaskDefinitionKey();
|
||||
String processInstanceId = historicTaskInstance.get().getProcessInstanceId();
|
||||
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
|
||||
List<HistoricVariableInstance> variableInstanceList = historyService.createHistoricVariableInstanceQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.list();
|
||||
Map<String, Object> variableAll = new HashMap<>();
|
||||
|
||||
if (CollUtil.isNotEmpty(variableInstanceList)) {
|
||||
variableInstanceList.forEach(historicVariableInstance -> variableAll
|
||||
.put(historicVariableInstance.getVariableName(), historicVariableInstance.getValue()));
|
||||
}
|
||||
|
||||
taskResultDto.setFlowId(flowId);
|
||||
taskResultDto.setNodeId(taskDefinitionKey);
|
||||
taskResultDto.setCurrentTask(false);
|
||||
taskResultDto.setVariableAll(variableAll);
|
||||
taskResultDto.setProcessInstanceId(processInstanceId);
|
||||
|
||||
}
|
||||
|
||||
// 变量
|
||||
Map<String, Object> paramMap = taskResultDto.getVariableAll();
|
||||
// 是否是当前活动任务
|
||||
Boolean currentTask = taskResultDto.getCurrentTask();
|
||||
if (!currentTask) {
|
||||
ProcessNodeRecordAssignUser processNodeRecordAssignUser = processNodeRecordAssignUserService.lambdaQuery()
|
||||
.eq(ProcessNodeRecordAssignUser::getTaskId, taskId)
|
||||
.eq(ProcessNodeRecordAssignUser::getUserId, userId)
|
||||
.eq(ProcessNodeRecordAssignUser::getStatus, NodeStatusEnum.YJS.getCode())
|
||||
.last("limit 1")
|
||||
.orderByDesc(ProcessNodeRecordAssignUser::getEndTime)
|
||||
.one();
|
||||
|
||||
if (processNodeRecordAssignUser != null) {
|
||||
String data = processNodeRecordAssignUser.getData();
|
||||
if (StrUtil.isNotBlank(data)) {
|
||||
Map<String, Object> collect = objectMapper.readValue(data, new TypeReference<>() {
|
||||
});
|
||||
paramMap.putAll(collect);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 当前节点数据
|
||||
Node node = nodeDataService.getNodeData(taskResultDto.getFlowId(), taskResultDto.getNodeId()).getData();
|
||||
Map<String, String> formPerms = node.getFormPerms();
|
||||
|
||||
Process oaForms = processService.getByFlowId(taskResultDto.getFlowId());
|
||||
if (oaForms == null) {
|
||||
return R.failed("流程不存在");
|
||||
}
|
||||
|
||||
Dict set = Dict.create()
|
||||
.set("processInstanceId", taskResultDto.getProcessInstanceId())
|
||||
.set("node", taskResultDto.getTaskNode())
|
||||
.set("process", oaForms.getProcess())
|
||||
.set("delegateAgain", taskResultDto.getDelegate())
|
||||
.set("delegationTask", StrUtil.equals(taskResultDto.getDelegationState(), "PENDING"))
|
||||
.set("formItems", oaForms.getFormItems())
|
||||
.set("formData", paramMap)
|
||||
.set("formConfig", oaForms.getFormConfig())
|
||||
.set("formPerms", formPerms);
|
||||
|
||||
return R.ok(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成任务
|
||||
* <p>
|
||||
* 执行任务的完成操作,将任务标记为已完成并流转到下一个节点。 该方法会将当前用户ID设置到任务参数中,确保任务由正确的用户完成
|
||||
* </p>
|
||||
* @param taskParamDto 任务参数,包含任务ID、审批意见、流程变量等
|
||||
* @return R 操作结果
|
||||
*/
|
||||
@Override
|
||||
public R completeTask(TaskParamDto taskParamDto) {
|
||||
long userId = SecurityUtils.getUser().getId();
|
||||
taskParamDto.setUserId(String.valueOf(userId));
|
||||
|
||||
Map<String, Object> taskLocalParamMap = taskParamDto.getTaskLocalParamMap();
|
||||
if (CollUtil.isNotEmpty(taskLocalParamMap)) {
|
||||
taskService.setVariablesLocal(taskParamDto.getTaskId(), taskLocalParamMap);
|
||||
}
|
||||
|
||||
taskService.complete(taskParamDto.getTaskId(), taskParamDto.getParamMap());
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 前加签
|
||||
* <p>
|
||||
* 将任务委托给其他用户处理,委托后任务会分配给被委托人, 被委托人完成后任务会返回给原委托人
|
||||
* </p>
|
||||
* @param taskParamDto 任务参数,包含任务ID、被委托人ID列表等
|
||||
* @return R 操作结果
|
||||
*/
|
||||
@Transactional
|
||||
@Override
|
||||
public R delegateTask(TaskParamDto taskParamDto) {
|
||||
taskParamDto.setUserId(SecurityUtils.getUser().getId().toString());
|
||||
return flowableEngineService.delegateTask(taskParamDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加签完成任务
|
||||
* <p>
|
||||
* 被委托人完成委托任务后,通过此方法将任务返回给原委托人。 这是委托流程的第二步,用于结束委托状态
|
||||
* </p>
|
||||
* @param taskParamDto 任务参数,包含任务ID、审批意见等
|
||||
* @return R 操作结果
|
||||
*/
|
||||
@Override
|
||||
public R resolveTask(TaskParamDto taskParamDto) {
|
||||
return flowableEngineService.resolveTask(taskParamDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行人
|
||||
* <p>
|
||||
* 转办任务给其他用户,与委托不同,转办后任务直接由新执行人处理, 不会返回给原执行人
|
||||
* </p>
|
||||
* @param taskParamDto 任务参数,包含任务ID、新执行人ID等
|
||||
* @return R 操作结果
|
||||
*/
|
||||
@Override
|
||||
public R setAssignee(TaskParamDto taskParamDto) {
|
||||
Map<String, Object> taskLocalParamMap = taskParamDto.getTaskLocalParamMap();
|
||||
if (CollUtil.isNotEmpty(taskLocalParamMap)) {
|
||||
taskService.setVariablesLocal(taskParamDto.getTaskId(), taskLocalParamMap);
|
||||
}
|
||||
|
||||
// 设置任务的 owner 为当前任务的 assignee
|
||||
Task task = taskService.createTaskQuery()
|
||||
.taskId(taskParamDto.getTaskId())
|
||||
.includeTaskLocalVariables()
|
||||
.singleResult();
|
||||
taskService.setOwner(taskParamDto.getTaskId(), task.getAssignee());
|
||||
|
||||
// 设置任务的 assignee 为目标用户 (转办用户)
|
||||
for (Long targetUserId : taskParamDto.getTargetUserIdList()) {
|
||||
taskService.setAssignee(taskParamDto.getTaskId(), targetUserId.toString());
|
||||
}
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束流程
|
||||
* <p>
|
||||
* 强制终止流程实例的执行。该方法会: 1. 递归查找并终止所有子流程实例 2. 更新流程实例记录状态为已结束 3.
|
||||
* 设置结束原因为强制终止(finishReason=9)
|
||||
*
|
||||
* 通常用于流程异常或需要强制结束的场景
|
||||
* </p>
|
||||
* @param taskParamDto 任务参数,包含流程实例ID
|
||||
* @return R 操作结果
|
||||
*/
|
||||
@Override
|
||||
public R stopProcessInstance(TaskParamDto taskParamDto) {
|
||||
String reqProcessInstanceId = taskParamDto.getProcessInstanceId();
|
||||
List<String> allStopProcessInstanceIdList = getAllStopProcessInstanceIdList(reqProcessInstanceId);
|
||||
CollUtil.reverse(allStopProcessInstanceIdList);
|
||||
allStopProcessInstanceIdList.add(reqProcessInstanceId);
|
||||
allStopProcessInstanceIdList.forEach(processInstanceId -> {
|
||||
// 查询流程实例
|
||||
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
|
||||
.processInstanceId(processInstanceId)
|
||||
.processInstanceTenantId(TenantContextHolder.getTenantId().toString())
|
||||
.singleResult();
|
||||
if (Optional.ofNullable(processInstance).isPresent()) {
|
||||
// 查询执行实例
|
||||
List<String> executionIds = runtimeService.createExecutionQuery()
|
||||
.parentId(processInstanceId)
|
||||
.list()
|
||||
.stream()
|
||||
.map(Execution::getId)
|
||||
.toList();
|
||||
// 更改活动状态为结束
|
||||
runtimeService.createChangeActivityStateBuilder()
|
||||
.moveExecutionsToSingleActivityId(executionIds, "end")
|
||||
.changeState();
|
||||
}
|
||||
});
|
||||
|
||||
// 停止流程实例后,更新流程实例记录状态
|
||||
processInstanceRecordService.lambdaUpdate()
|
||||
.set(ProcessInstanceRecord::getStatus, NodeStatusEnum.YJS.getCode())
|
||||
.set(ProcessInstanceRecord::getFinishReason, "9")
|
||||
.set(ProcessInstanceRecord::getEndTime, new Date())
|
||||
.eq(ProcessInstanceRecord::getProcessInstanceId, reqProcessInstanceId)
|
||||
.update(new ProcessInstanceRecord());
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 退回
|
||||
* <p>
|
||||
* 将任务退回到指定的历史节点重新处理。 常用于审批不通过需要重新修改的场景
|
||||
* </p>
|
||||
* @param taskParamDto 任务参数,包含任务ID、目标节点ID、退回原因等
|
||||
* @return R 操作结果
|
||||
*/
|
||||
@Override
|
||||
public R back(TaskParamDto taskParamDto) {
|
||||
taskParamDto.setUserId(SecurityUtils.getUser().getId().toString());
|
||||
return flowableEngineService.back(taskParamDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有需要停止的子流程实例ID列表
|
||||
* <p>
|
||||
* 当一个流程包含子流程时,终止主流程需要同时终止所有子流程。 该方法递归查找指定流程的所有子流程实例ID
|
||||
* </p>
|
||||
* @param processInstanceId 父流程实例ID
|
||||
* @return 所有子流程实例ID列表
|
||||
*/
|
||||
private List<String> getAllStopProcessInstanceIdList(String processInstanceId) {
|
||||
List<ProcessInstanceRecord> list = processInstanceRecordService.lambdaQuery()
|
||||
.eq(ProcessInstanceRecord::getParentProcessInstanceId, processInstanceId)
|
||||
.list();
|
||||
|
||||
List<String> collect = new ArrayList<>(list.stream().map(ProcessInstanceRecord::getProcessInstanceId).toList());
|
||||
|
||||
for (ProcessInstanceRecord processInstanceRecord : list) {
|
||||
List<String> allStopProcessInstanceIdList = getAllStopProcessInstanceIdList(
|
||||
processInstanceRecord.getProcessInstanceId());
|
||||
|
||||
collect.addAll(allStopProcessInstanceIdList);
|
||||
|
||||
}
|
||||
return collect;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.pig4cloud.pigx.flow.mapper.BpmOaLeaveMapper">
|
||||
|
||||
<resultMap id="bpmOaLeaveMap" type="com.pig4cloud.pigx.flow.entity.BpmOaLeaveEntity">
|
||||
<id property="id" column="id"/>
|
||||
<result property="userId" column="user_id"/>
|
||||
<result property="type" column="type"/>
|
||||
<result property="reason" column="reason"/>
|
||||
<result property="startTime" column="start_time"/>
|
||||
<result property="endTime" column="end_time"/>
|
||||
<result property="day" column="day"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="processInstanceId" column="process_instance_id"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateBy" column="update_by"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
<result property="delFlag" column="del_flag"/>
|
||||
<result property="tenantId" column="tenant_id"/>
|
||||
</resultMap>
|
||||
</mapper>
|
||||
+82
-55
@@ -1,4 +1,4 @@
|
||||
package com.pig4cloud.pigx.flow.engine.listeners;
|
||||
package com.pig4cloud.pigx.flow.support.listeners;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -6,10 +6,10 @@ import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
|
||||
import com.pig4cloud.pigx.flow.engine.service.NodeTaskCompleteNotifyService;
|
||||
import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowTaskService;
|
||||
import com.pig4cloud.pigx.flow.task.dto.*;
|
||||
import com.pig4cloud.pigx.flow.task.utils.NodeUtil;
|
||||
import com.pig4cloud.pigx.flow.dto.*;
|
||||
import com.pig4cloud.pigx.flow.service.IProcessNodeDataService;
|
||||
import com.pig4cloud.pigx.flow.service.IRemoteService;
|
||||
import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.common.engine.api.delegate.event.FlowableEngineEventType;
|
||||
@@ -31,23 +31,32 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 流程事件监听器
|
||||
* 用于监听流程执行过程中的各种事件,并执行相应的操作
|
||||
* 包括节点开始/结束、任务分配/完成、流程开始/结束等事件
|
||||
* 流程全局事件监听器
|
||||
* <p>
|
||||
* Flowable全局事件监听器实现,用于监听和处理流程执行过程中的各种事件。 该监听器是流程引擎与业务系统交互的核心组件,负责将流程引擎的事件转换为业务事件。
|
||||
* <p>
|
||||
* 监听的主要事件类型: 1. 节点事件:节点开始、节点完成、多实例节点完成 2. 任务事件:任务分配、任务完成 3. 流程事件:流程开始、流程结束 4.
|
||||
* 变量事件:变量创建、变量更新、变量删除
|
||||
* <p>
|
||||
* 主要功能: - 记录流程执行轨迹 - 触发业务系统的相关操作 - 发送任务通知 - 同步流程数据到业务系统
|
||||
*
|
||||
* @author pigx
|
||||
*/
|
||||
@Slf4j
|
||||
public class FlowProcessEventListener implements FlowableEventListener {
|
||||
|
||||
/**
|
||||
* 当事件被触发时调用的方法
|
||||
* 根据不同事件类型执行不同的业务逻辑
|
||||
*
|
||||
* @param event Flowable引擎触发的事件对象
|
||||
* 处理Flowable引擎触发的事件
|
||||
* <p>
|
||||
* 该方法是事件监听的核心,根据事件类型分发到不同的处理逻辑。 每种事件类型都会触发相应的业务操作,如记录日志、发送通知等。
|
||||
* @param event Flowable引擎触发的事件对象,包含事件类型和相关数据
|
||||
* @throws Exception 当事件处理失败时可能抛出异常
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void onEvent(FlowableEvent event) {
|
||||
RemoteFlowTaskService remoteFlowTaskService = SpringUtil.getBean(RemoteFlowTaskService.class);
|
||||
IRemoteService remoteService = SpringUtil.getBean(IRemoteService.class);
|
||||
IProcessNodeDataService processNodeDataService = SpringUtil.getBean(IProcessNodeDataService.class);
|
||||
|
||||
log.info("流程事件监听器 类型={} class={}", event.getType(), event.getClass().getCanonicalName());
|
||||
|
||||
@@ -65,7 +74,7 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
|
||||
// 查询节点原始数据
|
||||
Node node = remoteFlowTaskService.queryNodeOriData(flowId, activityId).getData();
|
||||
Node node = processNodeDataService.getNodeData(flowId, activityId).getData();
|
||||
|
||||
// 构建节点开始记录参数并发送事件
|
||||
ProcessNodeRecordParamDto processNodeRecordParamDto = new ProcessNodeRecordParamDto();
|
||||
@@ -77,13 +86,17 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
}
|
||||
processNodeRecordParamDto.setNodeName(activityName);
|
||||
processNodeRecordParamDto.setExecutionId(flowableActivityEvent.getExecutionId());
|
||||
remoteFlowTaskService.startNodeEvent(processNodeRecordParamDto);
|
||||
remoteService.startNodeEvent(processNodeRecordParamDto);
|
||||
}
|
||||
|
||||
// 多实例任务完成事件处理
|
||||
// 触发时机:当多实例任务节点完成时,包括条件完成和全部完成两种情况
|
||||
if (event.getType().toString().equals(FlowableEngineEventType.MULTI_INSTANCE_ACTIVITY_COMPLETED_WITH_CONDITION.toString())
|
||||
|| event.getType().toString().equals(FlowableEngineEventType.MULTI_INSTANCE_ACTIVITY_COMPLETED.toString())) {
|
||||
if (event.getType()
|
||||
.toString()
|
||||
.equals(FlowableEngineEventType.MULTI_INSTANCE_ACTIVITY_COMPLETED_WITH_CONDITION.toString())
|
||||
|| event.getType()
|
||||
.toString()
|
||||
.equals(FlowableEngineEventType.MULTI_INSTANCE_ACTIVITY_COMPLETED.toString())) {
|
||||
|
||||
FlowableMultiInstanceActivityCompletedEventImpl flowableActivityEvent = (FlowableMultiInstanceActivityCompletedEventImpl) event;
|
||||
String activityId = flowableActivityEvent.getActivityId();
|
||||
@@ -102,7 +115,8 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
processNodeRecordParamDto.setNodeId(activityId);
|
||||
processNodeRecordParamDto.setNodeName(activityName);
|
||||
|
||||
remoteFlowTaskService.endNodeEvent(processNodeRecordParamDto);
|
||||
remoteService.endNodeEvent(processNodeRecordParamDto);
|
||||
|
||||
}
|
||||
|
||||
// 节点完成事件处理
|
||||
@@ -125,7 +139,8 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
processNodeRecordParamDto.setNodeId(activityId);
|
||||
processNodeRecordParamDto.setNodeName(activityName);
|
||||
|
||||
remoteFlowTaskService.endNodeEvent(processNodeRecordParamDto);
|
||||
remoteService.endNodeEvent(processNodeRecordParamDto);
|
||||
|
||||
}
|
||||
|
||||
// 变量更新事件处理
|
||||
@@ -154,7 +169,9 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
|
||||
// 流程终止事件处理
|
||||
// 触发时机:当流程以终止结束事件完成时
|
||||
if (event.getType().toString().equals(FlowableEngineEventType.PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT.toString())) {
|
||||
if (event.getType()
|
||||
.toString()
|
||||
.equals(FlowableEngineEventType.PROCESS_COMPLETED_WITH_TERMINATE_END_EVENT.toString())) {
|
||||
FlowableProcessTerminatedEventImpl e = (FlowableProcessTerminatedEventImpl) event;
|
||||
DelegateExecution execution = e.getExecution();
|
||||
String processInstanceId = e.getProcessInstanceId();
|
||||
@@ -162,11 +179,19 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
|
||||
log.info("流程终止完成事件 - 流程实例ID: {}", processInstanceId);
|
||||
|
||||
String processDefinitionId = execution.getProcessDefinitionId();
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId); // 流程id
|
||||
|
||||
// 构建流程结束参数并发送事件
|
||||
ProcessInstanceParamDto processInstanceParamDto = new ProcessInstanceParamDto();
|
||||
processInstanceParamDto.setProcessInstanceId(processInstanceId);
|
||||
processInstanceParamDto.setParamMap(execution.getVariables());
|
||||
remoteFlowTaskService.endProcessEvent(processInstanceParamDto);
|
||||
processInstanceParamDto.setFlowId(flowId);
|
||||
processInstanceParamDto.setParamMap(execution.getVariables());
|
||||
remoteService.endProcess(processInstanceParamDto);
|
||||
|
||||
// 发送任务完成通知
|
||||
SpringUtil.getBean(NodeTaskCompleteNotify.class)
|
||||
.sendFlowNotify(SecurityUtils.getToken(), processInstanceParamDto);
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = SpringUtil.getBean(ObjectMapper.class);
|
||||
@@ -180,11 +205,11 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
TaskEntityImpl task = (TaskEntityImpl) flowableEntityEvent.getEntity();
|
||||
|
||||
// 获取任务相关信息
|
||||
String assignee = task.getAssignee(); // 执行人id
|
||||
String taskDefinitionKey = task.getTaskDefinitionKey(); // 节点id
|
||||
String processInstanceId = task.getProcessInstanceId(); // 实例id
|
||||
String assignee = task.getAssignee(); // 执行人id
|
||||
String taskDefinitionKey = task.getTaskDefinitionKey(); // 节点id
|
||||
String processInstanceId = task.getProcessInstanceId(); // 实例id
|
||||
String processDefinitionId = task.getProcessDefinitionId();
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId); // 流程id
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId); // 流程id
|
||||
|
||||
log.info("任务完成事件 - 任务ID: {}, 节点ID: {}, 执行人: {}", task.getId(), taskDefinitionKey, assignee);
|
||||
|
||||
@@ -193,9 +218,9 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
processNodeRecordAssignUserParamDto.setFlowId(flowId);
|
||||
processNodeRecordAssignUserParamDto.setProcessInstanceId(processInstanceId);
|
||||
processNodeRecordAssignUserParamDto
|
||||
.setData(objectMapper.writeValueAsString(taskService.getVariables(task.getId())));
|
||||
.setData(objectMapper.writeValueAsString(taskService.getVariables(task.getId())));
|
||||
processNodeRecordAssignUserParamDto
|
||||
.setLocalData(objectMapper.writeValueAsString(taskService.getVariablesLocal(task.getId())));
|
||||
.setLocalData(objectMapper.writeValueAsString(taskService.getVariablesLocal(task.getId())));
|
||||
processNodeRecordAssignUserParamDto.setNodeId(taskDefinitionKey);
|
||||
processNodeRecordAssignUserParamDto.setUserId(Long.parseLong(task.getAssignee()));
|
||||
processNodeRecordAssignUserParamDto.setTaskId(task.getId());
|
||||
@@ -204,10 +229,11 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
processNodeRecordAssignUserParamDto.setApproveDesc(Convert.toStr(task.getVariableLocal("approveDesc")));
|
||||
processNodeRecordAssignUserParamDto.setExecutionId(task.getExecutionId());
|
||||
|
||||
remoteFlowTaskService.taskEndEvent(processNodeRecordAssignUserParamDto);
|
||||
remoteService.taskEndEvent(processNodeRecordAssignUserParamDto);
|
||||
|
||||
// 发送任务完成通知
|
||||
SpringUtil.getBean(NodeTaskCompleteNotifyService.class).sendNotify(SecurityUtils.getToken(), processNodeRecordAssignUserParamDto);
|
||||
SpringUtil.getBean(NodeTaskCompleteNotify.class)
|
||||
.sendNotify(SecurityUtils.getToken(), processNodeRecordAssignUserParamDto);
|
||||
}
|
||||
|
||||
// 任务分配事件处理
|
||||
@@ -217,16 +243,16 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
TaskEntityImpl task = (TaskEntityImpl) flowableEntityEvent.getEntity();
|
||||
|
||||
// 获取任务分配相关信息
|
||||
String assignee = task.getAssignee(); // 执行人id
|
||||
String owner = task.getOwner(); // 任务拥有者
|
||||
String delegationStateString = task.getDelegationStateString(); // 委派状态
|
||||
String taskDefinitionKey = task.getTaskDefinitionKey(); // 节点id
|
||||
String processInstanceId = task.getProcessInstanceId(); // 实例id
|
||||
String assignee = task.getAssignee(); // 执行人id
|
||||
String owner = task.getOwner(); // 任务拥有者
|
||||
String delegationStateString = task.getDelegationStateString(); // 委派状态
|
||||
String taskDefinitionKey = task.getTaskDefinitionKey(); // 节点id
|
||||
String processInstanceId = task.getProcessInstanceId(); // 实例id
|
||||
String processDefinitionId = task.getProcessDefinitionId();
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId); // 流程id
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId); // 流程id
|
||||
|
||||
log.info("任务分配事件 - 任务ID: {}, 节点ID: {}, 分配给: {}, 委派状态: {}",
|
||||
task.getId(), taskDefinitionKey, assignee, delegationStateString);
|
||||
log.info("任务分配事件 - 任务ID: {}, 节点ID: {}, 分配给: {}, 委派状态: {}", task.getId(), taskDefinitionKey, assignee,
|
||||
delegationStateString);
|
||||
|
||||
// 构建任务分配记录参数并发送事件
|
||||
ProcessNodeRecordAssignUserParamDto processNodeRecordAssignUserParamDto = new ProcessNodeRecordAssignUserParamDto();
|
||||
@@ -239,14 +265,14 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
|
||||
// 根据委派状态设置任务类型
|
||||
processNodeRecordAssignUserParamDto
|
||||
.setTaskType(StrUtil.equals(DelegationState.PENDING.toString(), delegationStateString) ? "DELEGATION"
|
||||
: (StrUtil.equals(DelegationState.RESOLVED.toString(), delegationStateString) ? "RESOLVED"
|
||||
: ""));
|
||||
.setTaskType(StrUtil.equals(DelegationState.PENDING.toString(), delegationStateString) ? "DELEGATION"
|
||||
: (StrUtil.equals(DelegationState.RESOLVED.toString(), delegationStateString) ? "RESOLVED"
|
||||
: ""));
|
||||
|
||||
processNodeRecordAssignUserParamDto.setApproveDesc(Convert.toStr(task.getVariableLocal("approveDesc")));
|
||||
processNodeRecordAssignUserParamDto.setExecutionId(task.getExecutionId());
|
||||
|
||||
remoteFlowTaskService.startAssignUser(processNodeRecordAssignUserParamDto);
|
||||
remoteService.startAssignUser(processNodeRecordAssignUserParamDto);
|
||||
}
|
||||
|
||||
// 流程开始事件处理
|
||||
@@ -262,8 +288,8 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
|
||||
{
|
||||
// 获取流程相关信息
|
||||
String nestedProcessInstanceId = flowableProcessStartedEvent.getNestedProcessInstanceId(); // 上级实例id
|
||||
String flowId = entity.getProcessDefinitionKey(); // 流程定义key
|
||||
String nestedProcessInstanceId = flowableProcessStartedEvent.getNestedProcessInstanceId(); // 上级实例id
|
||||
String flowId = entity.getProcessDefinitionKey(); // 流程定义key
|
||||
|
||||
// 获取启动人信息
|
||||
Object variable = execution.getVariable("root");
|
||||
@@ -282,16 +308,16 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
processInstanceRecordParamDto.setFlowId(flowId);
|
||||
processInstanceRecordParamDto.setProcessInstanceId(processInstanceId);
|
||||
processInstanceRecordParamDto.setFormData(objectMapper.writeValueAsString(variables));
|
||||
remoteFlowTaskService.createProcessEvent(processInstanceRecordParamDto);
|
||||
remoteService.createProcessEvent(processInstanceRecordParamDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定监听器抛出异常是否终止当前操作
|
||||
* 返回false表示即使监听器出现异常,流程引擎仍会继续执行
|
||||
*
|
||||
* @return 是否在监听器异常时终止操作
|
||||
* 确定监听器抛出异常时是否终止当前操作
|
||||
* <p>
|
||||
* 返回false表示即使监听器处理出现异常,流程引擎仍会继续执行。 这确保了监听器的异常不会影响流程的正常执行。
|
||||
* @return false,表示监听器异常不终止流程执行
|
||||
*/
|
||||
@Override
|
||||
public boolean isFailOnException() {
|
||||
@@ -300,9 +326,9 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
|
||||
/**
|
||||
* 确定监听器是否在事务生命周期事件发生时立即触发
|
||||
* 返回false表示不在事务生命周期事件上立即触发
|
||||
*
|
||||
* @return 是否在事务生命周期事件上触发
|
||||
* <p>
|
||||
* 返回false表示监听器不需要在事务生命周期事件上立即触发。 这意味着监听器将在正常的事件触发时机执行。
|
||||
* @return false,表示不在事务生命周期事件上触发
|
||||
*/
|
||||
@Override
|
||||
public boolean isFireOnTransactionLifecycleEvent() {
|
||||
@@ -311,12 +337,13 @@ public class FlowProcessEventListener implements FlowableEventListener {
|
||||
|
||||
/**
|
||||
* 获取在当前事务的生命周期中的触发点
|
||||
* 返回null表示不在特定事务生命周期触发点触发
|
||||
*
|
||||
* @return 事务生命周期中的触发点,null表示没有指定
|
||||
* <p>
|
||||
* 返回null表示监听器不需要在特定的事务生命周期触发点触发。 可选值包括:committed、committing、rolled-back等。
|
||||
* @return null,表示没有指定特定的事务触发点
|
||||
*/
|
||||
@Override
|
||||
public String getOnTransaction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package com.pig4cloud.pigx.flow.support.listeners;
|
||||
|
||||
import cn.hutool.core.lang.Validator;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pig4cloud.pigx.common.core.util.RetOps;
|
||||
import com.pig4cloud.pigx.common.security.util.NonWebTokenContextHolder;
|
||||
import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto;
|
||||
import com.pig4cloud.pigx.flow.dto.ProcessNodeRecordAssignUserParamDto;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessInstanceRecord;
|
||||
import com.pig4cloud.pigx.flow.mapper.ProcessInstanceRecordMapper;
|
||||
import com.pig4cloud.pigx.flow.service.IProcessInstanceStatusEventService;
|
||||
import com.pig4cloud.pigx.flow.service.IProcessNodeDataService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 节点任务完成通知服务
|
||||
* <p>
|
||||
* 该服务负责在流程节点任务完成时发送异步通知。当节点配置了外部事件回调URL时, 系统会自动调用该URL通知任务完成事件。这种机制允许流程与外部系统进行集成,
|
||||
* 实现业务流程的自动化。通知采用异步方式发送,不会阻塞主流程的执行。
|
||||
* </p>
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2024/7/9
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class NodeTaskCompleteNotify {
|
||||
|
||||
private final IProcessNodeDataService processNodeDataService;
|
||||
|
||||
private final ProcessInstanceRecordMapper processInstanceRecordMapper;
|
||||
|
||||
private final Optional<List<IProcessInstanceStatusEventService>> processInstanceStatusEventServicesOptional;
|
||||
|
||||
/**
|
||||
* 异步发送任务完成通知
|
||||
* <p>
|
||||
* 当流程节点任务完成时,如果节点配置了事件通知URL,则通过HTTP POST请求 将任务完成信息发送到指定的URL。通知内容包括任务ID、处理人、处理结果等
|
||||
* 完整信息。该方法使用异步执行,确保不影响主流程的性能。通知失败时会 记录日志但不会影响流程执行。
|
||||
* </p>
|
||||
* @param token 安全令牌,用于在异步线程中传递认证信息
|
||||
* @param taskCompleteParamDto 任务完成参数,包含: - flowId: 流程定义ID - nodeId: 节点ID - taskId:
|
||||
* 任务ID - userId: 处理人ID - result: 处理结果 - comment: 处理意见 - completeTime: 完成时间 -
|
||||
* variables: 流程变量
|
||||
*/
|
||||
@Async
|
||||
public void sendNotify(String token, ProcessNodeRecordAssignUserParamDto taskCompleteParamDto) {
|
||||
// 在异步线程中设置认证令牌
|
||||
NonWebTokenContextHolder.setToken(token);
|
||||
|
||||
// 获取节点配置并发送通知
|
||||
RetOps
|
||||
.of(processNodeDataService.getNodeData(taskCompleteParamDto.getFlowId(), taskCompleteParamDto.getNodeId()))
|
||||
.getData()
|
||||
.ifPresent(node -> {
|
||||
// 验证事件配置是否为有效的URL
|
||||
if (Validator.isUrl(node.getEventConfig())) {
|
||||
// 发送HTTP POST请求到配置的URL
|
||||
HttpResponse response = HttpRequest.post(node.getEventConfig())
|
||||
.body(JSONUtil.toJsonStr(taskCompleteParamDto))
|
||||
.timeout(10000) // 设置10秒超时
|
||||
.execute();
|
||||
log.debug("节点任务完成通知处理完成:{},数据:{}", response.getStatus(), taskCompleteParamDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步发送流程通知
|
||||
* @param token 认证令牌
|
||||
* @param instanceRecordParamDto 流程节点记录参数DTO
|
||||
* @throws IllegalArgumentException 参数为空时抛出异常
|
||||
*/
|
||||
public void sendFlowNotify(String token, ProcessInstanceParamDto instanceRecordParamDto) {
|
||||
// 在异步线程中设置认证令牌
|
||||
NonWebTokenContextHolder.setToken(token);
|
||||
if (processInstanceStatusEventServicesOptional.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (IProcessInstanceStatusEventService iProcessInstanceStatusEventService : processInstanceStatusEventServicesOptional
|
||||
.get()) {
|
||||
|
||||
if (iProcessInstanceStatusEventService.getFlowId().equals(instanceRecordParamDto.getFlowId())) {
|
||||
// 处理状态事件
|
||||
ProcessInstanceRecord processInstanceRecord = processInstanceRecordMapper.selectOne(Wrappers
|
||||
.<ProcessInstanceRecord>lambdaQuery()
|
||||
.eq(ProcessInstanceRecord::getProcessInstanceId, instanceRecordParamDto.getProcessInstanceId()));
|
||||
|
||||
instanceRecordParamDto.setStatus(processInstanceRecord.getStatus());
|
||||
instanceRecordParamDto.setFinishReason(processInstanceRecord.getFinishReason());
|
||||
iProcessInstanceStatusEventService.handleStatusEvent(instanceRecordParamDto);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package com.pig4cloud.pigx.flow.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.excel.annotation.RequestExcel;
|
||||
import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel;
|
||||
import com.pig4cloud.pigx.common.log.annotation.SysLog;
|
||||
import com.pig4cloud.pigx.common.security.annotation.HasPermission;
|
||||
import com.pig4cloud.pigx.flow.entity.BpmOaLeaveEntity;
|
||||
import com.pig4cloud.pigx.flow.service.BpmOaLeaveService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 请假表单
|
||||
*
|
||||
* @author pigx
|
||||
* @date 2025-08-08 09:32:41
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/bpmOaLeave")
|
||||
@Tag(description = "bpmOaLeave", name = "请假表单管理")
|
||||
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
|
||||
public class BpmOaLeaveController {
|
||||
|
||||
private final BpmOaLeaveService bpmOaLeaveService;
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
* @param page 分页对象
|
||||
* @param bpmOaLeave 请假表单
|
||||
* @return
|
||||
*/
|
||||
@Operation(summary = "分页查询", description = "分页查询")
|
||||
@GetMapping("/page")
|
||||
@HasPermission("flow_bpmOaLeave_view")
|
||||
public R getBpmOaLeavePage(@ParameterObject Page page, @ParameterObject BpmOaLeaveEntity bpmOaLeave) {
|
||||
return R.ok(bpmOaLeaveService.page(page, Wrappers.<BpmOaLeaveEntity>lambdaQuery()
|
||||
.like(StrUtil.isNotBlank(bpmOaLeave.getUsername()), BpmOaLeaveEntity::getUsername, bpmOaLeave.getUsername())
|
||||
.eq(Objects.nonNull(bpmOaLeave.getLeaveStatus()), BpmOaLeaveEntity::getLeaveStatus,
|
||||
bpmOaLeave.getLeaveStatus())
|
||||
.eq(Objects.nonNull(bpmOaLeave.getLeaveType()), BpmOaLeaveEntity::getLeaveType,
|
||||
bpmOaLeave.getLeaveType())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过条件查询请假表单
|
||||
* @param bpmOaLeave 查询条件
|
||||
* @return R 对象列表
|
||||
*/
|
||||
@Operation(summary = "通过条件查询", description = "通过条件查询对象")
|
||||
@GetMapping("/details")
|
||||
@HasPermission("flow_bpmOaLeave_view")
|
||||
public R getDetails(@ParameterObject BpmOaLeaveEntity bpmOaLeave) {
|
||||
return R.ok(bpmOaLeaveService.list(Wrappers.query(bpmOaLeave)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增请假表单
|
||||
* @param bpmOaLeave 请假表单
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "新增请假表单", description = "新增请假表单")
|
||||
@SysLog("新增请假表单")
|
||||
@PostMapping
|
||||
@HasPermission("flow_bpmOaLeave_add")
|
||||
public R save(@RequestBody BpmOaLeaveEntity bpmOaLeave) {
|
||||
return R.ok(bpmOaLeaveService.saveAndStartProcess(bpmOaLeave));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改请假表单
|
||||
* @param bpmOaLeave 请假表单
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "修改请假表单", description = "修改请假表单")
|
||||
@SysLog("修改请假表单")
|
||||
@PutMapping
|
||||
@HasPermission("flow_bpmOaLeave_edit")
|
||||
public R updateById(@RequestBody BpmOaLeaveEntity bpmOaLeave) {
|
||||
return R.ok(bpmOaLeaveService.updateById(bpmOaLeave));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除请假表单
|
||||
* @param ids id列表
|
||||
* @return R
|
||||
*/
|
||||
@Operation(summary = "通过id删除请假表单", description = "通过id删除请假表单")
|
||||
@SysLog("通过id删除请假表单")
|
||||
@DeleteMapping
|
||||
@HasPermission("flow_bpmOaLeave_del")
|
||||
public R removeById(@RequestBody Long[] ids) {
|
||||
return R.ok(bpmOaLeaveService.removeBatchByIds(CollUtil.toList(ids)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel 表格
|
||||
* @param bpmOaLeave 查询条件
|
||||
* @param ids 导出指定ID
|
||||
* @return excel 文件流
|
||||
*/
|
||||
@ResponseExcel
|
||||
@GetMapping("/export")
|
||||
@HasPermission("flow_bpmOaLeave_export")
|
||||
public List<BpmOaLeaveEntity> exportExcel(BpmOaLeaveEntity bpmOaLeave, Long[] ids) {
|
||||
return bpmOaLeaveService
|
||||
.list(Wrappers.lambdaQuery(bpmOaLeave).in(ArrayUtil.isNotEmpty(ids), BpmOaLeaveEntity::getId, ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入excel 表
|
||||
* @param bpmOaLeaveList 对象实体列表
|
||||
* @param bindingResult 错误信息列表
|
||||
* @return ok fail
|
||||
*/
|
||||
@PostMapping("/import")
|
||||
@HasPermission("flow_bpmOaLeave_export")
|
||||
public R importExcel(@RequestExcel List<BpmOaLeaveEntity> bpmOaLeaveList, BindingResult bindingResult) {
|
||||
return R.ok(bpmOaLeaveService.saveBatch(bpmOaLeaveList));
|
||||
}
|
||||
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.pig4cloud.pigx.flow.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pig4cloud.pigx.flow.entity.BpmOaLeaveEntity;
|
||||
|
||||
public interface BpmOaLeaveService extends IService<BpmOaLeaveEntity>, IProcessInstanceStatusEventService {
|
||||
|
||||
/**
|
||||
* 保存并启动请假流程
|
||||
* @param bpmOaLeave 请假实体对象
|
||||
* @return 保存后的请假实体对象
|
||||
*/
|
||||
BpmOaLeaveEntity saveAndStartProcess(BpmOaLeaveEntity bpmOaLeave);
|
||||
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.pig4cloud.pigx.flow.service;
|
||||
|
||||
import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto;
|
||||
|
||||
/**
|
||||
* 流程实例状态事件服务接口
|
||||
*
|
||||
* @author lengleng
|
||||
* @date 2025/08/08
|
||||
*/
|
||||
public interface IProcessInstanceStatusEventService {
|
||||
|
||||
/**
|
||||
* 获取监听的流程定义Key
|
||||
* @return 监听的流程定义Key
|
||||
*/
|
||||
String getFlowId();
|
||||
|
||||
/**
|
||||
* 处理状态事件
|
||||
* @param paramDto 流程节点记录分配用户参数DTO
|
||||
*/
|
||||
void handleStatusEvent(ProcessInstanceParamDto paramDto);
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package com.pig4cloud.pigx.flow.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
|
||||
import com.pig4cloud.pigx.flow.api.feign.RemoteFlowApiFlowService;
|
||||
import com.pig4cloud.pigx.flow.constant.NodeUserTypeEnum;
|
||||
import com.pig4cloud.pigx.flow.dto.ProcessInstanceParamDto;
|
||||
import com.pig4cloud.pigx.flow.entity.BpmOaLeaveEntity;
|
||||
import com.pig4cloud.pigx.flow.mapper.BpmOaLeaveMapper;
|
||||
import com.pig4cloud.pigx.flow.service.BpmOaLeaveService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 请假表单
|
||||
*
|
||||
* @author pigx
|
||||
* @date 2025-08-08 09:32:41
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BpmOaLeaveServiceImpl extends ServiceImpl<BpmOaLeaveMapper, BpmOaLeaveEntity>
|
||||
implements BpmOaLeaveService {
|
||||
|
||||
private final RemoteFlowApiFlowService flowApiFlowService;
|
||||
|
||||
/**
|
||||
* 获取监听的流程定义Key
|
||||
* @return 监听的流程定义Key
|
||||
*/
|
||||
@Override
|
||||
public String getFlowId() {
|
||||
return "QWE";
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理状态事件
|
||||
* @param paramDto 流程节点记录分配用户参数DTO
|
||||
*/
|
||||
@Override
|
||||
public void handleStatusEvent(ProcessInstanceParamDto paramDto) {
|
||||
this.update(Wrappers.<BpmOaLeaveEntity>lambdaUpdate()
|
||||
.set(BpmOaLeaveEntity::getLeaveStatus, paramDto.getStatus())
|
||||
.eq(BpmOaLeaveEntity::getProcessInstanceId, paramDto.getProcessInstanceId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存并启动请假流程
|
||||
* @param bpmOaLeave 请假实体对象
|
||||
* @return 保存后的请假实体对象
|
||||
*/
|
||||
@Override
|
||||
public BpmOaLeaveEntity saveAndStartProcess(BpmOaLeaveEntity bpmOaLeave) {
|
||||
bpmOaLeave.setUsername(SecurityUtils.getUser().getUsername());
|
||||
baseMapper.insert(bpmOaLeave);
|
||||
// 获取流程配置详情
|
||||
ProcessInstanceParamDto processInstanceParamDto = new ProcessInstanceParamDto();
|
||||
processInstanceParamDto.setFlowId(this.getFlowId());
|
||||
processInstanceParamDto.setStartUserId(String.valueOf(SecurityUtils.getUser().getId()));
|
||||
|
||||
Map<String, Object> paramMap = new HashMap<>();
|
||||
// 设置发起人
|
||||
Dict rootUser = Dict.create()
|
||||
.set("id", processInstanceParamDto.getStartUserId())
|
||||
.set("name", SecurityUtils.getUser().getUsername())
|
||||
.set("type", NodeUserTypeEnum.USER.getKey());
|
||||
paramMap.put("root", CollUtil.newArrayList(rootUser));
|
||||
processInstanceParamDto.setParamMap(paramMap);
|
||||
|
||||
R<String> stringR = flowApiFlowService.startProcessInstance(processInstanceParamDto);
|
||||
bpmOaLeave.setProcessInstanceId(stringR.getData());
|
||||
baseMapper.updateById(bpmOaLeave);
|
||||
return bpmOaLeave;
|
||||
}
|
||||
|
||||
}
|
||||
+246
-73
@@ -1,4 +1,4 @@
|
||||
package com.pig4cloud.pigx.flow.task.service.impl;
|
||||
package com.pig4cloud.pigx.flow.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
@@ -13,39 +13,66 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pig4cloud.pigx.admin.api.entity.SysUser;
|
||||
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
|
||||
import com.pig4cloud.pigx.common.core.util.R;
|
||||
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
|
||||
import com.pig4cloud.pigx.common.security.service.PigxUser;
|
||||
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
|
||||
import com.pig4cloud.pigx.flow.task.api.feign.RemoteFlowEngineService;
|
||||
import com.pig4cloud.pigx.flow.task.constant.NodeStatusEnum;
|
||||
import com.pig4cloud.pigx.flow.task.constant.NodeUserTypeEnum;
|
||||
import com.pig4cloud.pigx.flow.task.dto.*;
|
||||
import com.pig4cloud.pigx.flow.task.entity.*;
|
||||
import com.pig4cloud.pigx.flow.task.entity.Process;
|
||||
import com.pig4cloud.pigx.flow.task.service.*;
|
||||
import com.pig4cloud.pigx.flow.task.utils.NodeFormatUtil;
|
||||
import com.pig4cloud.pigx.flow.task.vo.NodeFormatParamVo;
|
||||
import com.pig4cloud.pigx.flow.task.vo.NodeVo;
|
||||
import com.pig4cloud.pigx.flow.task.vo.ProcessCopyVo;
|
||||
import com.pig4cloud.pigx.flow.constant.NodeStatusEnum;
|
||||
import com.pig4cloud.pigx.flow.constant.NodeUserTypeEnum;
|
||||
import com.pig4cloud.pigx.flow.dto.*;
|
||||
import com.pig4cloud.pigx.flow.entity.Process;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessCopy;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessInstanceRecord;
|
||||
import com.pig4cloud.pigx.flow.entity.ProcessNodeRecord;
|
||||
import com.pig4cloud.pigx.flow.service.*;
|
||||
import com.pig4cloud.pigx.flow.support.utils.NodeFormatUtil;
|
||||
import com.pig4cloud.pigx.flow.support.utils.NodeUtil;
|
||||
import com.pig4cloud.pigx.flow.vo.NodeFormatParamVo;
|
||||
import com.pig4cloud.pigx.flow.vo.NodeVo;
|
||||
import com.pig4cloud.pigx.flow.vo.ProcessCopyVo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.common.engine.impl.identity.Authentication;
|
||||
import org.flowable.engine.HistoryService;
|
||||
import org.flowable.engine.RuntimeService;
|
||||
import org.flowable.engine.TaskService;
|
||||
import org.flowable.engine.history.HistoricActivityInstance;
|
||||
import org.flowable.engine.history.HistoricActivityInstanceQuery;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.flowable.task.api.TaskQuery;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 实例进程服务
|
||||
* 流程实例服务实现类
|
||||
* <p>
|
||||
* 该类是流程实例管理的核心服务,负责处理流程实例的完整生命周期,主要功能包括: 1. 流程实例的启动、结束等生命周期管理 2. 待办任务、已办任务的查询 3.
|
||||
* 我发起的流程、抄送给我的流程查询 4. 流程节点的格式化显示 5. 流程详情查询
|
||||
*
|
||||
* 该服务整合了Flowable引擎和业务数据,为前端提供统一的流程操作接口
|
||||
* </p>
|
||||
*
|
||||
* @author pigx code generator
|
||||
* @date 2023-10-01
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
|
||||
private final RemoteFlowEngineService flowEngineService;
|
||||
|
||||
private final RemoteUserService userService;
|
||||
|
||||
private final TaskService taskService;
|
||||
|
||||
private final RuntimeService runtimeService;
|
||||
|
||||
private final HistoryService historyService;
|
||||
|
||||
private final IProcessInstanceRecordService processInstanceRecordService;
|
||||
|
||||
private final IProcessCopyService processCopyService;
|
||||
@@ -54,47 +81,117 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
|
||||
private final IProcessNodeRecordService processNodeRecordService;
|
||||
|
||||
private final IProcessNodeRecordAssignUserService processNodeRecordAssignUserService;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 启动流程
|
||||
* @param processInstanceParamDto
|
||||
* @return
|
||||
* <p>
|
||||
* 根据流程定义启动一个新的流程实例。该方法会: 1. 获取当前登录用户作为流程发起人 2. 设置流程发起人信息到流程变量中 3. 调用Flowable引擎启动流程
|
||||
* </p>
|
||||
* @param processInstanceParamDto 流程启动参数,包含: - flowId: 流程定义ID - paramMap: 流程变量
|
||||
* @return R 启动结果,包含流程实例ID等信息
|
||||
*/
|
||||
@Override
|
||||
public R startProcessInstance(ProcessInstanceParamDto processInstanceParamDto) {
|
||||
|
||||
// 获取当前登录用户
|
||||
PigxUser user = SecurityUtils.getUser();
|
||||
|
||||
// 设置流程发起人ID
|
||||
processInstanceParamDto.setStartUserId(String.valueOf(user.getId()));
|
||||
Map<String, Object> paramMap = processInstanceParamDto.getParamMap();
|
||||
|
||||
// 将发起人信息作为root变量存入流程,用于流程节点的权限判断
|
||||
Dict rootUser = Dict.create()
|
||||
.set("id", user.getId())
|
||||
.set("name", user.getUsername())
|
||||
.set("type", NodeUserTypeEnum.USER.getKey());
|
||||
paramMap.put("root", CollUtil.newArrayList(rootUser));
|
||||
return flowEngineService.startProcess(processInstanceParamDto);
|
||||
|
||||
// 调用引擎启动流程
|
||||
Authentication.setAuthenticatedUserId(processInstanceParamDto.getStartUserId());
|
||||
ProcessInstance processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(
|
||||
processInstanceParamDto.getFlowId(), processInstanceParamDto.getParamMap(),
|
||||
TenantContextHolder.getTenantId().toString());
|
||||
|
||||
String processInstanceId = processInstance.getProcessInstanceId();
|
||||
return R.ok(processInstanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前登录用户的待办任务
|
||||
* @param taskQueryParamDto 查询参数
|
||||
* @return R
|
||||
* <p>
|
||||
* 查询分配给当前用户的待办任务列表,并补充以下信息: 1. 流程实例的详细信息(流程名称、分组等) 2. 流程发起人信息(姓名、头像等) 3. 流程启动时间
|
||||
* </p>
|
||||
* @param taskQueryParamDto 查询参数,支持分页和条件过滤
|
||||
* @return R 包含分页后的待办任务列表
|
||||
*/
|
||||
@Override
|
||||
public R queryMineTask(TaskQueryParamDto taskQueryParamDto) {
|
||||
taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString());
|
||||
R<Page<TaskDto>> r = flowEngineService.queryAssignTask(taskQueryParamDto);
|
||||
|
||||
if (CollUtil.isEmpty(r.getData().getRecords())) {
|
||||
return r;
|
||||
String assign = taskQueryParamDto.getAssign();
|
||||
|
||||
List<TaskDto> taskDtoList = new ArrayList<>();
|
||||
|
||||
int pageIndex = taskQueryParamDto.getCurrent() - 1;
|
||||
int pageSize = taskQueryParamDto.getSize();
|
||||
|
||||
Page<TaskDto> pageResultDto = new Page<>();
|
||||
TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assign).orderByTaskCreateTime().desc();
|
||||
if (StrUtil.isNotBlank(taskQueryParamDto.getProcessName())) {
|
||||
List<ProcessInstance> processInstanceList = runtimeService.createProcessInstanceQuery()
|
||||
.processDefinitionName(taskQueryParamDto.getProcessName())
|
||||
.processInstanceTenantId(TenantContextHolder.getTenantId().toString())
|
||||
.list();
|
||||
if (CollUtil.isNotEmpty(processInstanceList)) {
|
||||
taskQuery.processInstanceIdIn(
|
||||
processInstanceList.stream().map(ProcessInstance::getProcessInstanceId).toList());
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> processInstanceIdSet = r.getData()
|
||||
.getRecords()
|
||||
.stream()
|
||||
if (ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime())) {
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
ZonedDateTime zonedBeforeDateTime = taskQueryParamDto.getTaskTime()[0].atZone(zoneId);
|
||||
ZonedDateTime zonedAfterDateTime = taskQueryParamDto.getTaskTime()[1].atZone(zoneId);
|
||||
Date beforeDate = Date.from(zonedBeforeDateTime.toInstant());
|
||||
Date afterDate = Date.from(zonedAfterDateTime.toInstant());
|
||||
taskQuery.taskCreatedBefore(afterDate).taskCreatedAfter(beforeDate);
|
||||
}
|
||||
|
||||
taskQuery.listPage(pageIndex * pageSize, pageSize).forEach(task -> {
|
||||
String taskId = task.getId();
|
||||
String processInstanceId = task.getProcessInstanceId();
|
||||
log.debug("(taskId) {} processInstanceId={} executrionId={}", task.getName(), processInstanceId,
|
||||
task.getExecutionId());
|
||||
|
||||
String taskDefinitionKey = task.getTaskDefinitionKey();
|
||||
String processDefinitionId = task.getProcessDefinitionId();
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
|
||||
TaskDto taskDto = new TaskDto();
|
||||
taskDto.setFlowId(flowId);
|
||||
taskDto.setTaskCreateTime(task.getCreateTime());
|
||||
taskDto.setNodeId(taskDefinitionKey);
|
||||
taskDto.setProcessInstanceId(processInstanceId);
|
||||
taskDto.setTaskId(taskId);
|
||||
taskDto.setAssign(task.getAssignee());
|
||||
taskDto.setTaskName(task.getName());
|
||||
taskDtoList.add(taskDto);
|
||||
});
|
||||
|
||||
long count = taskQuery.count();
|
||||
|
||||
log.debug("当前有 {} 个任务:", count);
|
||||
|
||||
pageResultDto.setTotal(count);
|
||||
pageResultDto.setRecords(taskDtoList);
|
||||
|
||||
if (CollUtil.isEmpty(taskDtoList)) {
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
Set<String> processInstanceIdSet = taskDtoList.stream()
|
||||
.map(TaskDto::getProcessInstanceId)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
@@ -103,14 +200,12 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
.in(ProcessInstanceRecord::getProcessInstanceId, processInstanceIdSet)
|
||||
.list();
|
||||
|
||||
// 发起人
|
||||
// 发起人
|
||||
List<Long> collect = processInstanceRecordList.stream().map(ProcessInstanceRecord::getUserId).toList();
|
||||
List<SysUser> startUserList = userService.getUserById(collect).getData();
|
||||
|
||||
Page<TaskDto> pageResultDto = r.getData();
|
||||
List<TaskDto> taskDtoList = new ArrayList<>();
|
||||
for (TaskDto record : r.getData().getRecords()) {
|
||||
List<TaskDto> taskNewDtoList = new ArrayList<>();
|
||||
for (TaskDto record : taskDtoList) {
|
||||
ProcessInstanceRecord processInstanceRecord = processInstanceRecordList.stream()
|
||||
.filter(w -> StrUtil.equals(w.getProcessInstanceId(), record.getProcessInstanceId()))
|
||||
.findAny()
|
||||
@@ -129,26 +224,83 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
record.setRootUserAvatarUrl(startUser.getAvatar());
|
||||
record.setStartTime(processInstanceRecord.getCreateTime());
|
||||
|
||||
taskDtoList.add(record);
|
||||
taskNewDtoList.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
pageResultDto.setRecords(taskDtoList);
|
||||
pageResultDto.setRecords(taskNewDtoList);
|
||||
|
||||
return R.ok(pageResultDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询已办任务
|
||||
* @param taskQueryParamDto 查询参数
|
||||
* @return R
|
||||
* <p>
|
||||
* 查询当前用户已经完成的任务列表,包括: 1. 用户已审批的任务 2. 补充流程实例信息、发起人信息等
|
||||
* </p>
|
||||
* @param taskQueryParamDto 查询参数,支持分页和时间范围过滤
|
||||
* @return R 包含分页后的已办任务列表
|
||||
*/
|
||||
@Override
|
||||
public R queryMineEndTask(TaskQueryParamDto taskQueryParamDto) {
|
||||
taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString());
|
||||
R<Page<TaskDto>> r = flowEngineService.queryCompletedTask(taskQueryParamDto);
|
||||
|
||||
Page<TaskDto> pageResultDto = r.getData();
|
||||
HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService
|
||||
.createHistoricActivityInstanceQuery();
|
||||
HistoricActivityInstanceQuery activityInstanceQuery = historicActivityInstanceQuery
|
||||
.taskAssignee(taskQueryParamDto.getAssign())
|
||||
.finished()
|
||||
.orderByHistoricActivityInstanceEndTime()
|
||||
.desc();
|
||||
|
||||
if (ArrayUtil.isNotEmpty(taskQueryParamDto.getTaskTime())) {
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
ZonedDateTime zonedBeforeDateTime = taskQueryParamDto.getTaskTime()[0].atZone(zoneId);
|
||||
ZonedDateTime zonedAfterDateTime = taskQueryParamDto.getTaskTime()[1].atZone(zoneId);
|
||||
Date beforeDate = Date.from(zonedBeforeDateTime.toInstant());
|
||||
Date afterDate = Date.from(zonedAfterDateTime.toInstant());
|
||||
activityInstanceQuery.finishedBefore(afterDate).finishedAfter(beforeDate);
|
||||
}
|
||||
|
||||
List<HistoricActivityInstance> list = activityInstanceQuery
|
||||
.listPage((taskQueryParamDto.getCurrent() - 1) * taskQueryParamDto.getSize(), taskQueryParamDto.getSize());
|
||||
|
||||
long count = activityInstanceQuery.count();
|
||||
List<TaskDto> taskDtoList = new ArrayList<>();
|
||||
|
||||
for (HistoricActivityInstance historicActivityInstance : list) {
|
||||
String activityId = historicActivityInstance.getActivityId();
|
||||
String activityName = historicActivityInstance.getActivityName();
|
||||
String executionId = historicActivityInstance.getExecutionId();
|
||||
String taskId = historicActivityInstance.getTaskId();
|
||||
Date startTime = historicActivityInstance.getStartTime();
|
||||
Date endTime = historicActivityInstance.getEndTime();
|
||||
Long durationInMillis = historicActivityInstance.getDurationInMillis();
|
||||
String processInstanceId = historicActivityInstance.getProcessInstanceId();
|
||||
|
||||
String processDefinitionId = historicActivityInstance.getProcessDefinitionId();
|
||||
// 流程id
|
||||
String flowId = NodeUtil.getFlowId(processDefinitionId);
|
||||
|
||||
TaskDto taskDto = new TaskDto();
|
||||
taskDto.setFlowId(flowId);
|
||||
taskDto.setTaskCreateTime(startTime);
|
||||
taskDto.setTaskEndTime(endTime);
|
||||
taskDto.setNodeId(activityId);
|
||||
taskDto.setExecutionId(executionId);
|
||||
taskDto.setProcessInstanceId(processInstanceId);
|
||||
taskDto.setDurationInMillis(durationInMillis);
|
||||
taskDto.setTaskId(taskId);
|
||||
taskDto.setAssign(historicActivityInstance.getAssignee());
|
||||
taskDto.setTaskName(activityName);
|
||||
|
||||
taskDtoList.add(taskDto);
|
||||
}
|
||||
|
||||
Page<TaskDto> pageResultDto = new Page<>();
|
||||
pageResultDto.setTotal(count);
|
||||
pageResultDto.setRecords(taskDtoList);
|
||||
|
||||
List<TaskDto> records = pageResultDto.getRecords();
|
||||
if (CollUtil.isEmpty(records)) {
|
||||
return R.ok(pageResultDto);
|
||||
@@ -168,7 +320,7 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
List<Long> collect = processInstanceRecordList.stream().map(ProcessInstanceRecord::getUserId).toList();
|
||||
List<SysUser> startUserList = userService.getUserById(collect).getData();
|
||||
|
||||
List<TaskDto> taskDtoList = new ArrayList<>();
|
||||
List<TaskDto> taskNewDtoList = new ArrayList<>();
|
||||
for (TaskDto record : records) {
|
||||
|
||||
ProcessInstanceRecord processInstanceRecord = processInstanceRecordList.stream()
|
||||
@@ -191,23 +343,30 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
record.setRootUserAvatarUrl(startUser.getAvatar());
|
||||
record.setTaskId(record.getTaskId());
|
||||
record.setStartTime(processInstanceRecord.getCreateTime());
|
||||
taskDtoList.add(record);
|
||||
taskNewDtoList.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
pageResultDto.setRecords(taskDtoList);
|
||||
pageResultDto.setRecords(taskNewDtoList);
|
||||
|
||||
return R.ok(pageResultDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束流程实例
|
||||
* @param processInstanceParamDto 流程实例参数数据传输对象
|
||||
* @return 返回处理结果
|
||||
* <p>
|
||||
* 手动结束一个流程实例,通常用于: 1. 流程审批完成,正常结束 2. 流程被终止或取消
|
||||
*
|
||||
* 该方法会更新流程实例记录的结束时间、状态和结束原因
|
||||
* </p>
|
||||
* @param processInstanceParamDto 流程实例参数,包含: - processInstanceId: 流程实例ID - paramMap:
|
||||
* 可能包含approve_condition等结束原因
|
||||
* @return R 操作结果
|
||||
*/
|
||||
@Override
|
||||
public R end(ProcessInstanceParamDto processInstanceParamDto) {
|
||||
|
||||
// 从参数中提取审批条件,用于判断流程结束原因(通过/拒绝)
|
||||
Optional<Boolean> objectOptional = processInstanceParamDto.getParamMap()
|
||||
.keySet()
|
||||
.stream()
|
||||
@@ -215,14 +374,17 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
.reduce((first, second) -> second)
|
||||
.map(key -> MapUtil.getBool(processInstanceParamDto.getParamMap(), key, true));
|
||||
|
||||
// 构建更新条件
|
||||
LambdaUpdateChainWrapper<ProcessInstanceRecord> lambdaUpdateChainWrapper = processInstanceRecordService
|
||||
.lambdaUpdate()
|
||||
.set(ProcessInstanceRecord::getEndTime, new Date())
|
||||
.set(ProcessInstanceRecord::getStatus, NodeStatusEnum.YJS.getCode());
|
||||
|
||||
// 如果有审批结果,设置结束原因
|
||||
objectOptional
|
||||
.ifPresent(aBoolean -> lambdaUpdateChainWrapper.set(ProcessInstanceRecord::getFinishReason, aBoolean));
|
||||
|
||||
// 执行更新
|
||||
lambdaUpdateChainWrapper
|
||||
.eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceParamDto.getProcessInstanceId())
|
||||
.update(new ProcessInstanceRecord());
|
||||
@@ -230,9 +392,12 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我发起的
|
||||
* @param taskQueryParamDto 查询条件
|
||||
* @return
|
||||
* 查询我发起的流程
|
||||
* <p>
|
||||
* 查询当前用户发起的所有流程实例,支持: 1. 按状态过滤(进行中、已完成等) 2. 按时间范围过滤 3. 分页查询
|
||||
* </p>
|
||||
* @param taskQueryParamDto 查询条件,包含状态、时间范围、分页参数
|
||||
* @return R 包含分页后的流程实例列表
|
||||
*/
|
||||
@Override
|
||||
public R queryMineStarted(TaskQueryParamDto taskQueryParamDto) {
|
||||
@@ -252,9 +417,12 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询抄送给我的
|
||||
* @param taskQueryParamDto 查询参数
|
||||
* @return
|
||||
* 查询抄送给我的流程
|
||||
* <p>
|
||||
* 查询抄送给当前用户的流程列表。抄送功能允许流程参与者将流程信息 通知给其他相关人员,被抄送人可以查看流程但不参与审批
|
||||
* </p>
|
||||
* @param taskQueryParamDto 查询参数,支持时间范围过滤和分页
|
||||
* @return R 包含分页后的抄送记录,包括发起人信息
|
||||
*/
|
||||
@Override
|
||||
public R queryMineCC(TaskQueryParamDto taskQueryParamDto) {
|
||||
@@ -288,16 +456,21 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
}
|
||||
|
||||
Page p = BeanUtil.copyProperties(page, Page.class);
|
||||
|
||||
p.setRecords(processCopyVoList);
|
||||
|
||||
return R.ok(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化流程显示
|
||||
* @param nodeFormatParamVo
|
||||
* @return
|
||||
* <p>
|
||||
* 将流程定义数据格式化为前端可展示的节点结构,主要功能: 1. 解析流程定义的JSON数据 2. 标记已完成的节点 3. 根据流程变量计算条件节点的显示状态 4.
|
||||
* 处理节点权限和表单权限
|
||||
*
|
||||
* 该方法用于流程图的可视化展示
|
||||
* </p>
|
||||
* @param nodeFormatParamVo 格式化参数,包含: - flowId: 流程定义ID - processInstanceId: 流程实例ID -
|
||||
* taskId: 任务ID(可选) - paramMap: 流程变量
|
||||
* @return R 包含格式化后的节点列表
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
@@ -319,29 +492,22 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
if (StrUtil.isNotBlank(nodeFormatParamVo.getTaskId())) {
|
||||
VariableQueryParamDto variableQueryParamDto = new VariableQueryParamDto();
|
||||
variableQueryParamDto.setTaskId(nodeFormatParamVo.getTaskId());
|
||||
R<Map<String, Object>> r = flowEngineService.queryTaskVariables(variableQueryParamDto);
|
||||
if (!r.isOk()) {
|
||||
ProcessNodeRecordAssignUser processNodeRecordAssignUser = processNodeRecordAssignUserService
|
||||
.lambdaQuery()
|
||||
.eq(ProcessNodeRecordAssignUser::getTaskId, nodeFormatParamVo.getTaskId())
|
||||
.eq(ProcessNodeRecordAssignUser::getStatus, NodeStatusEnum.YJS.getCode())
|
||||
.last("limit 1")
|
||||
.orderByDesc(ProcessNodeRecordAssignUser::getEndTime)
|
||||
.one();
|
||||
Map<String, Object> variables;
|
||||
List<String> keyList = variableQueryParamDto.getKeyList();
|
||||
if (CollUtil.isEmpty(keyList)) {
|
||||
TaskQuery taskQuery = taskService.createTaskQuery();
|
||||
|
||||
String data = processNodeRecordAssignUser.getData();
|
||||
Task task = taskQuery.taskId(variableQueryParamDto.getTaskId()).singleResult();
|
||||
if (task == null) {
|
||||
return R.failed("任务不存在");
|
||||
}
|
||||
|
||||
Map<String, Object> variableMap = objectMapper.readValue(data, new TypeReference<>() {
|
||||
});
|
||||
variableMap.putAll(paramMap);
|
||||
paramMap.putAll(variableMap);
|
||||
variables = runtimeService.getVariables(task.getExecutionId());
|
||||
}
|
||||
else {
|
||||
Map<String, Object> variableMap = r.getData();
|
||||
variableMap.putAll(paramMap);
|
||||
paramMap.putAll(variableMap);
|
||||
variables = taskService.getVariables(variableQueryParamDto.getTaskId(), keyList);
|
||||
}
|
||||
|
||||
paramMap.putAll(variables);
|
||||
}
|
||||
|
||||
Set<String> completeNodeSet = new HashSet<>();
|
||||
@@ -370,8 +536,14 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
|
||||
/**
|
||||
* 流程详情
|
||||
* @param processInstanceId
|
||||
* @return
|
||||
* <p>
|
||||
* 查询流程实例的详细信息,包括: 1. 流程定义信息(流程结构) 2. 表单项定义 3. 表单数据(用户填写的数据) 4. 表单权限配置 5. 流程状态信息
|
||||
*
|
||||
* 该方法主要用于流程详情页面的数据展示
|
||||
* </p>
|
||||
* @param processInstanceId 流程实例ID
|
||||
* @return R 包含流程详情的Dict对象
|
||||
* @throws Exception 当解析JSON数据失败时抛出异常
|
||||
*/
|
||||
@SneakyThrows
|
||||
@Override
|
||||
@@ -399,6 +571,7 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService {
|
||||
.set("formItems", oaForms.getFormItems())
|
||||
.set("formData", formData)
|
||||
.set("formData", formData)
|
||||
.set("formConfig", oaForms.getFormConfig())
|
||||
.set("formPerms", formPerms1);
|
||||
|
||||
return R.ok(set);
|
||||
|
||||
Reference in new issue
Block a user