永康市建设银行网站查询搜索引擎优化自然排名
永康市建设银行网站查询,搜索引擎优化自然排名,网站制作优势,中兴建设有限公司网站1. 从图纸到引擎#xff1a;让SpringBoot认识你的流程图
上一篇文章我们聊了怎么用Flowable UI画图#xff0c;就像设计师画好了建筑蓝图。但光有蓝图#xff0c;房子不会自己盖起来。今天咱们要干的#xff0c;就是把这个“员工请假流程图”的蓝图#xff0c;真正变成一个…1. 从图纸到引擎让SpringBoot认识你的流程图上一篇文章我们聊了怎么用Flowable UI画图就像设计师画好了建筑蓝图。但光有蓝图房子不会自己盖起来。今天咱们要干的就是把这个“员工请假流程图”的蓝图真正变成一个能在你的SpringBoot应用里跑起来的、活生生的流程。这个过程我把它叫做“注入灵魂”。首先你得明白一个核心概念Flowable引擎。你可以把它想象成一个超级敬业、记忆力超群的“流程管家”。你的SpringBoot应用就是一座办公楼而这个管家就坐镇在办公楼里。你之前画好的BPMN XML文件就是一份详细的工作手册上面写着“请假怎么批先找谁再找谁什么情况能过什么情况打回”。我们的目标就是把这本手册交给这位管家并且告诉他“以后公司里所有请假的事儿都按这个来办你负责盯着。”所以整个集成的过程其实就是三步第一把这位“流程管家”Flowable引擎请到你的SpringBoot办公楼里来上班。第二把你写好的“工作手册”BPMN文件交给他。第三告诉他“现在可以开始处理第一单请假业务了”。听起来是不是挺简单的但魔鬼藏在细节里我踩过的坑争取让你都绕过去。为了让这个“管家”能在SpringBoot里安心工作我们得给他准备好办公环境。这主要就是添加依赖和配置数据源。依赖就像是给他配的办公工具数据源就是他的文件柜所有流程走到哪一步了、谁在处理、结果是什么这些“记忆”都得存起来。这里我强烈建议用MySQL别图省事用内嵌的H2除非你只是做五分钟的演示。因为流程数据是业务核心数据必须持久化H2一重启数据就没了到生产环境哭都来不及。2. 项目初始化与核心依赖配置好了废话不多说我们直接开干。打开你的IDE创建一个标准的SpringBoot项目这里我假设你用的是Maven。关键就在这个pom.xml文件里我们要把“流程管家”和他的“工具包”请进来。dependencies !-- SpringBoot基础依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- Flowable 核心引擎依赖 -- dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.7.0/version !-- 请使用最新稳定版 -- /dependency !-- 数据库驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 可选Flowable UI 模型器与设计器API方便前端集成设计器 -- dependency groupIdorg.flowable/groupId artifactIdflowable-ui-modeler-rest/artifactId version6.7.0/version /dependency /dependencies这里重点说一下flowable-spring-boot-starter这个依赖。它是个“全家桶”一次性把Flowable流程引擎、Spring集成组件、数据库管理模块全都给你引进来并且自动做好了很多默认配置。用上它你就省去了大量手动组装“管家”的麻烦事。依赖加好了接下来得给管家配个“文件柜”。打开application.yml或者application.properties配置你的数据库连接。这里有个巨坑请注意Flowable引擎启动时会自动检查数据库里有没有它需要的几十张表。如果没有它会根据你配置的database-schema-update属性来决定行为。spring: datasource: url: jdbc:mysql://localhost:3306/flowable_db?characterEncodingUTF-8serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver # Flowable 引擎配置 flowable: # 是否异步执行历史数据生产环境建议true开发可设false方便调试 async-executor-activate: false # 数据库架构更新策略 database-schema-update: true # 是否检查流程定义版本 check-process-definitions: false重点就是这个database-schema-update。我一般在第一次启动项目时把它设置为true。这样Flowable引擎发现数据库是空的就会自动执行建表SQL把流程定义表、运行时任务表、历史记录表等统统创建好。等表创建成功后一定要记得把它改回false或者更安全的flowable-drop-create这也不建议在生产用。因为true意味着每次启动都尝试更新表结构万一你的数据库有数据而新版本引擎的表结构有变化可能导致数据迁移问题甚至数据丢失。所以建表用一次true之后就用false这是血的教训。3. 加载流程图把BPMN文件交给引擎表建好了引擎也初始化了现在该把我们的“工作手册”——也就是上一篇文章导出的那个leave-request.bpmn20.xml文件——交给引擎了。这里有两种主流做法我都给你讲讲你可以根据项目情况选。第一种自动扫描部署推荐给新手。这是最省事的方法。SpringBoot和Flowable Starter配合可以自动扫描resources目录下的特定文件夹里的BPMN文件。你只需要在application.yml里加一行配置flowable: check-process-definitions: true process-definition-location-prefix: classpath:/processes/ process-definition-location-suffixes: .bpmn20.xml,.bpmn然后在src/main/resources目录下新建一个文件夹叫processes把你画好的leave-request.bpmn20.xml文件扔进去。这样每次SpringBoot应用启动时Flowable引擎就会自动扫描这个文件夹发现新的或变更的BPMN文件就会把它“部署”到引擎里。所谓“部署”其实就是解析这个XML文件把里面的开始事件、用户任务、网关这些元素转换成引擎内部能理解的数据结构存到数据库的ACT_RE_PROCDEF流程定义表等表里。第二种编程式部署更灵活可控。有时候我们的流程文件可能来自网络、数据库或者需要根据条件动态决定部署哪个版本。这时候就需要通过代码来操作。我们需要注入一个RepositoryService这是Flowable提供的用于管理流程定义就是BPMN文件的核心服务。Service public class ProcessDeployService { Autowired private RepositoryService repositoryService; /** * 部署classpath下的BPMN文件 */ public void deployProcessFromClasspath() { Deployment deployment repositoryService.createDeployment() .addClasspathResource(processes/leave-request.bpmn20.xml) // 文件路径 .name(员工请假流程部署) // 部署名称 .deploy(); // 执行部署 System.out.println(流程部署成功部署ID: deployment.getId()); } /** * 动态部署一个BPMN XML字符串比如从数据库或前端传来 * param bpmnXmlContent BPMN文件的XML字符串内容 * param processName 流程名称 */ public void deployProcessFromString(String bpmnXmlContent, String processName) { Deployment deployment repositoryService.createDeployment() .addString(processName .bpmn20.xml, bpmnXmlContent) .name(processName 动态部署) .deploy(); System.out.println(动态流程部署成功部署ID: deployment.getId()); } }你可以写一个CommandLineRunner或者在一个PostConstruct方法里调用deployProcessFromClasspath这样应用一启动就完成部署。编程式部署的好处是你可以在部署时添加分类标签category、设置租户ID多租户系统有用并且能清晰地捕获部署结果。部署成功后你可以去数据库里看一眼ACT_RE_PROCDEF表里面应该已经有了一条记录KEY_字段就是你流程图里设置的流程ID比如leaveRequest。4. 启动第一个流程实例让流程“活”起来流程定义部署好了它依然只是个“模板”或者“手册”。就像有了员工手册但还没有任何一个员工真正提交请假申请。现在我们要触发一次实际的请假也就是启动一个流程实例。这个过程相当于一个员工比如张三点下了“提交请假申请”的按钮。谁来启动呢我们需要用到另一个核心服务RuntimeService。通常我们会把这个启动逻辑放在一个Controller或者Service里对外提供一个API。下面我写一个完整的、可运行的例子包含启动流程和传递初始参数。RestController RequestMapping(/api/process) public class ProcessController { Autowired private RuntimeService runtimeService; Autowired private TaskService taskService; Autowired private HistoryService historyService; /** * 启动一个员工请假流程实例 * param startRequest 包含启动参数的请求体 * return 流程实例ID */ PostMapping(/leave/start) public ResponseEntityMapString, String startLeaveProcess(RequestBody ProcessStartRequest startRequest) { // 构建流程变量。这些变量会在整个流程生命周期中传递和使用。 MapString, Object variables new HashMap(); variables.put(applicant, startRequest.getApplicant()); // 申请人 variables.put(leaveDays, startRequest.getLeaveDays()); // 请假天数 variables.put(reason, startRequest.getReason()); // 请假事由 variables.put(startDate, startRequest.getStartDate()); // 开始日期 // 这里可以放更多业务变量比如部门、职位等 // 关键一步启动流程实例 // “leaveRequest”必须和你BPMN文件中process idleaveRequest ...的id完全一致 ProcessInstance processInstance runtimeService.startProcessInstanceByKey(leaveRequest, variables); MapString, String result new HashMap(); result.put(processInstanceId, processInstance.getId()); result.put(processDefinitionId, processInstance.getProcessDefinitionId()); result.put(message, 员工请假流程实例启动成功); return ResponseEntity.ok(result); } } // 简单的启动请求封装 Data // 使用Lombok注解 class ProcessStartRequest { private String applicant; private Integer leaveDays; private String reason; private Date startDate; }当你调用这个/api/process/leave/start接口传入张三的信息请假3天病假明天开始引擎就会做以下几件事根据流程定义KeyleaveRequest找到对应的最新版本的流程定义。创建一个新的流程实例ProcessInstance你可以把它理解为一次独立的请假申请流程。将你传入的variables变量绑定到这个流程实例上。流程指针会走到BPMN图中的开始事件然后沿着连线自动执行到第一个用户任务比如“提交请假申请”这个任务。这个任务会被创建并且等待人来处理。同时流程实例ID、任务ID等信息都会被存入数据库。这时流程就真正“活”了它不再是一张静态的图而是一个有状态、有数据、正在等待处理的生命体。你可以通过runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult()来查询这个运行中的实例。5. 任务查询与办理驱动流程向前走流程启动后卡在了第一个用户任务“提交请假申请”那里。在Flowable里用户任务必须由人或系统“完成”complete后流程才会继续流向下一个节点。那么谁来完成任务怎么查询当前有哪些任务这就是TaskService的职责了。假设我们设计的流程是张三提交后任务自动流转到他的部门经理“李四”那里进行审批。那么李四登录系统后需要先看到待他审批的任务列表。RestController RequestMapping(/api/task) public class TaskController { Autowired private TaskService taskService; /** * 获取指定候选人的待办任务列表 * param candidateUser 候选人用户ID * return 任务列表 */ GetMapping(/list) public ResponseEntityListTaskRepresentation getTasks(RequestParam String candidateUser) { // 查询任务候选人或候选组为指定用户的任务 ListTask tasks taskService.createTaskQuery() .taskCandidateUser(candidateUser) // 根据候选人查询 // .taskCandidateGroup(deptManager) // 或者根据候选组查询 .orderByTaskCreateTime().desc() // 按创建时间倒序 .list(); // 转换成前端友好的DTO对象 ListTaskRepresentation taskList tasks.stream().map(task - { TaskRepresentation dto new TaskRepresentation(); dto.setTaskId(task.getId()); dto.setTaskName(task.getName()); dto.setProcessInstanceId(task.getProcessInstanceId()); dto.setCreateTime(task.getCreateTime()); // 还可以获取流程变量显示请假详情 MapString, Object variables taskService.getVariables(task.getId()); dto.setApplicant((String) variables.get(applicant)); dto.setLeaveDays((Integer) variables.get(leaveDays)); return dto; }).collect(Collectors.toList()); return ResponseEntity.ok(taskList); } /** * 办理完成一个任务 * param taskId 任务ID * param approvalRequest 审批意见和结果 * return */ PostMapping(/complete/{taskId}) public ResponseEntityString completeTask(PathVariable String taskId, RequestBody TaskApprovalRequest approvalRequest) { // 在完成任务前可以设置新的流程变量比如审批意见和结果 MapString, Object taskVariables new HashMap(); taskVariables.put(managerComment, approvalRequest.getComment()); taskVariables.put(approvalResult, approvalRequest.getApproved()); // true/false // 关键操作完成任务 taskService.complete(taskId, taskVariables); // 完成任务后流程引擎会自动 // 1. 将当前任务标记为已完成。 // 2. 根据流程图的连线Sequence Flow推动流程指针到下一个节点。 // 3. 如果下一个节点是用户任务则创建新的待办任务。 // 4. 如果下一个节点是网关则根据条件判断流向。 // 5. 如果下一个节点是结束事件则整个流程实例结束。 return ResponseEntity.ok(任务处理完成流程已进入下一环节); } } // 任务展示对象 Data class TaskRepresentation { private String taskId; private String taskName; private String processInstanceId; private Date createTime; private String applicant; private Integer leaveDays; } // 任务审批请求对象 Data class TaskApprovalRequest { private String comment; private Boolean approved; }李四调用/api/task/list?candidateUserlisi就能看到张三的请假申请任务。他点击处理调用/api/task/complete/{taskId}接口并传入审批意见和结果同意或拒绝。taskService.complete()方法一执行引擎就会驱动流程向下走。这里有个非常重要的点流程的走向是由你的BPMN图决定的。比如你在经理审批节点后面接了一个排他网关菱形中间是X网关有两条出口线一条线的条件是${approvalResult true}同意指向“人事备案”另一条线的条件是${approvalResult false}拒绝指向“通知申请人驳回”。那么当李四传入approved: false时引擎在完成任务后会自动计算网关条件让流程沿着“拒绝”那条线走下去自动创建“通知申请人”的新任务。这一切都是引擎自动完成的你不需要写任何if-else来控制流程分支这就是工作流引擎的核心价值。6. 流程状态查询与历史追踪流程跑起来之后我们肯定想知道它到哪一步了是谁处理的花了多长时间。这就需要用到HistoryService。它记录了流程实例从生到死的完整足迹对于审计、报表和监控至关重要。比如你想查看一个特定流程实例的当前状态和所有经过的步骤GetMapping(/instance/{instanceId}/history) public ResponseEntityListHistoricActivityInstance getProcessHistory(PathVariable String instanceId) { // 查询该流程实例的所有历史活动 ListHistoricActivityInstance activities historyService.createHistoricActivityInstanceQuery() .processInstanceId(instanceId) .orderByHistoricActivityInstanceStartTime().asc() .list(); // 返回的数据会包含活动ID、活动名称如“经理审批”、活动类型userTask, startEvent等、办理人、开始时间、结束时间、耗时等。 // 如果“结束时间”为null说明这个活动是当前正在进行的活动。 return ResponseEntity.ok(activities); } /** * 查询已结束的流程实例 */ GetMapping(/finished) public ResponseEntityListHistoricProcessInstance getFinishedProcesses() { ListHistoricProcessInstance list historyService.createHistoricProcessInstanceQuery() .finished() // 只查询已结束的 .orderByProcessInstanceEndTime().desc() .list(); return ResponseEntity.ok(list); }通过历史服务你可以轻松地构建出流程的审批时间线、统计每个环节的处理效率、分析流程瓶颈。这是手工编写状态机代码很难实现的功能。我建议在重要的业务流程中将关键的历史数据同步到你的业务数据库或日志系统方便做更复杂的业务分析。7. 实战调试技巧与常见问题排查集成和开发过程中难免会遇到流程不按预期走的情况。这里分享几个我常用的调试“神器”和避坑指南。第一善用Flowable的日志。在application.yml里把Flowable的日志级别调到DEBUG你能看到引擎内部执行的每一步细节比如“正在评估表达式${approvalResult true}”、“找到了X条出口顺序流”、“创建了任务‘人事备案’”等等。这对理解引擎行为和排查逻辑错误非常有帮助。logging: level: org.flowable: DEBUG第二可视化跟踪流程状态。虽然我们没集成Flowable UI但你可以通过数据库直接查看状态。关键的表有这几张ACT_RU_TASK运行时任务表。当前所有待办任务都在这里。如果任务没出现先查这张表。ACT_RU_EXECUTION运行时执行流表。代表流程当前正在执行的位置。ACT_RU_VARIABLE运行时变量表。所有流程实例的变量值都在这里检查你设置的变量对不对。ACT_HI_ACTINST历史活动实例表。流程走过的所有步骤包括开始、结束、每个任务、每个网关都按时间顺序记录在这里。这是最强大的调试工具流程卡住了看看最后一条记录停在了哪个节点。第三注意变量作用域。这是新手最容易迷糊的地方。流程变量有全局变量runtimeService.setVariable和本地任务变量taskService.setVariableLocal之分。全局变量在整个流程实例内都有效而本地任务变量只属于某个特定的任务实例。在完成任务时通过taskService.complete(taskId, variables)传入的变量默认是设置到流程实例上的全局变量。如果你在网关条件表达式中引用了一个变量一定要确保它在全局作用域内并且类型正确比如字符串和布尔值的比较要小心。第四处理异步任务和事务。默认情况下Flowable的服务调用如startProcessInstanceByKey,complete都是同步且在同一个数据库事务中的。这意味着如果任务完成后的业务逻辑非常耗时或者调用了外部服务可能会导致事务时间过长。对于这类场景可以考虑使用Flowable的异步执行器Async Executor或者将耗时操作放在事务提交后的事件监听器里执行。配置flowable.async-executor-activate: true可以开启异步执行但要注意相关表ACT_RU_JOB的监控。踩过几次坑之后我的经验是每画完一个流程图先用最简单的代码不涉及复杂业务逻辑把整个流程从启动到结束跑一遍。确认流程走向完全符合设计后再开始嵌入真实的业务代码。这样能帮你快速定位问题是出在流程设计上还是出在业务集成上。