feat(flow): 支持业务表单,提供业务表单示例

This commit is contained in:
lengleng authored and pigcloud committed 2025-10-09 14:50:27 +08:00
1 parent 0d5326ff77
commit cd1854e498
17 files changed
+2185 -128

No files matched your search

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