diff --git a/db/0pigxx_boot.sql b/db/0pigxx_boot.sql index 48e8bc173..f97e2bcca 100644 --- a/db/0pigxx_boot.sql +++ b/db/0pigxx_boot.sql @@ -1701,11 +1701,13 @@ CREATE TABLE `process_node_data` ( `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标志', `create_time` datetime COMMENT '创建时间', `update_time` datetime COMMENT '更新时间', + `process_definition_id` varchar(64) DEFAULT NULL COMMENT '引擎流程定义ID,发布版本快照', `flow_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '流程id', `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '表单数据', `node_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '所属租户id', PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_node_definition` (`tenant_id`,`process_definition_id`,`node_id`), KEY `data_idx_id` (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1195 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='流程节点数据'; diff --git a/db/3pigxx_flow.sql b/db/3pigxx_flow.sql index 8c5648ee8..bc6fecafe 100644 --- a/db/3pigxx_flow.sql +++ b/db/3pigxx_flow.sql @@ -138,11 +138,13 @@ CREATE TABLE `process_node_data` ( `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标志', `create_time` datetime COMMENT '创建时间', `update_time` datetime COMMENT '更新时间', + `process_definition_id` varchar(64) DEFAULT NULL COMMENT '引擎流程定义ID,发布版本快照', `flow_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '流程id', `data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '表单数据', `node_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, `tenant_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '所属租户id', PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_node_definition` (`tenant_id`,`process_definition_id`,`node_id`), KEY `data_idx_id` (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1195 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='流程节点数据'; diff --git a/db/migration/20261013_flow_node_version.md b/db/migration/20261013_flow_node_version.md new file mode 100644 index 000000000..d4c5f2670 --- /dev/null +++ b/db/migration/20261013_flow_node_version.md @@ -0,0 +1,69 @@ +# 节点配置版本隔离升级 + +本次只隔离节点配置。业务 flowId 不变;流程图、表单定义的完整历史快照不在本次范围。任务、抄送和实例详情使用对应版本的字段权限,当前表单中未被该快照声明的字段默认隐藏;这不替代服务端的提交字段鉴权。已结束实例的详情依赖 Flowable 历史数据来确定版本,历史已清理时会明确报错,不能回退最新版。 + +## 上线顺序 + +1. 暂停流程发布、发起、任务处理和异步执行器,备份流程业务表及 Flowable 数据。不得新旧版本混跑:旧代码会按 flowId + nodeId 删除节点记录。 +2. 对已有库执行同目录 20261013_flow_node_version.sql;新库直接使用初始化脚本。升级 SQL 只执行一次,不删除旧配置。 +3. 核对存量定义与节点配置,建立明确的版本映射。至少覆盖所有运行中定义;如需要查询历史任务、抄送或使用旧定义,也必须覆盖对应历史定义。 +4. 将已核验配置写入对应 process_definition_id,包含 root 和所有业务节点。每个租户、定义、节点只能有一条快照。按该版本的已核验表单字段集合补齐各节点 formPerms:原来未配置/空值的字段按原有前端默认语义补 E,已有 H/R/E 保留;包含 children 中的嵌套字段及带 field 的容器。不能用当前表单的新增字段补旧版权限。无法恢复原字段集合时,此版本迁移尚未完成。 +5. 复查覆盖情况,部署全部新代码后恢复业务。无法确认映射时不要恢复受影响流程;运行期会明确报“缺少节点版本快照”,不会回退读取最新版。 + +## 只读盘点 + +在工作流所在数据库执行以下查询;若自定义了 Flowable 表名前缀,请先调整。 + + SELECT DISTINCT d.TENANT_ID_, d.KEY_, d.ID_, d.VERSION_, d.DEPLOYMENT_ID_ + FROM ACT_RU_EXECUTION e + JOIN ACT_RE_PROCDEF d ON d.ID_ = e.PROC_DEF_ID_ + ORDER BY d.TENANT_ID_, d.KEY_, d.VERSION_; + + SELECT p.tenant_id, p.flow_id, p.unique_id, d.ID_, d.VERSION_, p.process + FROM process p + JOIN ACT_RE_PROCDEF d + ON d.DEPLOYMENT_ID_ = p.unique_id + AND d.KEY_ = p.flow_id + AND d.TENANT_ID_ = CAST(p.tenant_id AS CHAR) + WHERE p.del_flag = '0'; + + SELECT id, tenant_id, flow_id, node_id, del_flag, process_definition_id, data + FROM process_node_data + ORDER BY tenant_id, flow_id, node_id, id; + +process.unique_id 存储部署 ID,可以定位当前模板的引擎定义。它只能帮助定位当前发布版本,不代表所有旧节点记录都属于该版本。旧节点表保留了逻辑删除数据,但没有发布版本标记;不能按行 ID 或时间顺序自动配对。 + +核对节点 JSON 与相应发布资料/备份,包括审批人、多实例模式、拒绝策略、字段权限、触发器和通知地址。配置中的敏感业务内容不要写入审查日志。 + +## 存量写入模板 + +下面只是单行映射示例,所有占位值都必须替换为已核验结果。先 SELECT 确认目标行和定义的租户、业务 key、版本一致,再在受控事务中执行。不要按 flowId 批量绑定最新版。 + + UPDATE process_node_data n + JOIN ACT_RE_PROCDEF d + ON d.ID_ = '<已核验的流程定义ID>' + AND d.KEY_ = n.flow_id + AND d.TENANT_ID_ = CAST(n.tenant_id AS CHAR) + SET n.process_definition_id = d.ID_, n.del_flag = '0' + WHERE n.id = <已核验的节点记录ID> + AND n.tenant_id = <目标租户ID> + AND n.process_definition_id IS NULL; + +一条旧记录如果需属于多个已验证版本,应分别插入不同 ID 的快照副本;不要反复改写同一行的版本 ID。若需从原始设计 JSON 重建节点,注意前端的 nodeName/childNode 与数据库 JSON 的 name/children 映射,并保存所有节点。无法恢复的历史版本应明确列为阻塞,不能用最新版替代。 + +迁移后检查所有运行实例是否有 root 快照: + + SELECT DISTINCT d.ID_, d.KEY_, d.TENANT_ID_ + FROM ACT_RU_EXECUTION e + JOIN ACT_RE_PROCDEF d ON d.ID_ = e.PROC_DEF_ID_ + LEFT JOIN process_node_data n + ON n.process_definition_id = d.ID_ + AND CAST(n.tenant_id AS CHAR) = d.TENANT_ID_ + AND n.node_id = 'root' AND n.del_flag = '0' + WHERE n.id IS NULL; + +该查询必须无遗漏;还需对照设计检查全部业务节点,不能仅用 root 存在代替完整性核验。新发布时程序先按当时的表单字段集合补齐默认 E 权限,再在部署事务中写快照,后续不得人工修改已发布数据。 + +## 回退 + +不能在产生新快照后直接恢复旧代码继续写流程。需要先冻结流程写入,再根据备份和期间新增实例单独制定回退方案。不要直接删除快照或批量清空版本列。 diff --git a/db/migration/20261013_flow_node_version.sql b/db/migration/20261013_flow_node_version.sql new file mode 100644 index 000000000..4951bf3ef --- /dev/null +++ b/db/migration/20261013_flow_node_version.sql @@ -0,0 +1,6 @@ +-- 升级已有数据库;全新数据库使用 0pigxx_boot.sql 或 3pigxx_flow.sql。 +-- 执行前停止流程写入并备份;本脚本不猜测存量节点与流程版本的对应关系。 +-- 必须完成同目录说明中的存量核对后再上线应用。 +ALTER TABLE process_node_data + ADD COLUMN process_definition_id varchar(64) DEFAULT NULL COMMENT '引擎流程定义ID,发布版本快照', + ADD UNIQUE KEY uk_node_definition (tenant_id, process_definition_id, node_id); diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordAssignUserParamDto.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordAssignUserParamDto.java index bab6406e5..6b4073dbf 100644 --- a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordAssignUserParamDto.java +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/ProcessNodeRecordAssignUserParamDto.java @@ -5,11 +5,9 @@ import lombok.Data; /** * 流程节点记录执行人参数数据传输对象 *
- * 该DTO用于记录流程节点的执行人信息。当任务分配给用户或用户处理任务时, - * 创建相应的执行人记录,用于追踪任务的处理过程和审批历史。 - * 包含任务执行的完整上下文信息。 + * 该DTO用于记录流程节点的执行人信息。当任务分配给用户或用户处理任务时, 创建相应的执行人记录,用于追踪任务的处理过程和审批历史。 包含任务执行的完整上下文信息。 *
- * + * * @author pigx */ @Data @@ -18,17 +16,18 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 流程定义ID *- * 流程模板的唯一标识。 - * 标识该记录所属的流程定义。 + * 流程模板的唯一标识。 标识该记录所属的流程定义。 *
*/ private String flowId; + /** 事件产生时绑定的引擎流程定义 ID。 */ + private String processDefinitionId; + /** * 流程实例ID *- * 运行中的流程实例唯一标识。 - * 用于关联到具体的流程实例。 + * 运行中的流程实例唯一标识。 用于关联到具体的流程实例。 *
*/ private String processInstanceId; @@ -36,8 +35,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 表单数据 *- * JSON格式的表单数据,包含流程实例的业务数据。 - * 记录任务执行时的表单内容,便于历史查看。 + * JSON格式的表单数据,包含流程实例的业务数据。 记录任务执行时的表单内容,便于历史查看。 *
*/ private String data; @@ -45,8 +43,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 节点本地数据 *- * 节点级别的局部数据,仅在当前节点范围内有效。 - * 可能包含节点特定的配置或临时数据。 + * 节点级别的局部数据,仅在当前节点范围内有效。 可能包含节点特定的配置或临时数据。 *
*/ private String localData; @@ -54,8 +51,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 节点ID *- * 流程节点的唯一标识。 - * 标识任务所在的节点。 + * 流程节点的唯一标识。 标识任务所在的节点。 *
*/ private String nodeId; @@ -63,8 +59,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 执行人用户ID *- * 被分配执行该任务的用户ID。 - * 记录谁负责处理这个任务。 + * 被分配执行该任务的用户ID。 记录谁负责处理这个任务。 *
*/ private Long userId; @@ -72,8 +67,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 执行ID *- * Flowable引擎的执行ID。 - * 用于关联流程引擎中的执行实例。 + * Flowable引擎的执行ID。 用于关联流程引擎中的执行实例。 *
*/ private String executionId; @@ -81,8 +75,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 任务ID *- * Flowable引擎生成的任务ID。 - * 用于唯一标识一个用户任务实例。 + * Flowable引擎生成的任务ID。 用于唯一标识一个用户任务实例。 *
*/ private String taskId; @@ -90,8 +83,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 审批意见 *- * 用户处理任务时填写的审批意见或备注。 - * 记录审批理由、建议等信息。 + * 用户处理任务时填写的审批意见或备注。 记录审批理由、建议等信息。 *
*/ private String approveDesc; @@ -99,8 +91,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 节点名称 *- * 流程节点的显示名称,如"部门经理审批"。 - * 用于在界面上展示,提高可读性。 + * 流程节点的显示名称,如"部门经理审批"。 用于在界面上展示,提高可读性。 *
*/ private String nodeName; @@ -108,11 +99,7 @@ public class ProcessNodeRecordAssignUserParamDto { /** * 任务类型 *- * 标识任务的类型: - * - APPROVAL: 审批任务 - * - COPY: 抄送任务 - * - NOTIFY: 通知任务 - * - CUSTOM: 自定义任务 + * 标识任务的类型: - APPROVAL: 审批任务 - COPY: 抄送任务 - NOTIFY: 通知任务 - CUSTOM: 自定义任务 *
*/ private String taskType; diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/TaskResultDto.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/TaskResultDto.java index f8a916f7d..173322976 100644 --- a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/TaskResultDto.java +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/dto/TaskResultDto.java @@ -7,9 +7,7 @@ import java.util.Map; /** * 任务结果数据传输对象 *- * 该DTO用于返回任务的详细信息和执行结果。 - * 包含任务的当前状态、节点配置、流程变量、委派状态等信息。 - * 主要用于任务详情展示、任务处理页面的数据渲染。 + * 该DTO用于返回任务的详细信息和执行结果。 包含任务的当前状态、节点配置、流程变量、委派状态等信息。 主要用于任务详情展示、任务处理页面的数据渲染。 *
* * @author pigx @@ -20,9 +18,7 @@ public class TaskResultDto { /** * 是否为当前任务 *- * 标识该任务是否为流程的当前活动任务。 - * true: 任务正在等待处理 - * false: 任务已完成或不是当前节点 + * 标识该任务是否为流程的当前活动任务。 true: 任务正在等待处理 false: 任务已完成或不是当前节点 *
*/ private Boolean currentTask; @@ -30,18 +26,18 @@ public class TaskResultDto { /** * 流程定义ID *- * 任务所属的流程定义唯一标识。 - * 用于获取流程模板信息。 + * 任务所属的流程定义唯一标识。 用于获取流程模板信息。 *
*/ private String flowId; + /** 任务实际绑定的引擎流程定义 ID。 */ + private String processDefinitionId; + /** * 任务节点配置 *- * 包含任务节点的完整配置信息。 - * 如节点类型、审批人设置、表单权限、条件配置等。 - * 用于渲染任务处理界面。 + * 包含任务节点的完整配置信息。 如节点类型、审批人设置、表单权限、条件配置等。 用于渲染任务处理界面。 *
*/ private Node taskNode; @@ -49,8 +45,7 @@ public class TaskResultDto { /** * 节点ID *- * 任务对应的流程节点ID。 - * 用于定位任务在流程中的位置。 + * 任务对应的流程节点ID。 用于定位任务在流程中的位置。 *
*/ private String nodeId; @@ -58,8 +53,7 @@ public class TaskResultDto { /** * 流程实例ID *- * 任务所属的流程实例唯一标识。 - * 用于关联到具体的流程实例。 + * 任务所属的流程实例唯一标识。 用于关联到具体的流程实例。 *
*/ private String processInstanceId; @@ -67,10 +61,7 @@ public class TaskResultDto { /** * 委派状态 *- * 标识任务的委派状态: - * - PENDING: 待处理,委派任务等待被委派人处理 - * - RESOLVED: 已解决,委派任务已完成并返回给委派人 - * - null: 非委派任务 + * 标识任务的委派状态: - PENDING: 待处理,委派任务等待被委派人处理 - RESOLVED: 已解决,委派任务已完成并返回给委派人 - null: 非委派任务 *
*/ private String delegationState; @@ -78,9 +69,7 @@ public class TaskResultDto { /** * 是否允许继续委派 *- * 控制任务是否可以被继续委派给其他人。 - * true: 允许当前处理人将任务委派给其他人 - * false: 不允许继续委派 + * 控制任务是否可以被继续委派给其他人。 true: 允许当前处理人将任务委派给其他人 false: 不允许继续委派 *
*/ private Boolean delegate; @@ -88,10 +77,7 @@ public class TaskResultDto { /** * 所有流程变量 *- * 包含流程实例的所有变量,包括: - * - 表单数据:用户填写的业务数据 - * - 系统变量:流程引擎设置的变量 - * - 控制变量:用于流程控制的变量 + * 包含流程实例的所有变量,包括: - 表单数据:用户填写的业务数据 - 系统变量:流程引擎设置的变量 - 控制变量:用于流程控制的变量 * 这些变量可用于任务处理和页面展示。 *
*/ diff --git a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/ProcessNodeData.java b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/ProcessNodeData.java index ef8a4bcd7..0927a2dee 100644 --- a/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/ProcessNodeData.java +++ b/pigx-flow/pigx-flow-api/src/main/java/com/pig4cloud/pigx/flow/entity/ProcessNodeData.java @@ -25,11 +25,16 @@ import java.time.LocalDateTime; @TableName("process_node_data") public class ProcessNodeData { + /** 已发布的引擎流程定义 ID;旧记录为空,不作为运行期快照。 */ + private String processDefinitionId; + + /** 所属租户。 */ + private Long tenantId; + /** * 流程实例ID *- * 关联到具体的流程实例,标识这些节点数据属于哪个流程实例。 - * 对应ProcessInstanceRecord表的flowId字段。 + * 关联到具体的流程实例,标识这些节点数据属于哪个流程实例。 对应ProcessInstanceRecord表的flowId字段。 *
*/ @TableField("flow_id") @@ -38,8 +43,7 @@ public class ProcessNodeData { /** * 节点表单数据 *- * 以JSON格式存储的节点表单数据,包含用户在该节点填写的所有表单字段值。 - * 数据结构根据节点配置的表单项动态变化,支持文本、数字、日期、选择等多种表单类型。 + * 以JSON格式存储的节点表单数据,包含用户在该节点填写的所有表单字段值。 数据结构根据节点配置的表单项动态变化,支持文本、数字、日期、选择等多种表单类型。 * 例如:{"amount": 5000, "reason": "采购办公用品", "approver": "user123"} *
*/ @@ -49,9 +53,7 @@ public class ProcessNodeData { /** * 节点ID *- * 流程定义中的节点标识符,用于关联到具体的流程节点。 - * 通过此ID可以确定数据属于流程的哪个环节,如发起节点、审批节点、抄送节点等。 - * 与Node对象中的id字段对应。 + * 流程定义中的节点标识符,用于关联到具体的流程节点。 通过此ID可以确定数据属于流程的哪个环节,如发起节点、审批节点、抄送节点等。 与Node对象中的id字段对应。 *
*/ @TableField("node_id") @@ -69,8 +71,7 @@ public class ProcessNodeData { /** * 逻辑删除标记 *- * 用于软删除功能,删除时不会真正从数据库中移除记录,而是标记为已删除。 - * 0-正常,1-已删除。保留历史数据便于审计和数据恢复。 + * 用于软删除功能,删除时不会真正从数据库中移除记录,而是标记为已删除。 0-正常,1-已删除。保留历史数据便于审计和数据恢复。 *
*/ @TableLogic @@ -80,8 +81,7 @@ public class ProcessNodeData { /** * 创建时间 *- * 记录节点数据的创建时间,通常是用户提交表单的时间。 - * 自动填充,用于数据审计和流程追踪。 + * 记录节点数据的创建时间,通常是用户提交表单的时间。 自动填充,用于数据审计和流程追踪。 *
*/ @TableField(fill = FieldFill.INSERT) @@ -90,8 +90,7 @@ public class ProcessNodeData { /** * 更新时间 *- * 记录节点数据的最后更新时间,在创建和更新时自动填充。 - * 用于跟踪数据的修改历史,如表单数据的补充或修正。 + * 记录节点数据的最后更新时间,在创建和更新时自动填充。 用于跟踪数据的修改历史,如表单数据的补充或修正。 *
*/ @TableField(fill = FieldFill.INSERT_UPDATE) diff --git a/pigx-flow/pigx-flow-biz/pom.xml b/pigx-flow/pigx-flow-biz/pom.xml index ba2e6a6e1..5fbd9da88 100644 --- a/pigx-flow/pigx-flow-biz/pom.xml +++ b/pigx-flow/pigx-flow-biz/pom.xml @@ -117,6 +117,11 @@- * 该服务负责管理流程定义中每个节点的配置数据,包括节点的属性设置、审批人配置、 - * 表单权限、条件表达式等。这些数据是流程引擎执行时的重要依据,决定了流程节点 - * 的行为方式。服务提供了节点数据的保存和查询功能,支持流程的动态配置。 + * 该服务负责管理流程定义中每个节点的配置数据,包括节点的属性设置、审批人配置、 表单权限、条件表达式等。这些数据是流程引擎执行时的重要依据,决定了流程节点 + * 的行为方式。节点配置随发布保存为不可变快照;设计态可查询最新版,运行期必须指定引擎定义 ID。 *
* * @author Vincent @@ -20,43 +19,41 @@ import com.pig4cloud.pigx.flow.entity.ProcessNodeData; public interface IProcessNodeDataService extends IService- * 保存流程节点的完整配置信息,包括节点基本属性、审批人设置、表单字段权限、 - * 条件配置等。如果节点数据已存在则更新,不存在则新增。保存时会进行数据 - * 验证,确保配置的合法性和完整性。该接口通常在流程设计时调用。 - *
- * - * @param processNodeDataDto 节点数据传输对象,包含: - * - flowId: 流程定义ID - * - nodeId: 节点ID - * - nodeName: 节点名称 - * - nodeType: 节点类型(审批/抄送/条件等) - * - nodeUser: 节点处理人配置 - * - formPermission: 表单字段权限配置 - * - conditions: 条件表达式(条件节点使用) - * - properties: 其他扩展属性 - * @return R 响应结果,成功返回保存后的节点数据ID,失败返回错误信息 + * 保留旧接口契约但拒绝直接修改节点配置;修改须随流程重新发布。 + * @param processNodeDataDto 旧接口的节点数据,允许为 null;不读取也不持久化 + * @return 说明需重新发布的失败结果,不为 null */ R saveNodeData(ProcessNodeDataDto processNodeDataDto); /** - * 获取指定节点的配置数据 - *- * 根据流程ID和节点ID查询节点的完整配置信息。返回的数据包括节点的所有 - * 配置项,可直接用于流程引擎的执行或流程设计器的回显。查询时会进行 - * 权限校验,确保调用者有权限访问该流程的节点数据。 - *
- * - * @param flowId 流程定义ID,标识节点所属的流程 - * @param nodeId 节点ID,流程中节点的唯一标识 - * @return R- * 该类负责处理流程抄送相关的业务逻辑,主要功能包括: - * 1. 管理流程抄送记录的增删改查 - * 2. 查询抄送详情,包括流程表单、表单数据和权限信息 - * 3. 为被抄送人提供查看流程执行情况的能力 - * + * 该类负责处理流程抄送相关的业务逻辑,主要功能包括: 1. 管理流程抄送记录的增删改查 2. 查询抄送详情,包括流程表单、表单数据和权限信息 3. + * 为被抄送人提供查看流程执行情况的能力 + * * 抄送功能允许流程在执行过程中通知相关人员,被抄送人可以查看但不参与流程审批 *
* @@ -35,56 +36,71 @@ import java.util.Map; @RequiredArgsConstructor public class ProcessCopyServiceImpl extends ServiceImpl- * 根据抄送记录ID查询完整的抄送详情,包括: - * 1. 流程定义信息(process) - * 2. 表单项定义(formItems) - * 3. 表单数据(formData)- 流程实例的实际填写数据 - * 4. 表单权限(formPerms)- 基于当前节点的表单字段权限设置 - * - * 该方法主要用于抄送人查看被抄送的流程详情 - *
- * - * @param id 抄送记录ID - * @return R 包含流程详情的响应对象,包括process、formItems、formData、formPerms - * @throws Exception 当解析JSON数据失败时抛出异常 - */ - @SneakyThrows - @Override - public R querySingleDetail(long id) { - // 获取抄送记录 - ProcessCopy processCopy = this.getById(id); - String flowId = processCopy.getFlowId(); - - // 查询流程定义 - Process oaForms = processService.getByFlowId(flowId); - if (oaForms == null) { - return R.failed("流程不存在"); - } - - // 获取表单数据和节点信息 - String formData = processCopy.getFormData(); - String nodeId = processCopy.getNodeId(); + private final HistoryService historyService; - // 获取节点的表单权限配置 - Node node = nodeDataService.getNodeData(flowId, nodeId).getData(); - Map+ * 根据抄送记录ID查询完整的抄送详情,包括: 1. 流程定义信息(process) 2. 表单项定义(formItems) 3. 表单数据(formData)- + * 流程实例的实际填写数据 4. 表单权限(formPerms)- 基于当前节点的表单字段权限设置 + * + * 该方法主要用于抄送人查看被抄送的流程详情 + *
+ * @param id 抄送记录ID + * @return R 包含流程详情的响应对象,包括process、formItems、formData、formPerms + * @throws Exception 当解析JSON数据失败时抛出异常 + */ + @SneakyThrows + @Override + public R querySingleDetail(long id) { + // 获取抄送记录 + ProcessCopy processCopy = this.getById(id); + String flowId = processCopy.getFlowId(); + + // 查询流程定义 + Process oaForms = processService.getByFlowId(flowId); + if (oaForms == null) { + return R.failed("流程不存在"); + } + + // 获取表单数据和节点信息 + String formData = processCopy.getFormData(); + String nodeId = processCopy.getNodeId(); + + // 获取节点的表单权限配置 + var running = runtimeService.createProcessInstanceQuery() + .processInstanceId(processCopy.getProcessInstanceId()) + .singleResult(); + String definitionId; + if (Objects.nonNull(running)) { + definitionId = running.getProcessDefinitionId(); + } + else { + var historic = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processCopy.getProcessInstanceId()) + .singleResult(); + if (Objects.isNull(historic)) { + return R.failed("流程实例历史不存在,无法确定节点配置版本"); + } + definitionId = historic.getProcessDefinitionId(); + } + Node node = nodeDataService.getPublishedNode(definitionId, nodeId).getData(); + + // 构建返回数据 + Dict set = Dict.create() + .set("process", oaForms.getProcess()) // 流程定义 + .set("formItems", oaForms.getFormItems()) // 表单项定义 + .set("formData", formData) // 表单数据 + .set("formPerms", NodeUtil.publishedFormPermissions(node, oaForms.getFormItems())); // 表单权限 + return R.ok(set); + } } diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java index f95a124a9..3e837c632 100644 --- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessInstanceServiceImpl.java @@ -66,638 +66,719 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class ProcessInstanceServiceImpl implements IProcessInstanceService { - private final RemoteUserService userService; - - private final TaskService taskService; - - private final RuntimeService runtimeService; - - private final HistoryService historyService; - - private final IProcessInstanceRecordService processInstanceRecordService; - - private final IProcessCopyService processCopyService; - - private final IProcessService processService; - - private final IProcessNodeRecordService processNodeRecordService; - - private final IProcessNodeRecordAssignUserService processNodeRecordAssignUserService; - - private final ObjectMapper objectMapper; - - /** - * 启动流程 - *- * 根据流程定义启动一个新的流程实例。该方法会: 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- * 查询分配给当前用户的待办任务列表,并补充以下信息: 1. 流程实例的详细信息(流程名称、分组等) 2. 流程发起人信息(姓名、头像等) 3. 流程启动时间 - *
- * - * @param taskQueryParamDto 查询参数,支持分页和条件过滤 - * @return R 包含分页后的待办任务列表 - */ - @Override - public R queryMineTask(TaskQueryParamDto taskQueryParamDto) { - taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString()); - - String assign = taskQueryParamDto.getAssign(); - - List- * 查询当前用户已经完成的任务列表,包括: 1. 用户已审批的任务 2. 补充流程实例信息、发起人信息等 - *
- * - * @param taskQueryParamDto 查询参数,支持分页和时间范围过滤 - * @return R 包含分页后的已办任务列表 - */ - @Override - public R queryMineEndTask(TaskQueryParamDto taskQueryParamDto) { - taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString()); - - 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- * 手动结束一个流程实例,通常用于: 1. 流程审批完成,正常结束 2. 流程被终止或取消 - *
- * 该方法会更新流程实例记录的结束时间、状态和结束原因 - *
- * - * @param processInstanceParamDto 流程实例参数,包含: - processInstanceId: 流程实例ID - paramMap: - * 可能包含approve_condition等结束原因 - * @return R 操作结果 - */ - @Override - public R end(ProcessInstanceParamDto processInstanceParamDto) { - - // 拒绝结束时会写入明确的结束原因;正常流转到结束节点时默认为通过 - Map- * 查询当前用户发起的所有流程实例,支持: 1. 按状态过滤(进行中、已完成等) 2. 按时间范围过滤 3. 分页查询 - *
- * - * @param taskQueryParamDto 查询条件,包含状态、时间范围、分页参数 - * @return R 包含分页后的流程实例列表 - */ - @Override - public R queryMineStarted(TaskQueryParamDto taskQueryParamDto) { - - long userId = SecurityUtils.getUser().getId(); - - Page- * 查询抄送给当前用户的流程列表。抄送功能允许流程参与者将流程信息 通知给其他相关人员,被抄送人可以查看流程但不参与审批 - *
- * - * @param taskQueryParamDto 查询参数,支持时间范围过滤和分页 - * @return R 包含分页后的抄送记录,包括发起人信息 - */ - @Override - public R queryMineCC(TaskQueryParamDto taskQueryParamDto) { - - long userId = SecurityUtils.getUser().getId(); - - Page- * 将流程定义数据格式化为前端可展示的节点结构,主要功能: 1. 解析流程定义的JSON数据 2. 标记已完成的节点 3. 根据流程变量计算条件节点的显示状态 4. - * 处理节点权限和表单权限 - *
- * 该方法用于流程图的可视化展示 - *
- * - * @param nodeFormatParamVo 格式化参数,包含: - flowId: 流程定义ID - processInstanceId: 流程实例ID - - * taskId: 任务ID(可选) - paramMap: 流程变量 - * @return R 包含格式化后的节点列表 - */ - @SneakyThrows - @Override - public R formatStartNodeShow(NodeFormatParamVo nodeFormatParamVo) { - String flowId = nodeFormatParamVo.getFlowId(); - String processInstanceId = nodeFormatParamVo.getProcessInstanceId(); - if (StrUtil.isAllBlank(flowId, processInstanceId)) { - return R.ok(new ArrayList<>()); - } - - if (StrUtil.isBlankIfStr(flowId) && StrUtil.isNotBlank(processInstanceId)) { - ProcessInstanceRecord processInstanceRecord = processInstanceRecordService.lambdaQuery().eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceId).one(); - flowId = processInstanceRecord.getFlowId(); - - } - Map- * 查询流程实例的详细信息,包括: 1. 流程定义信息(流程结构) 2. 表单项定义 3. 表单数据(用户填写的数据) 4. 表单权限配置 5. 流程状态信息 - *
- * 该方法主要用于流程详情页面的数据展示 - *
- * - * @param processInstanceId 流程实例ID - * @return R 包含流程详情的Dict对象 - * @throws Exception 当解析JSON数据失败时抛出异常 - */ - @SneakyThrows - @Override - public R detail(String processInstanceId) { - ProcessInstanceRecord processInstanceRecord = processInstanceRecordService.lambdaQuery().eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceId).one(); - - Process oaForms = processService.getByFlowId(processInstanceRecord.getFlowId()); - if (oaForms == null) { - return R.failed("流程不存在"); - } - - // 发起人变量数据 - String formData = processInstanceRecord.getFormData(); - String process = oaForms.getProcess(); - - Node nodeDto = objectMapper.readValue(process, Node.class); - Map+ * 根据流程定义启动一个新的流程实例。该方法会: 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+ * 查询分配给当前用户的待办任务列表,并补充以下信息: 1. 流程实例的详细信息(流程名称、分组等) 2. 流程发起人信息(姓名、头像等) 3. 流程启动时间 + *
+ * @param taskQueryParamDto 查询参数,支持分页和条件过滤 + * @return R 包含分页后的待办任务列表 + */ + @Override + public R queryMineTask(TaskQueryParamDto taskQueryParamDto) { + taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString()); + + String assign = taskQueryParamDto.getAssign(); + + List+ * 查询当前用户已经完成的任务列表,包括: 1. 用户已审批的任务 2. 补充流程实例信息、发起人信息等 + *
+ * @param taskQueryParamDto 查询参数,支持分页和时间范围过滤 + * @return R 包含分页后的已办任务列表 + */ + @Override + public R queryMineEndTask(TaskQueryParamDto taskQueryParamDto) { + taskQueryParamDto.setAssign(SecurityUtils.getUser().getId().toString()); + + 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+ * 手动结束一个流程实例,通常用于: 1. 流程审批完成,正常结束 2. 流程被终止或取消 + *
+ * 该方法会更新流程实例记录的结束时间、状态和结束原因 + *
+ * @param processInstanceParamDto 流程实例参数,包含: - processInstanceId: 流程实例ID - paramMap: + * 可能包含approve_condition等结束原因 + * @return R 操作结果 + */ + @Override + public R end(ProcessInstanceParamDto processInstanceParamDto) { + + // 拒绝结束时会写入明确的结束原因;正常流转到结束节点时默认为通过 + Map+ * 查询当前用户发起的所有流程实例,支持: 1. 按状态过滤(进行中、已完成等) 2. 按时间范围过滤 3. 分页查询 + *
+ * @param taskQueryParamDto 查询条件,包含状态、时间范围、分页参数 + * @return R 包含分页后的流程实例列表 + */ + @Override + public R queryMineStarted(TaskQueryParamDto taskQueryParamDto) { + + long userId = SecurityUtils.getUser().getId(); + + Page+ * 查询抄送给当前用户的流程列表。抄送功能允许流程参与者将流程信息 通知给其他相关人员,被抄送人可以查看流程但不参与审批 + *
+ * @param taskQueryParamDto 查询参数,支持时间范围过滤和分页 + * @return R 包含分页后的抄送记录,包括发起人信息 + */ + @Override + public R queryMineCC(TaskQueryParamDto taskQueryParamDto) { + + long userId = SecurityUtils.getUser().getId(); + + Page+ * 将流程定义数据格式化为前端可展示的节点结构,主要功能: 1. 解析流程定义的JSON数据 2. 标记已完成的节点 3. 根据流程变量计算条件节点的显示状态 4. + * 处理节点权限和表单权限 + *
+ * 该方法用于流程图的可视化展示 + *
+ * @param nodeFormatParamVo 格式化参数,包含: - flowId: 流程定义ID - processInstanceId: 流程实例ID - + * taskId: 任务ID(可选) - paramMap: 流程变量 + * @return R 包含格式化后的节点列表 + */ + @SneakyThrows + @Override + public R formatStartNodeShow(NodeFormatParamVo nodeFormatParamVo) { + String flowId = nodeFormatParamVo.getFlowId(); + String processInstanceId = nodeFormatParamVo.getProcessInstanceId(); + if (StrUtil.isAllBlank(flowId, processInstanceId)) { + return R.ok(new ArrayList<>()); + } + + if (StrUtil.isBlankIfStr(flowId) && StrUtil.isNotBlank(processInstanceId)) { + ProcessInstanceRecord processInstanceRecord = processInstanceRecordService.lambdaQuery() + .eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceId) + .one(); + flowId = processInstanceRecord.getFlowId(); + + } + Map+ * 查询流程实例的详细信息,包括: 1. 流程定义信息(流程结构) 2. 表单项定义 3. 表单数据(用户填写的数据) 4. 表单权限配置 5. 流程状态信息 + *
+ * 该方法主要用于流程详情页面的数据展示 + *
+ * @param processInstanceId 流程实例ID + * @return R 包含流程详情的Dict对象 + * @throws Exception 当解析JSON数据失败时抛出异常 + */ + @SneakyThrows + @Override + public R detail(String processInstanceId) { + ProcessInstanceRecord processInstanceRecord = processInstanceRecordService.lambdaQuery() + .eq(ProcessInstanceRecord::getProcessInstanceId, processInstanceId) + .one(); + + Process oaForms = processService.getByFlowId(processInstanceRecord.getFlowId()); + if (oaForms == null) { + return R.failed("流程不存在"); + } + + // 发起人变量数据 + String formData = processInstanceRecord.getFormData(); + var running = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); + String definitionId; + if (Objects.nonNull(running)) { + definitionId = running.getProcessDefinitionId(); + } + else { + var historic = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId) + .singleResult(); + if (Objects.isNull(historic)) { + return R.failed("流程实例历史不存在,无法确定节点配置版本"); + } + definitionId = historic.getProcessDefinitionId(); + } + Node nodeDto = nodeDataService.getPublishedNode(definitionId, "root").getData(); + Map- * 该类负责管理流程节点的详细数据,主要功能包括: 1. 保存流程节点的配置数据(审批人、表单权限、条件等) 2. 查询指定节点的详细配置信息 3. - * 为流程执行提供节点配置数据支持 - * - * 节点数据以JSON格式存储,包含节点类型、审批人配置、表单权限等信息 - *
- * - * @author Vincent - * @since 2023-05-07 - */ +/** 已发布节点配置按引擎定义 ID 隔离,不允许原地修改。 */ @Service +@RequiredArgsConstructor public class ProcessNodeDataServiceImpl extends ServiceImpl- * 将流程节点的配置信息保存到数据库。节点数据包括: - 节点类型(审批、抄送、条件等) - 审批人配置 - 表单权限设置 - 条件表达式等 - *
- * @param processNodeDataDto 流程节点数据DTO,包含flowId、nodeId和data(JSON格式的节点配置) - * @return R 保存结果 + * {@inheritDoc} */ @Override public R saveNodeData(ProcessNodeDataDto processNodeDataDto) { - ProcessNodeData processNodeData = BeanUtil.copyProperties(processNodeDataDto, ProcessNodeData.class); - baseMapper.delete(Wrappers.- * 根据流程ID和节点ID查询节点的详细配置信息。 节点数据存储为JSON格式,查询后会自动转换为Node对象 - *
- * @param flowId 流程ID - * @param nodeId 节点ID - * @return R- * 根据流程ID获取完整的流程配置信息,包括: - 流程基本信息 - 表单配置(自动生成序列号字段的值) - 需要发起人选择执行人的节点列表 - 表单权限配置 - *
- * - * @param flowId 流程定义ID - * @return 包含流程详细信息的响应对象 - */ - @Override - public R- * 内部方法,用于构建流程的视图对象。主要处理: - 解析流程节点结构 - 查找需要发起人选择的节点 - 处理序列号类型的表单字段,自动生成下一个序号 - - * 提取起始节点的表单权限配置 - *
- * - * @param flowId 流程定义ID - * @return 流程视图对象,包含所有展示所需的信息 - */ - @SneakyThrows - private ProcessVO getProcessVO(String flowId) { + /** + * 获取流程详细信息 + *+ * 根据流程ID获取完整的流程配置信息,包括: - 流程基本信息 - 表单配置(自动生成序列号字段的值) - 需要发起人选择执行人的节点列表 - 表单权限配置 + *
+ * @param flowId 流程定义ID + * @return 包含流程详细信息的响应对象 + */ + @Override + public R+ * 内部方法,用于构建流程的视图对象。主要处理: - 解析流程节点结构 - 查找需要发起人选择的节点 - 处理序列号类型的表单字段,自动生成下一个序号 - + * 提取起始节点的表单权限配置 + *
+ * @param flowId 流程定义ID + * @return 流程视图对象,包含所有展示所需的信息 + */ + @SneakyThrows + private ProcessVO getProcessVO(String flowId) { - List- * 通过流程的唯一标识符查询流程定义实体。 注意:一个流程可能有多个版本,此方法返回当前有效版本。 - *
- * - * @param flowId 流程定义ID - * @return 流程定义实体,如果不存在返回null - */ - @Override - public Process getByFlowId(String flowId) { - return this.lambdaQuery().eq(Process::getFlowId, flowId).one(); - } + processVO.setFormItems(formItemJsonArray.toStringPretty()); + processVO.setFormPerms(startNode.getFormPerms()); + return processVO; + } - /** - * 根据流程ID更新流程定义 - *- * 更新流程的配置信息,包括表单、节点、权限等。 更新不会影响已经运行的流程实例。 - *
- * - * @param process 包含更新信息的流程实体 - */ - @Override - public void updateByFlowId(Process process) { - this.lambdaUpdate().eq(Process::getFlowId, process.getFlowId()).update(process); - } + /** + * 根据流程ID查询流程定义 + *+ * 通过流程的唯一标识符查询流程定义实体。 注意:一个流程可能有多个版本,此方法返回当前有效版本。 + *
+ * @param flowId 流程定义ID + * @return 流程定义实体,如果不存在返回null + */ + @Override + public Process getByFlowId(String flowId) { + return this.lambdaQuery().eq(Process::getFlowId, flowId).one(); + } - /** - * 隐藏流程定义 - *- * 将流程标记为隐藏状态,隐藏的流程: - 不在流程列表中显示 - 不能创建新的流程实例 - 已有的流程实例可以继续执行 - *
- * - * @param flowId 要隐藏的流程ID - */ - @Override - public void hide(String flowId) { - this.lambdaUpdate() - .set(Process::getHidden, YesNoEnum.YES.getCode()) - .eq(Process::getFlowId, flowId) - .update(new Process()); - } + /** + * 根据流程ID更新流程定义 + *+ * 更新流程的配置信息,包括表单、节点、权限等。 更新不会影响已经运行的流程实例。 + *
+ * @param process 包含更新信息的流程实体 + */ + @Override + public void updateByFlowId(Process process) { + this.lambdaUpdate().eq(Process::getFlowId, process.getFlowId()).update(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(); - } + /** + * 隐藏流程定义 + *+ * 将流程标记为隐藏状态,隐藏的流程: - 不在流程列表中显示 - 不能创建新的流程实例 - 已有的流程实例可以继续执行 + *
+ * @param flowId 要隐藏的流程ID + */ + @Override + public void hide(String flowId) { + this.lambdaUpdate() + .set(Process::getHidden, YesNoEnum.YES.getCode()) + .eq(Process::getFlowId, flowId) + .update(new Process()); + } - 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); - } + /** + * 创建新的流程定义 + *+ * 创建流程的完整流程: 1. 调用流程引擎创建BPMN模型 2. 如果是更新操作,隐藏旧版本 3. 保存流程定义到数据库 4. 设置流程发起人权限 5. + * 生成流程使用范围描述 + *
+ * @param process 流程定义实体,包含流程的所有配置信息 + * @return 创建结果,成功返回流程ID,失败返回错误信息 + */ + @SneakyThrows + @Override + @Transactional(rollbackFor = Exception.class) + 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(); + } - Process oldProcess = this.getByFlowId(flowId); + Node publishedRoot = objectMapper.readValue(process.getProcess(), Node.class); + NodeUtil.snapshotFormPermissions(publishedRoot, process.getFormItems()); + BpmnModel bpmnModel = ModelUtil.buildBpmnModel(publishedRoot, process.getName(), flowId); + { + byte[] bpmnBytess = new BpmnXMLConverter().convertToXML(bpmnModel); + String filename = "/tmp/flowable-deployment/" + flowId + ".bpmn20.xml"; + log.debug("部署时的模型文件:{}", filename); + FileUtil.writeBytes(bpmnBytess, filename); + } - Deployment deployment = repositoryService.createDeployment() - .addBpmnModel(StrUtil.format("{}.bpmn20.xml", "pig"), bpmnModel) - .tenantId(TenantContextHolder.getTenantId().toString()) - .deploy(); + Process oldProcess = this.getByFlowId(flowId); - NodeUser nodeUser = objectMapper.readValue(process.getAdminList(), new TypeReference- * 支持三种操作类型: - 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(); - } + if (Objects.isNull(nodeUserList)) { + return R.ok(); + } + for (NodeUser nodeUserDto : nodeUserList) { + ProcessStarter processStarter = new ProcessStarter(); + processStarter.setProcessId(p.getId()); + processStarter.setTypeId(nodeUserDto.getId()); + processStarter.setType(nodeUserDto.getType()); + processStarterService.save(processStarter); + } - /** - * 验证流程ID是否已注册流程状态事件处理 - *- * 通过 Redis Set 查询 flowId 是否已被任意微服务模块注册, - * 支持跨服务校验。 - *
- * - * @param flowId 流程定义ID - * @return 验证结果:true表示存在,false表示不存在 - */ - @Override - public R+ * 支持三种操作类型: - 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(); + } + + /** + * 验证流程ID是否已注册流程状态事件处理 + *+ * 通过 Redis Set 查询 flowId 是否已被任意微服务模块注册, 支持跨服务校验。 + *
+ * @param flowId 流程定义ID + * @return 验证结果:true表示存在,false表示不存在 + */ + @Override + public R- * 获取当前用户的任务统计数据,包括: 1. 待办任务数量 2. 已办任务数量 3. 我发起的流程数量 4. 抄送给我的数量 - *
- * - * @return R 包含IndexPageStatistics对象,含有各项统计数据 - */ - @Override - public R queryTaskData() { + /** + * 查询首页数据看板 + *+ * 获取当前用户的任务统计数据,包括: 1. 待办任务数量 2. 已办任务数量 3. 我发起的流程数量 4. 抄送给我的数量 + *
+ * @return R 包含IndexPageStatistics对象,含有各项统计数据 + */ + @Override + public R queryTaskData() { - TaskQuery taskQuery = taskService.createTaskQuery(); + TaskQuery taskQuery = taskService.createTaskQuery(); - // 待办数量 - long pendingNum = taskQuery.taskAssignee(String.valueOf(SecurityUtils.getUser().getId())) - .taskTenantId(TenantContextHolder.getTenantId().toString()) - .count(); - // 已完成任务 - HistoricActivityInstanceQuery historicActivityInstanceQuery = historyService - .createHistoricActivityInstanceQuery(); + // 待办数量 + 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(); + 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(); + 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); - } + // 获取抄送任务 + 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) { - // 默认使用主表单数据源 - return queryTask(taskId, view, true); - } + /** + * 查询任务 + *+ * 查询指定任务的详细信息,包括: 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) { + // 默认使用主表单数据源 + return queryTask(taskId, view, true); + } - /** - * 查询任务(支持选择表单数据源) - *- * 查询指定任务的详细信息,包括: 1. 任务的基本信息(节点名称、执行人等) 2. 流程表单定义和表单数据 3. 当前节点的表单权限配置 4. - * 任务是否可委托、是否是委托任务等状态 - *
- * 对于已完成的任务,可选择从主表单数据或节点快照读取表单数据 - *
- * - * @param taskId 任务ID - * @param view 是否是查看模式(true:只查看,false:可操作) - * @param useMainFormData 是否使用主表单数据(true=从process_instance_record.form_data读取最新数据,false=从节点快照读取) - * @return R 包含任务详情的Dict对象 - * @throws Exception 当解析JSON数据失败时抛出异常 - */ - @SneakyThrows - @Override - public R queryTask(String taskId, boolean view, boolean useMainFormData) { + /** + * 查询任务(支持选择表单数据源) + *+ * 查询指定任务的详细信息,包括: 1. 任务的基本信息(节点名称、执行人等) 2. 流程表单定义和表单数据 3. 当前节点的表单权限配置 4. + * 任务是否可委托、是否是委托任务等状态 + *
+ * 对于已完成的任务,可选择从主表单数据或节点快照读取表单数据 + *
+ * @param taskId 任务ID + * @param view 是否是查看模式(true:只查看,false:可操作) + * @param useMainFormData + * 是否使用主表单数据(true=从process_instance_record.form_data读取最新数据,false=从节点快照读取) + * @return R 包含任务详情的Dict对象 + * @throws Exception 当解析JSON数据失败时抛出异常 + */ + @SneakyThrows + @Override + public R queryTask(String taskId, boolean view, boolean useMainFormData) { - long userId = SecurityUtils.getUser().getId(); + long userId = SecurityUtils.getUser().getId(); - Optional