永康市网站建设牛商网朱依静
永康市网站建设,牛商网朱依静,国内最好的旅游网站,wordpress装饰公司主题1. 工作流引擎#xff1a;为什么你的业务需要它#xff1f;
想象一下#xff0c;你所在的公司#xff0c;一个员工想要请假。在“原始时代”#xff0c;他可能需要拿着一份纸质表格#xff0c;先找自己的组长签字#xff0c;再去找部门经理审批#xff0c;最后可能还要…1. 工作流引擎为什么你的业务需要它想象一下你所在的公司一个员工想要请假。在“原始时代”他可能需要拿着一份纸质表格先找自己的组长签字再去找部门经理审批最后可能还要送到人事部门备案。这张纸在办公室里“旅行”的路径就是一个典型的工作流。如果这个流程复杂一点比如超过5天的年假需要总监审批或者病假需要附上医院证明这个“旅行”规则就更复杂了。工作流引擎就是把这个“纸上旅行”的过程搬到计算机里让它自动化、可视化、可管理。它不关心业务具体是什么是请假、报销还是合同审批它只关心规则和状态当前这件事走到哪一步了下一步该谁处理处理完后该去哪它就像一个不知疲倦的交通指挥中心确保每一笔业务都能按照预设的路线图准确、高效地流转到终点。我刚开始接触工作流时也觉得它挺“重”的心想为了一个请假流程引入一套引擎是不是杀鸡用牛刀但后来在实战中踩过坑才明白当你的业务规则开始变化、参与角色增多、并且需要追踪每一个环节的耗时和责任人时如果没有一个引擎在背后支撑你的代码很快就会变成一堆难以维护的if...else地狱。今天审批链是 A-B-C明天可能就要变成 A-C-D或者 A 和 B 并行审批。每次变动都去硬编码开发累测试更累还容易出错。而像Activiti这样的工作流引擎就是把这种流转逻辑从业务代码中解耦出来。你可以用画图的方式他们叫流程设计器来定义流程引擎负责驱动执行。业务开发人员只需要关注“当这个审批任务到达张三时我该怎么展示表单”以及“李四点击同意后我该更新哪些业务数据” 至于怎么找到张三、同意后是去找王五还是赵六这些路由逻辑交给引擎就好。所以简单来说工作流引擎解决的核心问题是将业务过程中可变的路由逻辑和稳定的操作逻辑分离提升系统的可维护性、可扩展性和可视化程度。Activiti 就是 Java 世界里实现这个目标的一个非常流行、强大的开源工具箱。2. Activiti 的前世今生从 JBPM 到新时代说到 Activiti就不能不提它的“父亲”——JBPM。这有点像技术圈的一段“江湖往事”。JBPM 全称是 Java Business Process Management早在21世纪初就已经是开源工作流领域里的明星项目了。它在2004年被 JBoss 组织收养成为了 JBoss 企业级中间件套件中的重要一员名字也变成了 JBoss jBPM。jBPM 在版本4之前可以说是风光无限。它基于 Hibernate 作为持久层框架将流程定义、执行状态等都持久化到数据库里理念非常先进。但是技术路线之争在哪里都存在。在 jBPM4 发布后核心开发团队内部对未来发展方向产生了分歧。当时的项目领头人 Tom Baeyens 做了一个大胆的决定离开原团队另起炉灶。于是在2010年Activiti 5诞生了。你可以把它理解为 jBPM 的一个“精神续作”由原班核心人马打造但采用了全新的技术架构。最标志性的一个变化就是把持久层框架从 Hibernate 换成了MyBatis。这个改动在当时引起了不小的讨论。Hibernate 是一个全自动的 ORM 框架而 MyBatis 更偏向半自动的 SQL 映射。Activiti 团队认为对于工作流引擎这种需要高度优化和复杂查询的场景开发者对 SQL 的精确控制比全自动映射更重要。这个选择也让 Activiti 在性能调优上给了开发者更大的空间。从那时起Activiti 和 jBPM 就分道扬镳各自发展。Activiti 因为其轻量、易集成、文档丰富尤其是对 Spring 支持友好的特点迅速在社区积累了极高的人气成为了许多企业进行业务流程自动化的首选。后来Activiti 项目的主要贡献者又创办了公司推出了 Activiti Cloud 等更现代化的云原生版本但 Activiti 7 的核心引擎思想依然一脉相承。了解这段历史你就能明白为什么今天搜索 Java 工作流Activiti 的声量如此之大——它出身名门又针对新时代的开发习惯做了大胆革新。3. 核心组件拆解ProcessEngine 与七大服务当你把 Activiti 的 Jar 包引入项目后最核心、首先要接触的就是ProcessEngine流程引擎对象。你可以把它看作 Activiti 宇宙的“总控制器”或者“服务工厂”。它是线程安全的通常一个应用只需要一个实例。所有具体的功能都通过它来获取对应的服务接口。这就好比你去一家大型游乐场ProcessEngine是售票大厅从这里你可以拿到通往不同区域服务的“地图”。接下来我们看看由ProcessEngine产生的七大核心服务它们各自掌管着工作流生命周期的不同阶段RepositoryService流程“仓库”管理员这个服务负责一切静态资源的管理主要是流程定义Process Definition。你可以通过它来部署一个流程模型通常是一个.bpmn20.xml文件就像把一张新的电路设计图存档到图书馆。它也可以查询、删除已部署的流程定义。对应的数据库表都以ACT_RE_*开头RE 代表 Repository。我常用的操作就是repositoryService.createDeployment().addClasspathResource(请假流程.bpmn20.xml).deploy()一行代码就把流程定义发布上去了。RuntimeService流程“执行现场”指挥官流程定义部署好了还只是个蓝图。当某个员工发起一个请假申请时一个具体的流程实例Process Instance就诞生了。RuntimeService就是管理这些正在运行的实例的。它可以启动流程、删除流程、触发接收消息事件、设置流程变量等。它操作的数据是运行时、动态的存放在ACT_RU_*表里RU 代表 Runtime。这里有个关键点Activiti 为了追求运行时的高性能这些RU表只保存运行中的数据流程实例一旦结束相关记录就会被清除只把摘要信息转移到历史表。所以别担心你的运行表会无限膨胀。TaskService任务“分发与追踪”专员这是和业务开发人员打交道最多的服务。流程流转到某个用户任务User Task节点时就会生成一个待办任务Task。TaskService负责查询任务比如“查找张三的所有待办”、认领任务、完成任务、设置任务优先级、指派任务等。我们业务代码里最常见的片段就是taskService.complete(taskId, variables)表示处理人点击了“同意”或“驳回”任务完成流程引擎会根据连线条件驱动流程走向下一个节点。HistoryService流程“档案馆”馆长虽然运行时数据会被清理但完整的流程历史对于审计、报表和分析至关重要。HistoryService提供了查询历史流程实例、历史任务、历史活动、历史变量等功能。所有历史数据都存储在ACT_HI_*表里HI 代表 History。你可以通过它轻松查出“王五在去年三月份总共审批了多少条请假申请平均耗时多久”。IdentityService人员“组织架构”管家这个服务用于管理用户、组以及它们之间的关系。理论上你可以用它将 Activiti 和你公司现有的 LDAP 或组织架构系统集成。不过在实际项目中我发现很多团队并不会直接使用IdentityService来增删用户而是更倾向于“弱关联”。也就是说Activiti 任务表中的办理人字段ASSIGNEE_只存储我们业务系统的用户ID如zhangsan关于这个ID的详细信息姓名、部门等则由业务系统自己提供。这样耦合度更低。FormService动态“表单”构造器它用于处理流程中的动态表单。流程定义中可以关联表单属性FormService可以在流程启动时或任务完成时获取或提交这些表单数据。对于表单结构复杂、且需要与流程节点绑定的场景比较有用。但在前后端分离架构流行的今天很多项目选择自己开发前端表单只把表单数据作为流程变量Variable传递所以这个服务的使用频率见仁见智。ManagementService引擎“运维”工具包这个服务提供了一些运维和管理功能比如查询数据库表和表的元数据、执行自定义的 SQL 查询因为 Activiti 用的是 MyBatis所以支持原生 SQL 映射、管理作业Job如定时器事件等。它不像前面几个服务那样在核心业务流程中必用但在排查问题或做引擎健康检查时非常顺手。把这七个服务搞清楚你对 Activiti 能干什么、怎么干心里就有了一张清晰的地图。它们各司其职共同协作驱动着业务流程的运转。4. 数据库表结构透视引擎的数据骨架很多朋友一看到 Activiti 那二十多张数据库表就头疼其实它们非常有规律理解了命名规则和分组就很容易掌握。Activiti 的表名都以前缀ACT_开头后面跟着两个字母的分类标识然后是具体的表名。ACT_RE_(REpository - 存储)* 这是“静态资源库”。ACT_RE_DEPLOYMENT表记录你每次的部署操作每次deploy()都会生成一条记录。ACT_RE_PROCDEF表则存储了被部署的流程定义的具体信息包括流程定义的ID、KEY、版本、资源名称等。一个流程定义KEY相同每部署一次版本号就会自动加一引擎默认会使用版本最高的那一个。这里的数据一旦部署除非你删除部署否则不会改变。ACT_RU_(RUntime - 运行时)* 这是“运行指挥中心”。ACT_RU_EXECUTION表可能是最重要的运行时表之一它表示流程执行实例。一个流程实例Process Instance通常对应一条执行记录但如果是子流程或并行网关可能会有多条。ACT_RU_TASK表存储当前所有待办任务你的TaskService查询主要就来自这里。ACT_RU_VARIABLE表存储流程变量比如请假的days天数、reason理由这些业务数据。ACT_RU_IDENTITYLINK表存储任务与参与者办理人、候选人、组的关系。正如前面所说这些表的数据在流程实例结束后会被清除以保持高效。ACT_HI_(HIstory - 历史)* 这是“历史博物馆”。ACT_HI_PROCINST和ACT_HI_ACTINST分别记录历史流程实例和历史活动节点你流程走过的每一步在这里都有迹可循。ACT_HI_TASKINST记录所有已完成的任务。ACT_HI_VARINST记录历史变量。ACT_HI_DETAIL则记录一些更详细的变更信息。做报表、查流水、分析效率全靠这几张表。ACT_ID_(IDentity - 身份)* 这是“人员花名册”。ACT_ID_USER、ACT_ID_GROUP、ACT_ID_MEMBERSHIP分别存储用户、用户组和他们的关系。如果你决定用 Activiti 自带的身份体系就会用到它们。ACT_GE_(GEneral - 通用)* 这是“杂物间”。ACT_GE_BYTEARRAY表特别重要它是个“万能”的二进制存储表。你部署的流程定义XML文件、流程图中生成的图片、甚至是某些自定义的扩展资源都会以二进制形式存在这里。ACT_GE_PROPERTY表存储引擎本身的属性比如数据库模式的版本号。我刚开始学的时候喜欢在调试流程时直接打开数据库看看这些表的变化。比如启动一个流程实例看看ACT_RU_EXECUTION和ACT_RU_TASK多了什么记录完成一个任务观察这些记录如何从RU表消失又在HI表出现。这种直观的观察比读十遍文档都管用。理解表结构是你深度使用和排查 Activiti 问题的基础。5. 实战用一个请假流程串起所有概念光说不练假把式我们用一个最简单的请假流程把上面提到的概念和组件串起来。假设流程是员工提交请假申请 - 部门经理审批 - 结束。第一步定义流程使用 BPMN 2.0我们会画一个图或者写一个XML文件leave.bpmn20.xml。这个文件里定义了一个开始事件Start Event表示流程起点。一个用户任务User Task名为“提交申请”办理人Assignee是#{applicant}一个流程变量。另一个用户任务名为“经理审批”办理人是#{manager}。一个结束事件End Event表示流程终点。用顺序流Sequence Flow把这些节点连起来。第二步部署流程在Spring Boot项目里你可能有这样的代码Autowired private RepositoryService repositoryService; public void deployProcess() { Deployment deployment repositoryService.createDeployment() .addClasspathResource(processes/leave.bpmn20.xml) .name(员工请假流程) .deploy(); System.out.println(流程部署成功部署ID: deployment.getId()); }执行后流程定义就被存入ACT_RE_*系列表流程图图片会存入ACT_GE_BYTEARRAY。第三步启动流程实例员工张三工号zhangsan要请3天假他发起申请Autowired private RuntimeService runtimeService; Autowired private TaskService taskService; public void startLeave(String applicantUserId, String managerUserId) { // 设置流程变量 MapString, Object variables new HashMap(); variables.put(applicant, applicantUserId); // 申请人 variables.put(manager, managerUserId); // 审批经理 variables.put(days, 3); variables.put(reason, 回家探亲); // 使用流程定义的KEY来启动实例 ProcessInstance instance runtimeService.startProcessInstanceByKey(leaveProcess, variables); System.out.println(流程实例启动实例ID: instance.getId()); // 启动后第一个任务提交申请会自动完成吗不会它需要办理人“提交”。 // 但通常“提交申请”这个任务可能是个表单填写任务这里我们简化假设启动即视为提交。 // 所以我们可以直接查询当前应该到达的任务经理审批 Task task taskService.createTaskQuery() .processInstanceId(instance.getId()) .singleResult(); System.out.println(当前待办任务 task.getName() , 办理人 task.getAssignee()); }这时数据库里ACT_RU_EXECUTION、ACT_RU_TASK经理审批任务、ACT_RU_VARIABLE等表都会插入相应数据。第四步处理任务经理李四lisi登录系统查询自己的待办public ListTask getTasksForUser(String userId) { return taskService.createTaskQuery() .taskAssignee(userId) // 查找指派给该用户的任务 .orderByTaskCreateTime().desc() .list(); }他看到了“经理审批”任务点击“同意”public void approveTask(String taskId) { // 可以传递新的变量比如审批意见 MapString, Object taskVariables new HashMap(); taskVariables.put(approvalComment, 同意请合理安排工作。); taskService.complete(taskId, taskVariables); System.out.println(任务已完成流程继续。); }taskService.complete()是这个流程中最关键的调用之一。引擎会做以下事情完成当前任务ACT_RU_TASK中该任务记录删除ACT_HI_TASKINST新增记录。沿着出口顺序流离开“经理审批”节点。判断下一个节点是“结束事件”。到达结束事件流程实例结束。清理ACT_RU_*运行时表中该实例的所有数据。在ACT_HI_*历史表中完善该实例的历史记录。第五步查询历史流程结束后你想看看张三这次请假的完整记录Autowired private HistoryService historyService; public void getProcessHistory(String processInstanceId) { // 查询历史流程实例 HistoricProcessInstance historicInstance historyService.createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); System.out.println(流程定义ID: historicInstance.getProcessDefinitionId()); System.out.println(开始时间: historicInstance.getStartTime()); System.out.println(结束时间: historicInstance.getEndTime()); System.out.println(总耗时(毫秒): historicInstance.getDurationInMillis()); // 查询这个流程中所有的活动历史 ListHistoricActivityInstance activities historyService.createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricActivityInstanceStartTime().asc() .list(); for (HistoricActivityInstance activity : activities) { System.out.println(活动: activity.getActivityName() [ activity.getActivityType() ], 开始: activity.getStartTime() , 结束: activity.getEndTime()); } }通过这个简单的例子你应该能感受到我们的业务代码蓝色部分主要在和TaskService与RuntimeService打交道负责“喂数据”和“取任务”。而流程的走向、状态的变迁、历史的记录这些复杂逻辑红色部分全部由 Activiti 引擎默默完成了。这种职责分离正是工作流引擎的价值所在。6. 超越基础网关、变量与监听器掌握了基本流程后现实业务要复杂得多。“如果请假超过3天需要总监二次审批”这种条件分支怎么处理这就需要用到网关Gateway。排他网关Exclusive Gateway像是一个单选开关。我们可以在“经理审批”完成后接一个排他网关然后引出两条线一条线条件设为${days 3}直接通向结束事件另一条线条件设为${days 3}通向一个新的“总监审批”用户任务。引擎会在流转时自动计算条件选择一条且仅一条路径执行。并行网关Parallel Gateway则用于处理需要多人同时会签的场景。比如一个采购申请需要技术经理和财务经理同时审批。在并行网关之后分出两条线指向两个审批任务这两个任务会同时产生互不阻塞。只有当所有并行分支的任务都完成后才能在汇聚点继续向下流转。流程变量Process Variable是连接业务数据与流程路由的桥梁。我们之前用到的days、applicant都是流程变量。它们可以设置在流程实例级别runtimeService.setVariable也可以设置在任务级别在taskService.complete时传递。网关的条件表达式、任务办理人的动态指派如#{manager}都依赖于流程变量。变量的作用域也需要注意实例变量在整个实例内可见任务变量通常只在当前任务链中有效。监听器Listener是增强流程灵活性的利器。你可以在流程事件如实例启动、结束或任务事件如任务创建、完成上挂接监听器。比如我们可以在“经理审批”任务创建时触发一个监听器自动给经理发送一封邮件通知。监听器分为Java类监听器和表达式监听器用起来非常灵活。我在项目中经常用监听器来记录一些自定义的操作日志或者在某些关键节点完成后去回调业务系统更新状态。这些高级特性让 Activiti 从一个简单的流程驱动器变成了一个能够应对复杂业务逻辑的流程大脑。刚开始不必追求全部掌握先从排他网关和流程变量用起你会发现很多业务场景已经可以优雅地实现了。7. 集成与落地在Spring Boot项目中玩转Activiti现在主流的开发方式都是基于 Spring BootActiviti 与之集成可以说是无缝衔接。通过引入activiti-spring-boot-starter依赖Spring Boot 会自动为你配置好ProcessEngine及其各项服务数据源也会自动共用。但在实际项目落地时有几点我踩过的坑值得你注意第一用户体系集成。正如前面所说Activiti 自带的IdentityService往往不符合企业现有的用户权限系统。更常见的做法是“忽略”它。我们在流程定义中任务的办理人通常设置为一个变量比如#{departmentManager}。然后在业务代码中通过一个自定义的解析器根据当前上下文和业务规则将这个表达式解析为具体的用户ID。任务查询时也不直接用taskService.createTaskQuery().taskAssignee(userId)而是先通过我们自己的系统查出用户有权处理的所有任务ID列表再用这个列表去taskService里查询详细信息。这样权限控制牢牢掌握在自己手里。第二事务管理。Activiti 的操作默认是参与 Spring 事务的。这意味着如果你在同一个Transactional方法里既完成了taskService.complete()又更新了自己的业务表它们会是一个事务。这很好保证了数据一致性。但要小心在监听器尤其是Java类监听器中执行数据库操作也默认在同一个事务里。如果监听器里抛了异常会导致整个事务回滚包括流程状态的变更。这是符合预期的但你需要意识到这一点。第三异步执行器。Activiti 的定时事件比如“超时自动通过”、后续事件如邮件任务是靠异步执行器Async Executor来处理的。在生产环境一定要启用并配置好它。它本质上是一个线程池负责从ACT_RU_JOB表中取出作业并执行。如果它没启动那些定时器就永远不会触发。第四流程设计器。虽然可以手写 BPMN XML但有一个可视化设计器效率会高很多。Activiti 官方提供了一个不错的 Web 版设计器Activiti Modeler可以集成到你的项目中。不过更多团队选择使用更轻量或更符合自身习惯的工具比如画图工具 Draw.io它支持导出 BPMN 2.0或者一些开源的设计器前端库。定义好的 XML 文件可以放在项目的resources/processes目录下随应用启动自动部署。把 Activiti 引入项目绝不是简单的加个依赖。你需要思考流程定义如何管理是打包在项目里还是存在数据库动态部署流程版本如何升级是直接部署新版本旧实例继续跑老版本还是需要迁移如何做流程的监控和统计这些问题在技术选型初期就要有个大致规划。从我经验来看Activiti 的稳定性和社区活跃度足以支撑企业级应用它的学习曲线前期稍陡但一旦上手对于复杂流程的驾驭能力会给你带来巨大的回报。