From cd1854e498a7f43b7cf7fbf36a6161e7c7ea3ca0 Mon Sep 17 00:00:00 2001 From: lengleng Date: Fri, 8 Aug 2025 20:51:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(flow):=20=E6=94=AF=E6=8C=81=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E8=A1=A8=E5=8D=95=EF=BC=8C=E6=8F=90=E4=BE=9B=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E8=A1=A8=E5=8D=95=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flow/dto/ProcessInstanceParamDto.java | 58 +++ .../flow/dto/ProcessNodeRecordParamDto.java | 103 +++++ .../pig4cloud/pigx/flow/entity/Process.java | 146 +++++++ .../controller/ProcessFlowController.java | 238 ++++++++++ .../pigx/flow/entity/BpmOaLeaveEntity.java | 122 ++++++ .../pigx/flow/mapper/BpmOaLeaveMapper.java | 11 + .../impl/ProcessNodeDataServiceImpl.java | 74 ++++ .../flow/service/impl/ProcessServiceImpl.java | 306 +++++++++++++ .../flow/service/impl/TaskServiceImpl.java | 408 ++++++++++++++++++ .../resources/mapper/BpmOaLeaveMapper.xml | 23 + .../listeners/FlowProcessEventListener.java | 137 +++--- .../listeners/NodeTaskCompleteNotify.java | 108 +++++ .../task/controller/BpmOaLeaveController.java | 137 ++++++ .../flow/task/service/BpmOaLeaveService.java | 15 + .../IProcessInstanceStatusEventService.java | 25 ++ .../service/impl/BpmOaLeaveServiceImpl.java | 83 ++++ .../impl/ProcessInstanceServiceImpl.java | 319 ++++++++++---- 17 files changed, 2185 insertions(+), 128 deletions(-) create mode 100644 pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessInstanceParamDto.java create mode 100644 pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordParamDto.java create mode 100644 pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/Process.java create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessFlowController.java create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/entity/BpmOaLeaveEntity.java create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/mapper/BpmOaLeaveMapper.java create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessNodeDataServiceImpl.java create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java create mode 100644 pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java create mode 100644 pigx-flow/pigx-flow-biz/src/main/resources/mapper/BpmOaLeaveMapper.xml create mode 100644 pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/NodeTaskCompleteNotify.java create mode 100644 pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/controller/BpmOaLeaveController.java create mode 100644 pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/BpmOaLeaveService.java create mode 100644 pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/IProcessInstanceStatusEventService.java create mode 100644 pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/BpmOaLeaveServiceImpl.java diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessInstanceParamDto.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessInstanceParamDto.java new file mode 100644 index 000000000..6f4797751 --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessInstanceParamDto.java @@ -0,0 +1,58 @@ +package com.pig4cloud.pigx.flow.dto; + +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +/** + * 流程实例参数数据传输对象 + *

+ * 该DTO用于启动流程实例或操作流程实例时传递参数。 包含流程定义信息、流程变量、发起人等核心信息。 主要用于流程引擎的实例创建和流程变量设置。 + *

+ * + * @author pigx + */ +@Data +public class ProcessInstanceParamDto { + + /** + * 流程定义ID + *

+ * 要启动的流程定义的唯一标识。 对应已部署的流程模板ID,如:leave_process_v1。 + *

+ */ + private String flowId; + + /** + * 流程变量集合 + *

+ * 存储流程执行过程中需要的各种变量,如: - 表单数据:申请金额、请假天数等 - 控制变量:审批级别、分支条件等 - 业务数据:关联的业务ID、业务类型等 + * 这些变量会在流程执行过程中被各个节点使用。 + *

+ */ + private Map paramMap = new HashMap<>(); + + /** + * 流程发起人ID + *

+ * 发起流程实例的用户ID。 用于记录流程的发起者,也可能作为第一个任务的执行人。 + *

+ */ + private String startUserId; + + /** + * 流程实例ID + *

+ * 已存在的流程实例ID,用于对已有流程实例进行操作。 如果是启动新流程,此字段为空; 如果是操作已有流程(如设置变量),则需要提供此ID。 + *

+ */ + private String processInstanceId; + + /** 状态 */ + private Integer status; + + /** 完成原因 */ + private String finishReason; + +} diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordParamDto.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordParamDto.java new file mode 100644 index 000000000..2f196438a --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordParamDto.java @@ -0,0 +1,103 @@ +package com.pig4cloud.pigx.flow.dto; + +import lombok.Data; + +/** + * 流程节点记录参数数据传输对象 + *

+ * 该DTO用于创建流程节点的执行记录。记录节点的执行信息, 包括节点类型、表单数据、执行上下文等。 主要用于流程历史记录的创建和流程轨迹的追踪。 + *

+ * + * @author pigx + */ +@Data +public class ProcessNodeRecordParamDto { + + /** + * 流程定义ID + *

+ * 流程模板的唯一标识。 标识该记录所属的流程定义。 + *

+ */ + private String flowId; + + /** + * 流程实例ID + *

+ * 运行中的流程实例唯一标识。 用于关联到具体的流程实例。 + *

+ */ + private String processInstanceId; + + /** + * 表单数据 + *

+ * JSON格式的表单数据,包含节点执行时的业务数据。 记录节点处理时的表单内容快照。 + *

+ */ + private String data; + + /** + * 节点ID + *

+ * 流程节点的唯一标识。 标识具体执行的是哪个节点。 + *

+ */ + private String nodeId; + + /** + * 节点类型 + *

+ * 节点的类型标识,对应NodeTypeEnum: - ROOT: 发起人节点 - APPROVAL: 审批节点 - CC: 抄送节点 - CONDITION: 条件节点 + * - CONCURRENT: 并行节点 - DELAY: 延迟节点 - TRIGGER: 触发器节点 - EMPTY: 空节点 - END: 结束节点 + *

+ */ + private String nodeType; + + /** + * 节点名称 + *

+ * 流程节点的显示名称,如"部门经理审批"、"财务审核"等。 用于在界面上展示,便于用户理解。 + *

+ */ + private String nodeName; + + /** + * 执行ID + *

+ * Flowable引擎的执行ID。 用于关联流程引擎中的执行实例。 + *

+ */ + private String executionId; + + /** + * 任务ID + *

+ * Flowable引擎生成的任务ID。 对于用户任务节点,记录对应的任务ID。 + *

+ */ + private String taskId; + + /** + * 审批意见 + *

+ * 节点执行时的意见或备注。 通常用于审批节点记录审批意见。 + *

+ */ + private String approveDesc; + + /** + * 任务类型 + *

+ * 标识任务的具体类型,用于区分不同的处理逻辑: - APPROVAL: 审批任务 - COPY: 抄送任务 - NOTIFY: 通知任务 - SYSTEM: 系统任务 + *

+ */ + private String taskType; + + /** 状态 */ + private Integer status; + + /** 完成原因 */ + private String finishReason; + +} diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/Process.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/Process.java new file mode 100644 index 000000000..b841172db --- /dev/null +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/Process.java @@ -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; + +/** + * 流程定义实体类 + *

+ * 该实体类用于存储工作流程的定义信息,包括流程的基本信息、表单配置、流程配置等。 每个流程定义可以创建多个流程实例,支持多租户隔离。 + *

+ * + * @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; + +} diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessFlowController.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessFlowController.java new file mode 100644 index 000000000..913a58dce --- /dev/null +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/controller/ProcessFlowController.java @@ -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.*; + +/** + * 流程引擎核心控制器 + *

+ * 该控制器是工作流引擎的核心接口,直接与Flowable引擎交互,提供流程生命周期管理功能: 1. 流程定义的创建和部署 - 将前端设计的流程模型转换为BPMN格式并部署到引擎 + * 2. 流程实例的启动和管理 - 启动流程实例、停止运行中的流程 3. 任务的查询和处理 - 查询待办任务、已办任务,执行任务审批 + *

+ *

+ * 该控制器主要供内部系统调用,通过封装Flowable原生API,简化流程操作。 所有操作都支持多租户隔离,确保数据安全。 + *

+ * + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/flow") +public class ProcessFlowController { + + private final TaskService taskService; + + private final HistoryService historyService; + + private final RepositoryService repositoryService; + + private final RuntimeService runtimeService; + + /** + * 启动流程实例 + *

+ * 根据流程定义ID启动一个新的流程实例。启动时可以传入流程变量, 这些变量会在整个流程执行过程中使用(如条件判断、人员分配等)。 + *

+ * @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); + + } + + /** + * 审批任务(测试接口) + *

+ * 该接口用于测试流程审批功能,完成指定的任务并设置审批结果。 实际业务中应使用TaskController中更完善的审批接口。 + *

+ * @param taskId 待审批的任务ID + * @param approved 审批结果:true-通过,false-拒绝 + */ + @PostMapping("/approve") + public void approve(String taskId, boolean approved) { + Map variables = new HashMap<>(); + variables.put("approved", approved); + variables.put("ko", 10); + variables.put("assigneeListSub", CollUtil.newArrayList("aa", "bb")); + taskService.complete(taskId, variables); + + } + + /** + * 分页查询用户已办任务 + *

+ * 查询指定用户已经完成的历史任务,支持按时间范围过滤。 返回的任务信息包含任务处理时长、完成时间等历史数据。 + *

+ * @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 list = activityInstanceQuery + .listPage((taskQueryParamDto.getCurrent() - 1) * taskQueryParamDto.getSize(), taskQueryParamDto.getSize()); + + long count = activityInstanceQuery.count(); + List 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 pageResultDto = new Page<>(); + pageResultDto.setTotal(count); + pageResultDto.setRecords(taskDtoList); + return R.ok(pageResultDto); + } + + /** + * 分页查询用户待办任务 + *

+ * 查询分配给指定用户的待处理任务,支持按流程名称和创建时间范围过滤。 待办任务是需要用户处理的活动任务,处理后任务将流转到下一节点。 + *

+ * @param taskQueryParamDto 查询参数,包含: - assign: 任务处理人ID - processName: 流程名称(可选,用于过滤) - + * taskTime: 任务创建时间范围数组 [开始时间, 结束时间] - current: 当前页码 - size: 每页大小 + * @return R 统一响应对象,包含分页的待办任务列表(TaskDto) + */ + @PostMapping("/queryAssignTask") + public R queryAssignTask(@RequestBody TaskQueryParamDto taskQueryParamDto) { + + String assign = taskQueryParamDto.getAssign(); + + List taskDtoList = new ArrayList<>(); + + int pageIndex = taskQueryParamDto.getCurrent() - 1; + int pageSize = taskQueryParamDto.getSize(); + + Page pageResultDto = new Page<>(); + TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assign).orderByTaskCreateTime().desc(); + if (StrUtil.isNotBlank(taskQueryParamDto.getProcessName())) { + List 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); + } + +} diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/entity/BpmOaLeaveEntity.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/entity/BpmOaLeaveEntity.java new file mode 100644 index 000000000..47f16af00 --- /dev/null +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/entity/BpmOaLeaveEntity.java @@ -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 { + + /** + * 请假表单主键 + */ + @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; + +} diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/mapper/BpmOaLeaveMapper.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/mapper/BpmOaLeaveMapper.java new file mode 100644 index 000000000..558a04c00 --- /dev/null +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/mapper/BpmOaLeaveMapper.java @@ -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 { + +} diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessNodeDataServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessNodeDataServiceImpl.java new file mode 100644 index 000000000..44a0d0413 --- /dev/null +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessNodeDataServiceImpl.java @@ -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; + +/** + * 流程节点数据服务实现类 + *

+ * 该类负责管理流程节点的详细数据,主要功能包括: 1. 保存流程节点的配置数据(审批人、表单权限、条件等) 2. 查询指定节点的详细配置信息 3. + * 为流程执行提供节点配置数据支持 + * + * 节点数据以JSON格式存储,包含节点类型、审批人配置、表单权限等信息 + *

+ * + * @author Vincent + * @since 2023-05-07 + */ +@Service +public class ProcessNodeDataServiceImpl extends ServiceImpl + implements IProcessNodeDataService { + + /** + * 保存流程节点数据 + *

+ * 将流程节点的配置信息保存到数据库。节点数据包括: - 节点类型(审批、抄送、条件等) - 审批人配置 - 表单权限设置 - 条件表达式等 + *

+ * @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.lambdaQuery() + .eq(ProcessNodeData::getNodeId, processNodeData.getNodeId()) + .eq(ProcessNodeData::getFlowId, processNodeData.getFlowId())); + baseMapper.insert(processNodeData); + return R.ok(); + } + + /** + * 获取节点数据 + *

+ * 根据流程ID和节点ID查询节点的详细配置信息。 节点数据存储为JSON格式,查询后会自动转换为Node对象 + *

+ * @param flowId 流程ID + * @param nodeId 节点ID + * @return R 包含节点配置的Node对象,如果节点不存在则返回null + */ + @Override + public R getNodeData(String flowId, String nodeId) { + Optional 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); + } + +} diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java new file mode 100644 index 000000000..7ef33cf00 --- /dev/null +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java @@ -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; + +/** + * 流程定义服务实现类 + *

+ * 实现流程定义的核心业务逻辑,包括流程的创建、编辑、查询、部署等功能。 该类是流程管理的核心实现,负责: - 流程定义的CRUD操作 - 流程表单的动态配置管理 - + * 流程节点的解析和验证 - 与Flowable引擎的交互部署 - 流程权限的管理和控制 + *

+ * + * @author cxygzl + * @since 2023-05-25 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ProcessServiceImpl extends ServiceImpl implements IProcessService { + + private final IProcessStarterService processStarterService; + + private final RepositoryService repositoryService; + + private final ObjectMapper objectMapper; + + private final Sequence flowSequence; + + /** + * 获取流程详细信息 + *

+ * 根据流程ID获取完整的流程配置信息,包括: - 流程基本信息 - 表单配置(自动生成序列号字段的值) - 需要发起人选择执行人的节点列表 - 表单权限配置 + *

+ * @param flowId 流程定义ID + * @return 包含流程详细信息的响应对象 + */ + @Override + public R getDetail(String flowId) { + // 获取流程详情 + ProcessVO processVO = this.getProcessVO(flowId); + return R.ok(processVO); + } + + /** + * 获取流程视图对象 + *

+ * 内部方法,用于构建流程的视图对象。主要处理: - 解析流程节点结构 - 查找需要发起人选择的节点 - 处理序列号类型的表单字段,自动生成下一个序号 - + * 提取起始节点的表单权限配置 + *

+ * @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 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查询流程定义 + *

+ * 通过流程的唯一标识符查询流程定义实体。 注意:一个流程可能有多个版本,此方法返回当前有效版本。 + *

+ * @param flowId 流程定义ID + * @return 流程定义实体,如果不存在返回null + */ + @Override + public Process getByFlowId(String flowId) { + return this.lambdaQuery().eq(Process::getFlowId, flowId).one(); + } + + /** + * 根据流程ID更新流程定义 + *

+ * 更新流程的配置信息,包括表单、节点、权限等。 更新不会影响已经运行的流程实例。 + *

+ * @param process 包含更新信息的流程实体 + */ + @Override + public void updateByFlowId(Process process) { + this.lambdaUpdate().eq(Process::getFlowId, process.getFlowId()).update(process); + } + + /** + * 隐藏流程定义 + *

+ * 将流程标记为隐藏状态,隐藏的流程: - 不在流程列表中显示 - 不能创建新的流程实例 - 已有的流程实例可以继续执行 + *

+ * @param flowId 要隐藏的流程ID + */ + @Override + public void hide(String flowId) { + this.lambdaUpdate() + .set(Process::getHidden, YesNoEnum.YES.getCode()) + .eq(Process::getFlowId, flowId) + .update(new Process()); + } + + /** + * 创建新的流程定义 + *

+ * 创建流程的完整流程: 1. 调用流程引擎创建BPMN模型 2. 如果是更新操作,隐藏旧版本 3. 保存流程定义到数据库 4. 设置流程发起人权限 5. + * 生成流程使用范围描述 + *

+ * @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>() { + }).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 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(); + } + + /** + * 更新流程状态或分组 + *

+ * 支持三种操作类型: - stop:停用流程(设置stop=1) - delete:删除流程(逻辑删除,设置hidden=1) - 其他:启用流程(设置stop=0) + * 同时支持修改流程所属分组 + *

+ * @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(); + } + +} diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java new file mode 100644 index 000000000..70b6e43e5 --- /dev/null +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/TaskServiceImpl.java @@ -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.*; + +/** + * 任务服务实现类 + *

+ * 该类是流程任务操作的核心服务,负责处理用户与流程任务的交互,主要功能包括: 1. 任务查询(待办任务详情、任务表单权限等) 2. 任务操作(完成、委托、转办、退回等) 3. + * 任务统计(首页数据看板) 4. 流程控制(终止流程、加签等高级操作) + * + * 该服务整合了流程引擎的任务API,为前端提供统一的任务操作接口 + *

+ * + * @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; + + /** + * 查询首页数据看板 + *

+ * 获取当前用户的任务统计数据,包括: 1. 待办任务数量 2. 已办任务数量 3. 我发起的流程数量 4. 抄送给我的数量 + *

+ * @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); + } + + /** + * 查询任务 + *

+ * 查询指定任务的详细信息,包括: 1. 任务的基本信息(节点名称、执行人等) 2. 流程表单定义和表单数据 3. 当前节点的表单权限配置 4. + * 任务是否可委托、是否是委托任务等状态 + * + * 对于已完成的任务,会从历史记录中获取任务数据 + *

+ * @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 = 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 variables = taskService.getVariables(taskId); + Map 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 = 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 variableInstanceList = historyService.createHistoricVariableInstanceQuery() + .processInstanceId(processInstanceId) + .list(); + Map 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 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 collect = objectMapper.readValue(data, new TypeReference<>() { + }); + paramMap.putAll(collect); + + } + } + + } + + // 当前节点数据 + Node node = nodeDataService.getNodeData(taskResultDto.getFlowId(), taskResultDto.getNodeId()).getData(); + Map 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); + } + + /** + * 完成任务 + *

+ * 执行任务的完成操作,将任务标记为已完成并流转到下一个节点。 该方法会将当前用户ID设置到任务参数中,确保任务由正确的用户完成 + *

+ * @param taskParamDto 任务参数,包含任务ID、审批意见、流程变量等 + * @return R 操作结果 + */ + @Override + public R completeTask(TaskParamDto taskParamDto) { + long userId = SecurityUtils.getUser().getId(); + taskParamDto.setUserId(String.valueOf(userId)); + + Map taskLocalParamMap = taskParamDto.getTaskLocalParamMap(); + if (CollUtil.isNotEmpty(taskLocalParamMap)) { + taskService.setVariablesLocal(taskParamDto.getTaskId(), taskLocalParamMap); + } + + taskService.complete(taskParamDto.getTaskId(), taskParamDto.getParamMap()); + + return R.ok(); + } + + /** + * 前加签 + *

+ * 将任务委托给其他用户处理,委托后任务会分配给被委托人, 被委托人完成后任务会返回给原委托人 + *

+ * @param taskParamDto 任务参数,包含任务ID、被委托人ID列表等 + * @return R 操作结果 + */ + @Transactional + @Override + public R delegateTask(TaskParamDto taskParamDto) { + taskParamDto.setUserId(SecurityUtils.getUser().getId().toString()); + return flowableEngineService.delegateTask(taskParamDto); + } + + /** + * 加签完成任务 + *

+ * 被委托人完成委托任务后,通过此方法将任务返回给原委托人。 这是委托流程的第二步,用于结束委托状态 + *

+ * @param taskParamDto 任务参数,包含任务ID、审批意见等 + * @return R 操作结果 + */ + @Override + public R resolveTask(TaskParamDto taskParamDto) { + return flowableEngineService.resolveTask(taskParamDto); + } + + /** + * 设置执行人 + *

+ * 转办任务给其他用户,与委托不同,转办后任务直接由新执行人处理, 不会返回给原执行人 + *

+ * @param taskParamDto 任务参数,包含任务ID、新执行人ID等 + * @return R 操作结果 + */ + @Override + public R setAssignee(TaskParamDto taskParamDto) { + Map 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(); + } + + /** + * 结束流程 + *

+ * 强制终止流程实例的执行。该方法会: 1. 递归查找并终止所有子流程实例 2. 更新流程实例记录状态为已结束 3. + * 设置结束原因为强制终止(finishReason=9) + * + * 通常用于流程异常或需要强制结束的场景 + *

+ * @param taskParamDto 任务参数,包含流程实例ID + * @return R 操作结果 + */ + @Override + public R stopProcessInstance(TaskParamDto taskParamDto) { + String reqProcessInstanceId = taskParamDto.getProcessInstanceId(); + List 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 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(); + } + + /** + * 退回 + *

+ * 将任务退回到指定的历史节点重新处理。 常用于审批不通过需要重新修改的场景 + *

+ * @param taskParamDto 任务参数,包含任务ID、目标节点ID、退回原因等 + * @return R 操作结果 + */ + @Override + public R back(TaskParamDto taskParamDto) { + taskParamDto.setUserId(SecurityUtils.getUser().getId().toString()); + return flowableEngineService.back(taskParamDto); + } + + /** + * 递归获取所有需要停止的子流程实例ID列表 + *

+ * 当一个流程包含子流程时,终止主流程需要同时终止所有子流程。 该方法递归查找指定流程的所有子流程实例ID + *

+ * @param processInstanceId 父流程实例ID + * @return 所有子流程实例ID列表 + */ + private List getAllStopProcessInstanceIdList(String processInstanceId) { + List list = processInstanceRecordService.lambdaQuery() + .eq(ProcessInstanceRecord::getParentProcessInstanceId, processInstanceId) + .list(); + + List collect = new ArrayList<>(list.stream().map(ProcessInstanceRecord::getProcessInstanceId).toList()); + + for (ProcessInstanceRecord processInstanceRecord : list) { + List allStopProcessInstanceIdList = getAllStopProcessInstanceIdList( + processInstanceRecord.getProcessInstanceId()); + + collect.addAll(allStopProcessInstanceIdList); + + } + return collect; + } + +} diff --git a/pigx-flow/pigx-flow-biz/src/main/resources/mapper/BpmOaLeaveMapper.xml b/pigx-flow/pigx-flow-biz/src/main/resources/mapper/BpmOaLeaveMapper.xml new file mode 100644 index 000000000..14dd533e5 --- /dev/null +++ b/pigx-flow/pigx-flow-biz/src/main/resources/mapper/BpmOaLeaveMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/FlowProcessEventListener.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/FlowProcessEventListener.java index a6d90468e..8a6c3c7cf 100644 --- a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/FlowProcessEventListener.java +++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/FlowProcessEventListener.java @@ -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; /** - * 流程事件监听器 - * 用于监听流程执行过程中的各种事件,并执行相应的操作 - * 包括节点开始/结束、任务分配/完成、流程开始/结束等事件 + * 流程全局事件监听器 + *

+ * Flowable全局事件监听器实现,用于监听和处理流程执行过程中的各种事件。 该监听器是流程引擎与业务系统交互的核心组件,负责将流程引擎的事件转换为业务事件。 + *

+ * 监听的主要事件类型: 1. 节点事件:节点开始、节点完成、多实例节点完成 2. 任务事件:任务分配、任务完成 3. 流程事件:流程开始、流程结束 4. + * 变量事件:变量创建、变量更新、变量删除 + *

+ * 主要功能: - 记录流程执行轨迹 - 触发业务系统的相关操作 - 发送任务通知 - 同步流程数据到业务系统 + * + * @author pigx */ @Slf4j public class FlowProcessEventListener implements FlowableEventListener { /** - * 当事件被触发时调用的方法 - * 根据不同事件类型执行不同的业务逻辑 - * - * @param event Flowable引擎触发的事件对象 + * 处理Flowable引擎触发的事件 + *

+ * 该方法是事件监听的核心,根据事件类型分发到不同的处理逻辑。 每种事件类型都会触发相应的业务操作,如记录日志、发送通知等。 + * @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 是否在监听器异常时终止操作 + * 确定监听器抛出异常时是否终止当前操作 + *

+ * 返回false表示即使监听器处理出现异常,流程引擎仍会继续执行。 这确保了监听器的异常不会影响流程的正常执行。 + * @return false,表示监听器异常不终止流程执行 */ @Override public boolean isFailOnException() { @@ -300,9 +326,9 @@ public class FlowProcessEventListener implements FlowableEventListener { /** * 确定监听器是否在事务生命周期事件发生时立即触发 - * 返回false表示不在事务生命周期事件上立即触发 - * - * @return 是否在事务生命周期事件上触发 + *

+ * 返回false表示监听器不需要在事务生命周期事件上立即触发。 这意味着监听器将在正常的事件触发时机执行。 + * @return false,表示不在事务生命周期事件上触发 */ @Override public boolean isFireOnTransactionLifecycleEvent() { @@ -311,12 +337,13 @@ public class FlowProcessEventListener implements FlowableEventListener { /** * 获取在当前事务的生命周期中的触发点 - * 返回null表示不在特定事务生命周期触发点触发 - * - * @return 事务生命周期中的触发点,null表示没有指定 + *

+ * 返回null表示监听器不需要在特定的事务生命周期触发点触发。 可选值包括:committed、committing、rolled-back等。 + * @return null,表示没有指定特定的事务触发点 */ @Override public String getOnTransaction() { return null; } + } \ No newline at end of file diff --git a/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/NodeTaskCompleteNotify.java b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/NodeTaskCompleteNotify.java new file mode 100644 index 000000000..6b7ab81da --- /dev/null +++ b/pigx-flow/pigx-flow-engine/pigx-flow-engine-biz/src/main/java/com/pig4cloud/pigx/flow/engine/listeners/NodeTaskCompleteNotify.java @@ -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; + +/** + * 节点任务完成通知服务 + *

+ * 该服务负责在流程节点任务完成时发送异步通知。当节点配置了外部事件回调URL时, 系统会自动调用该URL通知任务完成事件。这种机制允许流程与外部系统进行集成, + * 实现业务流程的自动化。通知采用异步方式发送,不会阻塞主流程的执行。 + *

+ * + * @author lengleng + * @date 2024/7/9 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class NodeTaskCompleteNotify { + + private final IProcessNodeDataService processNodeDataService; + + private final ProcessInstanceRecordMapper processInstanceRecordMapper; + + private final Optional> processInstanceStatusEventServicesOptional; + + /** + * 异步发送任务完成通知 + *

+ * 当流程节点任务完成时,如果节点配置了事件通知URL,则通过HTTP POST请求 将任务完成信息发送到指定的URL。通知内容包括任务ID、处理人、处理结果等 + * 完整信息。该方法使用异步执行,确保不影响主流程的性能。通知失败时会 记录日志但不会影响流程执行。 + *

+ * @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 + .lambdaQuery() + .eq(ProcessInstanceRecord::getProcessInstanceId, instanceRecordParamDto.getProcessInstanceId())); + + instanceRecordParamDto.setStatus(processInstanceRecord.getStatus()); + instanceRecordParamDto.setFinishReason(processInstanceRecord.getFinishReason()); + iProcessInstanceStatusEventService.handleStatusEvent(instanceRecordParamDto); + } + + } + } + +} diff --git a/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/controller/BpmOaLeaveController.java b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/controller/BpmOaLeaveController.java new file mode 100644 index 000000000..d462b3951 --- /dev/null +++ b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/controller/BpmOaLeaveController.java @@ -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.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 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 bpmOaLeaveList, BindingResult bindingResult) { + return R.ok(bpmOaLeaveService.saveBatch(bpmOaLeaveList)); + } + +} diff --git a/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/BpmOaLeaveService.java b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/BpmOaLeaveService.java new file mode 100644 index 000000000..52e439b2b --- /dev/null +++ b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/BpmOaLeaveService.java @@ -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, IProcessInstanceStatusEventService { + + /** + * 保存并启动请假流程 + * @param bpmOaLeave 请假实体对象 + * @return 保存后的请假实体对象 + */ + BpmOaLeaveEntity saveAndStartProcess(BpmOaLeaveEntity bpmOaLeave); + +} diff --git a/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/IProcessInstanceStatusEventService.java b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/IProcessInstanceStatusEventService.java new file mode 100644 index 000000000..20f2c4dbc --- /dev/null +++ b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/IProcessInstanceStatusEventService.java @@ -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); + +} diff --git a/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/BpmOaLeaveServiceImpl.java b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/BpmOaLeaveServiceImpl.java new file mode 100644 index 000000000..9d3d0c730 --- /dev/null +++ b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/BpmOaLeaveServiceImpl.java @@ -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 + 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.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 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 stringR = flowApiFlowService.startProcessInstance(processInstanceParamDto); + bpmOaLeave.setProcessInstanceId(stringR.getData()); + baseMapper.updateById(bpmOaLeave); + return bpmOaLeave; + } + +} diff --git a/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/ProcessInstanceServiceImpl.java b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/ProcessInstanceServiceImpl.java index aee504085..2be6ffa91 100644 --- a/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/ProcessInstanceServiceImpl.java +++ b/pigx-flow/pigx-flow-task/pigx-flow-task-biz/src/main/java/com/pig4cloud/pigx/flow/task/service/impl/ProcessInstanceServiceImpl.java @@ -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; /** - * 实例进程服务 + * 流程实例服务实现类 + *

+ * 该类是流程实例管理的核心服务,负责处理流程实例的完整生命周期,主要功能包括: 1. 流程实例的启动、结束等生命周期管理 2. 待办任务、已办任务的查询 3. + * 我发起的流程、抄送给我的流程查询 4. 流程节点的格式化显示 5. 流程详情查询 + * + * 该服务整合了Flowable引擎和业务数据,为前端提供统一的流程操作接口 + *

+ * + * @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 + *

+ * 根据流程定义启动一个新的流程实例。该方法会: 1. 获取当前登录用户作为流程发起人 2. 设置流程发起人信息到流程变量中 3. 调用Flowable引擎启动流程 + *

+ * @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 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 + *

+ * 查询分配给当前用户的待办任务列表,并补充以下信息: 1. 流程实例的详细信息(流程名称、分组等) 2. 流程发起人信息(姓名、头像等) 3. 流程启动时间 + *

+ * @param taskQueryParamDto 查询参数,支持分页和条件过滤 + * @return R 包含分页后的待办任务列表 */ @Override public R queryMineTask(TaskQueryParamDto taskQueryParamDto) { taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString()); - R> r = flowEngineService.queryAssignTask(taskQueryParamDto); - if (CollUtil.isEmpty(r.getData().getRecords())) { - return r; + String assign = taskQueryParamDto.getAssign(); + + List taskDtoList = new ArrayList<>(); + + int pageIndex = taskQueryParamDto.getCurrent() - 1; + int pageSize = taskQueryParamDto.getSize(); + + Page pageResultDto = new Page<>(); + TaskQuery taskQuery = taskService.createTaskQuery().taskAssignee(assign).orderByTaskCreateTime().desc(); + if (StrUtil.isNotBlank(taskQueryParamDto.getProcessName())) { + List processInstanceList = runtimeService.createProcessInstanceQuery() + .processDefinitionName(taskQueryParamDto.getProcessName()) + .processInstanceTenantId(TenantContextHolder.getTenantId().toString()) + .list(); + if (CollUtil.isNotEmpty(processInstanceList)) { + taskQuery.processInstanceIdIn( + processInstanceList.stream().map(ProcessInstance::getProcessInstanceId).toList()); + } } - Set 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 processInstanceIdSet = taskDtoList.stream() .map(TaskDto::getProcessInstanceId) .collect(Collectors.toSet()); @@ -103,14 +200,12 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService { .in(ProcessInstanceRecord::getProcessInstanceId, processInstanceIdSet) .list(); - // 发起人 // 发起人 List collect = processInstanceRecordList.stream().map(ProcessInstanceRecord::getUserId).toList(); List startUserList = userService.getUserById(collect).getData(); - Page pageResultDto = r.getData(); - List taskDtoList = new ArrayList<>(); - for (TaskDto record : r.getData().getRecords()) { + List 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 + *

+ * 查询当前用户已经完成的任务列表,包括: 1. 用户已审批的任务 2. 补充流程实例信息、发起人信息等 + *

+ * @param taskQueryParamDto 查询参数,支持分页和时间范围过滤 + * @return R 包含分页后的已办任务列表 */ @Override public R queryMineEndTask(TaskQueryParamDto taskQueryParamDto) { taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString()); - R> r = flowEngineService.queryCompletedTask(taskQueryParamDto); - Page 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 list = activityInstanceQuery + .listPage((taskQueryParamDto.getCurrent() - 1) * taskQueryParamDto.getSize(), taskQueryParamDto.getSize()); + + long count = activityInstanceQuery.count(); + List 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 pageResultDto = new Page<>(); + pageResultDto.setTotal(count); + pageResultDto.setRecords(taskDtoList); + List records = pageResultDto.getRecords(); if (CollUtil.isEmpty(records)) { return R.ok(pageResultDto); @@ -168,7 +320,7 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService { List collect = processInstanceRecordList.stream().map(ProcessInstanceRecord::getUserId).toList(); List startUserList = userService.getUserById(collect).getData(); - List taskDtoList = new ArrayList<>(); + List 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 返回处理结果 + *

+ * 手动结束一个流程实例,通常用于: 1. 流程审批完成,正常结束 2. 流程被终止或取消 + * + * 该方法会更新流程实例记录的结束时间、状态和结束原因 + *

+ * @param processInstanceParamDto 流程实例参数,包含: - processInstanceId: 流程实例ID - paramMap: + * 可能包含approve_condition等结束原因 + * @return R 操作结果 */ @Override public R end(ProcessInstanceParamDto processInstanceParamDto) { + // 从参数中提取审批条件,用于判断流程结束原因(通过/拒绝) Optional 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 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 + * 查询我发起的流程 + *

+ * 查询当前用户发起的所有流程实例,支持: 1. 按状态过滤(进行中、已完成等) 2. 按时间范围过滤 3. 分页查询 + *

+ * @param taskQueryParamDto 查询条件,包含状态、时间范围、分页参数 + * @return R 包含分页后的流程实例列表 */ @Override public R queryMineStarted(TaskQueryParamDto taskQueryParamDto) { @@ -252,9 +417,12 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService { } /** - * 查询抄送给我的 - * @param taskQueryParamDto 查询参数 - * @return + * 查询抄送给我的流程 + *

+ * 查询抄送给当前用户的流程列表。抄送功能允许流程参与者将流程信息 通知给其他相关人员,被抄送人可以查看流程但不参与审批 + *

+ * @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 + *

+ * 将流程定义数据格式化为前端可展示的节点结构,主要功能: 1. 解析流程定义的JSON数据 2. 标记已完成的节点 3. 根据流程变量计算条件节点的显示状态 4. + * 处理节点权限和表单权限 + * + * 该方法用于流程图的可视化展示 + *

+ * @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> 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 variables; + List 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 variableMap = objectMapper.readValue(data, new TypeReference<>() { - }); - variableMap.putAll(paramMap); - paramMap.putAll(variableMap); + variables = runtimeService.getVariables(task.getExecutionId()); } else { - Map variableMap = r.getData(); - variableMap.putAll(paramMap); - paramMap.putAll(variableMap); + variables = taskService.getVariables(variableQueryParamDto.getTaskId(), keyList); } - + paramMap.putAll(variables); } Set completeNodeSet = new HashSet<>(); @@ -370,8 +536,14 @@ public class ProcessInstanceServiceImpl implements IProcessInstanceService { /** * 流程详情 - * @param processInstanceId - * @return + *

+ * 查询流程实例的详细信息,包括: 1. 流程定义信息(流程结构) 2. 表单项定义 3. 表单数据(用户填写的数据) 4. 表单权限配置 5. 流程状态信息 + * + * 该方法主要用于流程详情页面的数据展示 + *

+ * @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);