From ecdbfaca684c1077bb1a5db37330e0969270c501 Mon Sep 17 00:00:00 2001 From: PIGCLOUD Date: Tue, 3 Mar 2026 15:03:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(pigx-flow-biz):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E5=88=9B=E5=BB=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在创建流程时增加对空节点用户列表的检查,避免空指针异常 - 当 nodeUserList 为空时直接返回成功结果,跳过后续处理步骤 --- .../flow/service/impl/ProcessServiceImpl.java | 457 +++++++++--------- 1 file changed, 233 insertions(+), 224 deletions(-) diff --git a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java index 580031cb0..228742460 100644 --- a/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java +++ b/pigx-flow/pigx-flow-biz/src/main/java/com/pig4cloud/pigx/flow/service/impl/ProcessServiceImpl.java @@ -60,261 +60,270 @@ import java.util.Objects; @RequiredArgsConstructor public class ProcessServiceImpl extends ServiceImpl implements IProcessService { - private final IProcessStarterService processStarterService; + private final IProcessStarterService processStarterService; - private final RepositoryService repositoryService; + private final RepositoryService repositoryService; - private final ObjectMapper objectMapper; + private final ObjectMapper objectMapper; - private final Sequence flowSequence; + private final Sequence flowSequence; - private final FlowStatusEventPublisher flowStatusEventPublisher; + private final FlowStatusEventPublisher flowStatusEventPublisher; - /** - * 获取流程详细信息 - *

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

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

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

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

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

- * @param flowId 流程定义ID - * @return 流程视图对象,包含所有展示所需的信息 - */ - @SneakyThrows - private ProcessVO getProcessVO(String flowId) { + /** + * 获取流程视图对象 + *

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

+ * + * @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<>() { - }); + Process oaForms = getByFlowId(flowId); + String process = oaForms.getProcess(); + String formItems = oaForms.getFormItems(); + Node startNode = objectMapper.readValue(process, new TypeReference<>() { + }); - List selectUserNodeId = NodeUtil.selectUserNodeId(startNode); + List selectUserNodeId = NodeUtil.selectUserNodeId(startNode); - ProcessVO processVO = BeanUtil.copyProperties(oaForms, ProcessVO.class); - processVO.setSelectUserNodeId(selectUserNodeId); + 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); - } - } - } + 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; - } + processVO.setFormItems(formItemJsonArray.toStringPretty()); + processVO.setFormPerms(startNode.getFormPerms()); + return processVO; + } - /** - * 根据流程ID查询流程定义 - *

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

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

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

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

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

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

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

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

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

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

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

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

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

- * @param process 流程定义实体,包含流程的所有配置信息 - * @return 创建结果,成功返回流程ID,失败返回错误信息 - */ - @SneakyThrows - @Override - public R create(Process process) { - String flowId; - if (StrUtil.isNotBlank(process.getFlowId())) { - flowId = process.getFlowId(); - } - else { - flowId = "P" + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN) - + RandomUtil.randomString(5).toUpperCase(); - } + /** + * 创建新的流程定义 + *

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

+ * + * @param process 流程定义实体,包含流程的所有配置信息 + * @return 创建结果,成功返回流程ID,失败返回错误信息 + */ + @SneakyThrows + @Override + public R create(Process process) { + String flowId; + if (StrUtil.isNotBlank(process.getFlowId())) { + flowId = process.getFlowId(); + } else { + flowId = "P" + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN) + + RandomUtil.randomString(5).toUpperCase(); + } - BpmnModel bpmnModel = ModelUtil.buildBpmnModel(objectMapper.readValue(process.getProcess(), Node.class), - process.getName(), flowId); - { - byte[] bpmnBytess = new BpmnXMLConverter().convertToXML(bpmnModel); - String filename = "/tmp/flowable-deployment/" + flowId + ".bpmn20.xml"; - log.debug("部署时的模型文件:{}", filename); - FileUtil.writeBytes(bpmnBytess, filename); - } + 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); + Process oldProcess = this.getByFlowId(flowId); - Deployment deployment = repositoryService.createDeployment() - .addBpmnModel(StrUtil.format("{}.bpmn20.xml", "pig"), bpmnModel) - .tenantId(TenantContextHolder.getTenantId().toString()) - .deploy(); + Deployment deployment = repositoryService.createDeployment() + .addBpmnModel(StrUtil.format("{}.bpmn20.xml", "pig"), bpmnModel) + .tenantId(TenantContextHolder.getTenantId().toString()) + .deploy(); - NodeUser nodeUser = objectMapper.readValue(process.getAdminList(), new TypeReference>() { - }).get(0); + NodeUser nodeUser = objectMapper.readValue(process.getAdminList(), new TypeReference>() { + }).get(0); - // 更新流程 - if (StrUtil.isNotBlank(process.getFlowId()) && Objects.nonNull(oldProcess)) { - this.hide(process.getFlowId()); - // 修改所有的管理员 - this.lambdaUpdate() - .set(Process::getAdminId, nodeUser.getId()) - .eq(Process::getUniqueId, oldProcess.getUniqueId()) - .update(new Process()); + // 更新流程 + 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); + Node startNode = objectMapper.readValue(process.getProcess(), Node.class); - List nodeUserList = startNode.getNodeUserList(); + List nodeUserList = startNode.getNodeUserList(); - StringBuilder stringBuilder = new StringBuilder(); - if (CollUtil.isNotEmpty(nodeUserList)) { - int index = 0; + 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; - } + for (NodeUser user : nodeUserList) { + if (index > 0) { + stringBuilder.append(","); + } + stringBuilder.append(user.getName()); + index++; + if (index > 5) { + break; + } - } - } - Process p = new Process(); + } + } + Process p = new Process(); - if (Objects.nonNull(oldProcess)) { - p = oldProcess; - } + 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); + 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); - // 删除原有发起范围 - processStarterService - .remove(Wrappers.lambdaQuery().eq(ProcessStarter::getProcessId, p.getId())); - for (NodeUser nodeUserDto : nodeUserList) { - ProcessStarter processStarter = new ProcessStarter(); - processStarter.setProcessId(p.getId()); - processStarter.setTypeId(nodeUserDto.getId()); - processStarter.setType(nodeUserDto.getType()); - processStarterService.save(processStarter); - } + // 删除原有发起范围 + processStarterService.remove(Wrappers.lambdaQuery().eq(ProcessStarter::getProcessId, p.getId())); - 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); + } - /** - * 更新流程状态或分组 - *

- * 支持三种操作类型: - 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(); - } + return R.ok(); + } - /** - * 验证流程ID是否已注册流程状态事件处理 - *

- * 通过 Redis Set 查询 flowId 是否已被任意微服务模块注册, - * 支持跨服务校验。 - *

- * - * @param flowId 流程定义ID - * @return 验证结果:true表示存在,false表示不存在 - */ - @Override - public R validateFlowId(String flowId) { - if (StrUtil.isBlank(flowId)) { - return R.ok(false); - } - return R.ok(flowStatusEventPublisher.isFlowIdRegistered(flowId)); - } + /** + * 更新流程状态或分组 + *

+ * 支持三种操作类型: - 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 validateFlowId(String flowId) { + if (StrUtil.isBlank(flowId)) { + return R.ok(false); + } + return R.ok(flowStatusEventPublisher.isFlowIdRegistered(flowId)); + } }