当前位置: 首页 > news >正文

Activiti7工作流实战:手把手教你实现审批驳回与打回功能(附完整代码)

Activiti7工作流实战:深度解析审批驳回与打回功能的工程化实现

当部门经理在OA系统中点击"驳回"按钮时,系统背后究竟发生了什么?这个看似简单的操作背后,隐藏着工作流引擎中复杂的流程转向逻辑。本文将带您深入Activiti7的流程控制内核,从业务场景出发,构建一套健壮的审批驳回体系。

1. 审批驳回的业务本质与技术实现路径

在传统纸质审批时代,领导只需用红笔在表单上划个叉并写上"重填"二字。但在数字化流程中,这种"打回"操作需要精确控制三个核心要素:

  1. 流程方向的重定向:动态修改BPMN节点的流向关系
  2. 任务执行人的重置:确保任务能正确回到指定处理人
  3. 流程上下文的保留:维持业务数据的完整性与一致性

以典型的请假流程为例,当流程走到"部门经理审批"节点时,驳回操作需要实现以下技术路径:

// 伪代码展示核心流程 public void rejectApproval(String taskId, String comment) { // 1. 获取当前任务及流程实例 Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult(); ProcessInstance instance = runtimeService.createProcessInstanceQuery() .processInstanceId(currentTask.getProcessInstanceId()).singleResult(); // 2. 定位目标回退节点 HistoricActivityInstance targetNode = findPreviousNode(instance.getId(), currentTask.getTaskDefinitionKey()); // 3. 动态修改流向 redirectFlow(currentTask, targetNode); // 4. 添加审批意见 taskService.addComment(taskId, instance.getId(), "REJECT", comment); // 5. 完成任务触发流转 taskService.complete(taskId); }

这种实现方式与简单回退到上一节点不同,它需要处理以下特殊场景:

场景类型常规处理方案潜在风险优化方案
并行网关分支直接回退上一节点可能导致流程实例分裂网关边界检测与合并处理
会签节点退回发起人会签进度丢失会签结果快照与重启机制
多级审批链逐级回退审批链断裂拓扑排序确定最近有效节点

2. 动态流程转向的工程实现

Activiti的流程转向本质上是修改BPMN模型的运行时表现。我们需要采用"搭桥-过河-拆桥"的策略:

  1. 保存当前状态:备份原始流向关系
  2. 建立临时路径:创建指向目标节点的序列流
  3. 执行转向操作:完成任务触发流转
  4. 恢复初始状态:还原原始流向关系

以下是带异常处理的完整实现:

public void redirectFlow(Task currentTask, HistoricActivityInstance targetNode) { RepositoryService repositoryService = processEngine.getRepositoryService(); BpmnModel bpmnModel = repositoryService.getBpmnModel(currentTask.getProcessDefinitionId()); try { // 获取当前节点元素 FlowNode currentFlowNode = (FlowNode) bpmnModel .getMainProcess() .getFlowElement(currentTask.getTaskDefinitionKey()); // 获取目标节点元素 FlowNode targetFlowNode = (FlowNode) bpmnModel .getMainProcess() .getFlowElement(targetNode.getActivityId()); // 保存原始流向 List<SequenceFlow> originalFlows = new ArrayList<>(currentFlowNode.getOutgoingFlows()); // 创建临时转向 SequenceFlow tempFlow = new SequenceFlow(); tempFlow.setId("TEMP_FLOW_" + UUID.randomUUID()); tempFlow.setSourceFlowElement(currentFlowNode); tempFlow.setTargetFlowElement(targetFlowNode); // 应用临时流向 currentFlowNode.getOutgoingFlows().clear(); currentFlowNode.getOutgoingFlows().add(tempFlow); // 执行转向 taskService.complete(currentTask.getId()); // 恢复原始流向 currentFlowNode.getOutgoingFlows().clear(); currentFlowNode.getOutgoingFlows().addAll(originalFlows); } catch (Exception e) { throw new WorkflowException("流程转向失败: " + e.getMessage()); } }

关键注意事项

必须确保在分布式环境下,流程转向操作是原子性的。建议在方法上添加@Transactional注解,或在异常处理中加入流程状态回滚逻辑。

3. 驳回与打回的决策模型

在实际业务中,我们需要根据不同的业务场景选择不同的回退策略。以下是两种典型模式的对比分析:

策略一:渐进式回退(单步驳回)

  • 适用场景:仅需局部修改的审批场景
  • 技术特点:
    • 回退到直接上游节点
    • 保留大部分审批痕迹
    • 实现相对简单
  • 业务影响:审批周期较短,但可能需多次驳回

策略二:激进式打回(退回起点)

  • 适用场景:需要彻底重填的场景
  • 技术特点:
    • 直接跳转至流程起始节点
    • 重置整个审批链
    • 需要处理历史数据冲突
  • 业务影响:审批周期重置,但确保数据完整性

决策矩阵示例:

考量维度权重渐进式回退激进式打回
用户体验30%85
实现复杂度20%73
数据一致性25%69
审批效率25%74

在实际项目中,我们通常会采用混合策略。以下是通过规则引擎实现智能路由的示例:

public String determineRejectType(ProcessInstance instance, Task currentTask) { // 获取业务表单数据 FormData formData = formService.getTaskFormData(currentTask.getId()); // 分析驳回原因 String comment = (String) taskService.getVariable(currentTask.getId(), "rejectComment"); RejectReason reason = rejectAnalyzer.analyze(comment); // 应用决策规则 if (reason.requiresFullRestart()) { return findStartNode(instance.getProcessDefinitionId()); } else if (reason.needsDepartmentReview()) { return "deptVerify"; } else { return findPreviousUserTask(currentTask); } }

4. 会签场景下的特殊处理

会签节点的驳回需要额外考虑多实例任务的特性。当会签环节需要驳回时,我们面临以下技术挑战:

  1. 实例计数器的重置:需要处理nrOfInstances等内置变量
  2. 并行任务的终止:要确保所有活动实例被正确清理
  3. 审批结果的保留:保留已完成的会签意见供后续参考

解决方案示例:

public void rejectCountersign(Task task, String targetNodeId) { // 1. 终止所有活动实例 List<Task> activeTasks = taskService.createTaskQuery() .processInstanceId(task.getProcessInstanceId()) .taskDefinitionKey(task.getTaskDefinitionKey()) .list(); activeTasks.forEach(t -> { taskService.addComment(t.getId(), task.getProcessInstanceId(), "ABORT", "会签驳回终止"); taskService.setVariableLocal(t.getId(), "forceComplete", true); taskService.complete(t.getId()); }); // 2. 跳转到目标节点 runtimeService.createChangeActivityStateBuilder() .processInstanceId(task.getProcessInstanceId()) .moveActivityIdTo(task.getTaskDefinitionKey(), targetNodeId) .changeState(); // 3. 重置会签变量 runtimeService.setVariable(task.getProcessInstanceId(), "countersignResults", new HashMap<>()); }

性能优化技巧

  • 对于大型会签(参与人>20),建议采用分批处理
  • 使用异步执行器处理实例终止操作
  • 考虑引入快照机制保存会签进度

5. 生产环境中的增强实践

在真实项目部署时,我们还需要考虑以下增强功能:

审批链可视化

// 前端展示驳回路径的示例代码 function renderRejectPath(processInstanceId) { axios.get(`/api/process/${processInstanceId}/reject-path`) .then(response => { const pathData = response.data; // 使用流程图库展示路径 bpmnViewer.highlightPath(pathData); }); }

驳回次数限制

// 驳回频率控制 @RejectLimit(maxCount=3, period=24*60*60*1000) public void rejectTask(String taskId) { // 驳回实现 }

智能填充建议

public class RejectHelper { public List<RejectSuggestion> analyzeForm(FormData formData) { // 使用规则引擎分析表单问题 return ruleEngine.check(formData.getFields()); } }

在大型ERP系统中实施时,我们通常会建立专门的驳回控制模块,包含以下组件:

com.example.workflow.reject ├── controller │ └── RejectController.java ├── service │ ├── RejectStrategyFactory.java │ ├── impl │ │ ├── SimpleRejectStrategy.java │ │ ├── CountersignRejectStrategy.java │ │ └── ComplexGatewayRejectStrategy.java ├── model │ ├── RejectCommand.java │ └── RejectResult.java └── aop └── RejectAuditAspect.java

这种架构设计使得驳回逻辑可以灵活扩展,同时保持核心流程的稳定性。在实际项目中,我们通过策略模式支持了17种不同的驳回场景,包括跨流程跳转、条件驳回等复杂需求。

http://www.jsqmd.com/news/887383/

相关文章:

  • 软阴影:那个让虚拟世界“温柔起来“的光影小秘密
  • Java 23 种设计模式:从踩坑到精通 | Singleton —— 你写的单例真的安全吗?
  • 避坑指南:Sentaurus与SILVACO TCAD仿真NPN三极管,结果为啥差了几十uA?
  • 2026年5月25日博客精选
  • 2026年Q2国内主流超声治疗仪品牌排行盘点:经颅磁疗仪/膝盖超声波治疗仪/超声波治疗器/超声波治疗理疗/便携超声波治疗仪/选择指南 - 优质品牌商家
  • Dify笔记-一种知识库文件上传失败报错500解决方法
  • 拼多多核销商品
  • 三、Tucker 分解:从高阶PCA到多维数据压缩的实战解析
  • 手把手教你用C++和倍福ADS库在Ubuntu上读写PLC变量(附完整CMake配置)
  • 【DeepSeek安全测试辅助实战指南】:20年攻防专家亲授3大高危漏洞自动识别技巧
  • 从AlphaFold到药物设计:一文读懂蛋白质结构预测如何改变生物医药
  • ARM AArch32通用定时器寄存器架构与CNTHPS_TVAL详解
  • 迁移中国服务器数据到美国服务器
  • 别再自己画库了!手把手教你用立创EDA+AD19快速搞定原理图库(以BMI088为例)
  • 传统理财追求存钱越多越好,编写适度消费理财程序,计算快乐消费阀值,拒绝盲目极致存钱。
  • 卡内基梅隆大学等机构联合提出:让AI在“温故“中“知新“
  • 自制射频功率计:基于AD8317芯片,成本43欧元实现1MHz-10GHz测量
  • LM Studio使用MTP的qwen3.6-27B-以7840hs的780M为例
  • LLM推理优化:内核融合与动态批处理技术解析
  • DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程简介
  • 传统健身追求高强度运动,编写低负担轻健身规划程序,主动碎片化微运动,颠覆苦练健身观念。
  • 从零打造复古辉光管腕表:高压驱动、低功耗与微型化设计实战
  • 从Wi-Fi到蓝牙:DPSK差分相移键控在实际无线通信系统中的应用与MATLAB验证
  • 新手村任务:成为一个架构师需要哪些装备?
  • 航空发动机分布式控制系统关键技术【附代码】
  • 数组专项(二):二维数组、滑动窗口思想
  • 番茄小说下载器终极指南:三步构建你的离线阅读自由王国
  • 告别道路预测老套路:用ParkPredict+模型思路,解决停车场里的‘鬼探头’难题
  • 告别光秃秃的地形:用Unity Terrain Tools打造风格化森林与草地的进阶技巧(附素材资源推荐)
  • Python算法基础篇之分治算法原理与实战