十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot + Vue + Flowable 工作流系统实战:从零搭建审批流程应用

Spring Boot + Vue + Flowable 工作流系统实战:从零搭建审批流程应用 1. 项目缘起为什么选择这个技术栈最近在做一个内部审批系统核心需求是让业务部门能自己画流程图然后系统能自动跑起来。这听起来就是典型的工作流场景。市面上引擎不少Activiti名气大但Flowable作为它的分支这几年社区活跃文档也相对友好尤其是对Spring Boot的集成可以说是“开箱即用”级别的。后端用Spring Boot几乎是Java生态里的默认选项了生态全、配置省心。前端选Vue主要是团队熟而且它的响应式特性和组件化开发对于构建动态、交互复杂的管理后台页面非常顺手。所以Spring Boot Vue Flowable这个组合就成了我们技术选型的自然结果。这篇文章我就把从零搭建、核心功能实现到踩坑填坑的全过程梳理一遍目标是让你看完就能动手复现一个基础但完整的工作流应用。2. 环境搭建与项目初始化2.1 后端Spring Boot工程骨架首先用Spring Initializr或者IDE自带的创建向导生成一个基础的Spring Boot项目。依赖选择上除了基础的Spring Web、Spring Data JPA核心是引入Flowable。对于Spring Boot 2.7.x 或 3.x 版本在pom.xml里添加以下关键依赖!-- Flowable 核心引擎及Spring Boot整合 -- dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version6.8.0/version !-- 请使用当时最新稳定版 -- /dependency !-- 数据库驱动这里以MySQL为例 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- 数据源Spring Boot默认使用HikariCP无需显式引入 --这里有个细节flowable-spring-boot-starter这个starter包它会自动配置流程引擎、各种Service如RepositoryService、RuntimeService、TaskService以及数据库。你几乎不需要写任何XML配置。启动项目如果控制台没有报错并且能看到Flowable打印的Banner和自动建表的日志说明引擎初始化成功了。注意Flowable启动时会自动检查数据库如果表不存在会基于Liquibase自动创建近50张表。所以请确保你的数据库连接配置正确并且有创建表的权限。第一次启动看到刷屏的建表日志是正常的。2.2 前端Vue工程创建与基础配置前端我们使用Vue 3的官方脚手架Vite来创建项目它比传统的Webpack启动更快配置更简单。npm create vuelatest my-flowable-ui创建过程中根据提示选择需要的特性我建议勾选上TypeScript类型检查让开发更稳健、Router路由管理、Pinia状态管理替代Vuex。像Eslint、Prettier这些代码规范工具也建议选上利于团队协作。项目创建好后安装一些我们后续会用到的UI库和请求库。Element Plus是Vue 3下一个非常成熟的中后台UI框架我们用它来快速搭建页面。cd my-flowable-ui npm install npm install element-plus element-plus/icons-vue npm install axios接着在main.ts中全局引入Element Plus及其图标库import { createApp } from vue import App from ./App.vue import router from ./router import ElementPlus from element-plus import element-plus/dist/index.css import * as ElementPlusIconsVue from element-plus/icons-vue const app createApp(App) // 注册所有图标 for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) } app.use(router) app.use(ElementPlus) app.mount(#app)最后配置一下axios的全局实例统一管理后端API请求的基地址和拦截器。在src目录下新建一个utils/request.ts文件import axios from axios const service axios.create({ baseURL: http://localhost:8080/api, // 假设后端Spring Boot运行在8080端口 timeout: 10000 }) // 请求拦截器可用于添加token等 service.interceptors.request.use( config { // 从localStorage或Pinia store获取token const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }, error { return Promise.reject(error) } ) // 响应拦截器统一处理错误 service.interceptors.response.use( response { const res response.data // 这里根据你的后端统一响应格式进行调整例如判断 res.code 200 return res }, error { console.error(请求错误:, error) // 可以在这里统一处理401、403等状态码跳转登录页 return Promise.reject(error) } ) export default service这样前后端的基础架子就搭好了。后端提供了REST API和流程引擎服务前端有了一个现代化的SPA应用框架。接下来我们要让两者真正联动起来核心就是流程定义的管理。3. 流程定义管理从建模到部署工作流的起点是一个流程图在Flowable里这个图叫流程定义Process Definition。我们通常用BPMN 2.0标准来画这个图。3.1 使用Flowable Modeler或第三方工具建模Flowable自带了一个Web版的流程设计器Modeler你可以把它集成到自己的项目里。但对于快速启动我更推荐先用一个独立的工具画图。我常用的是Eclipse的BPMN 2.0插件或者在线工具如bpmn-io的demo。画一个最简单的请假流程开始事件-用户任务提交申请-排他网关-用户任务经理审批-排他网关-结束事件。排他网关后面可以连上“通过”和“拒绝”两条线并设置条件例如${approve}。画好后会得到一个.bpmn20.xml文件。这个文件是纯XML描述了流程的结构。你可以把它直接放到Spring Boot项目的src/main/resources/processes/目录下。项目启动时Flowable会自动扫描并部署这个文件。但这种方式只适合流程固定不变的场景。更灵活的方式是通过API动态部署。3.2 通过后端API动态部署流程我们在后端创建一个ProcessDefinitionController提供部署和查询的接口。首先注入Flowable的核心服务RepositoryService。RestController RequestMapping(/api/process-definition) public class ProcessDefinitionController { Autowired private RepositoryService repositoryService; PostMapping(/deploy) public Result deploy(RequestParam(file) MultipartFile file) throws IOException { if (file.isEmpty()) { return Result.error(请上传流程定义文件); } String fileName file.getOriginalFilename(); Deployment deployment repositoryService.createDeployment() .addBytes(fileName, file.getBytes()) .name(流程部署- fileName) .deploy(); return Result.success(部署成功, deployment.getId()); } GetMapping(/list) public Result list() { ListProcessDefinition list repositoryService.createProcessDefinitionQuery() .latestVersion() .list(); // 将ProcessDefinition对象转换为前端需要的VO ListProcessDefinitionVO voList list.stream().map(pd - { ProcessDefinitionVO vo new ProcessDefinitionVO(); vo.setId(pd.getId()); vo.setKey(pd.getKey()); vo.setName(pd.getName()); vo.setVersion(pd.getVersion()); vo.setDeploymentId(pd.getDeploymentId()); // 可以通过repositoryService.getResourceAsStream获取BPMN XML内容 return vo; }).collect(Collectors.toList()); return Result.success(voList); } }这里的Result是一个简单的统一响应封装类。ProcessDefinitionVO是视图对象只返回前端需要的字段。3.3 前端实现流程文件上传与列表展示在前端我们创建一个ProcessDefinition.vue组件。使用Element Plus的Upload组件上传.bpmn文件。template div el-upload classupload-demo :actionuploadUrl :headersuploadHeaders :on-successhandleUploadSuccess :before-uploadbeforeUpload el-button typeprimary点击上传BPMN文件/el-button template #tip div classel-upload__tip仅支持 .bpmn 或 .bpmn20.xml 格式文件/div /template /el-upload el-table :datatableData stylewidth: 100%; margin-top: 20px el-table-column propname label流程名称 / el-table-column propkey label流程KEY / el-table-column propversion label版本 / el-table-column propdeploymentId label部署ID / el-table-column label操作 template #defaultscope el-button link typeprimary clickviewDiagram(scope.row)查看流程图/el-button el-button link typedanger clickhandleDelete(scope.row)删除/el-button /template /el-table-column /el-table /div /template script setup langts import { ref, onMounted } from vue import request from /utils/request import { ElMessage } from element-plus interface ProcessDefinition { id: string name: string key: string version: number deploymentId: string } const uploadUrl ${import.meta.env.VITE_API_BASE_URL}/process-definition/deploy const uploadHeaders { Authorization: Bearer ${localStorage.getItem(token)} } const tableData refProcessDefinition[]([]) const loadData async () { const res await request.get(/process-definition/list) tableData.value res.data } const beforeUpload (file: File) { const isBPMN file.name.endsWith(.bpmn) || file.name.endsWith(.bpmn20.xml) if (!isBPMN) { ElMessage.error(只能上传BPMN格式文件!) return false } return true } const handleUploadSuccess () { ElMessage.success(上传并部署成功) loadData() // 刷新列表 } const viewDiagram (row: ProcessDefinition) { // 跳转到流程图查看页面传递部署ID和流程定义ID window.open(/process-diagram-viewer?deploymentId${row.deploymentId}processDefinitionId${row.id}) } onMounted(() { loadData() }) /script这样一个简单的流程定义管理功能就完成了。用户可以上传BPMN文件后端将其部署到Flowable引擎中前端展示所有已部署的流程定义列表。删除操作需要谨慎因为一旦有流程实例在运行删除部署可能会影响历史数据查询通常我们只做“挂起”操作而非物理删除。4. 流程实例与用户任务处理部署好的流程定义只是一个模板。当用户发起一个请假申请时就启动了一个流程实例Process Instance。实例会按照BPMN图的路径流转到达“用户任务”节点时就会生成一个待办任务Task。4.1 启动流程实例与任务查询我们在后端创建ProcessInstanceController和TaskController。启动流程实例需要知道用哪个流程定义来启动通常通过流程定义的key来指定。同时可以传入一些业务变量Variables比如申请人、请假天数、原因等。RestController RequestMapping(/api/process-instance) public class ProcessInstanceController { Autowired private RuntimeService runtimeService; PostMapping(/start) public Result start(RequestBody StartProcessInstanceRequest request) { // request中包含 processDefinitionKey, businessKey关联业务数据ID, variables MapString, Object variables request.getVariables(); // 通常会把当前登录用户ID放入变量 variables.put(starter, SecurityUtils.getCurrentUserId()); ProcessInstance instance runtimeService.startProcessInstanceByKey( request.getProcessDefinitionKey(), request.getBusinessKey(), variables ); return Result.success(流程启动成功, instance.getId()); } }流程启动后第一个用户任务比如“提交申请”会立即创建。我们需要一个接口让前端查询当前用户的待办任务。RestController RequestMapping(/api/task) public class TaskController { Autowired private TaskService taskService; GetMapping(/my-todo) public Result getMyTodoTasks() { String currentUserId SecurityUtils.getCurrentUserId(); ListTask tasks taskService.createTaskQuery() .taskAssignee(currentUserId) // 查询指派给当前用户的任务 .orderByTaskCreateTime().desc() .list(); ListTaskVO taskVOList tasks.stream().map(this::convertToVO).collect(Collectors.toList()); return Result.success(taskVOList); } GetMapping(/candidate) public Result getCandidateTasks() { String currentUserId SecurityUtils.getCurrentUserId(); // 查询当前用户作为候选人的任务需要认领 ListTask tasks taskService.createTaskQuery() .taskCandidateUser(currentUserId) .list(); ListTaskVO taskVOList tasks.stream().map(this::convertToVO).collect(Collectors.toList()); return Result.success(taskVOList); } }4.2 前端任务列表与办理页面前端创建一个MyTask.vue组件展示待办任务。template div el-tabs v-modelactiveTab el-tab-pane label我的待办 nametodo el-table :datatodoTasks el-table-column propname label任务名称 / el-table-column propprocessInstanceId label流程实例ID / el-table-column propcreateTime label创建时间 / el-table-column label操作 template #defaultscope el-button sizesmall clickhandleTask(scope.row)办理/el-button /template /el-table-column /el-table /el-tab-pane el-tab-pane label候选任务 namecandidate el-table :datacandidateTasks el-table-column propname label任务名称 / el-table-column propprocessInstanceId label流程实例ID / el-table-column label操作 template #defaultscope el-button sizesmall clickclaimTask(scope.row)认领/el-button /template /el-table-column /el-table /el-tab-pane /el-tabs !-- 任务办理对话框 -- el-dialog v-modeldialogVisible :titlecurrentTask.name div v-ifcurrentTask p流程实例ID: {{ currentTask.processInstanceId }}/p !-- 这里根据不同的任务类型渲染不同的表单 -- !-- 例如审批任务可能有“通过”、“拒绝”按钮和审批意见输入框 -- el-form :modeltaskForm el-form-item label审批意见 el-input v-modeltaskForm.comment typetextarea / /el-form-item el-form-item el-button typeprimary clickcompleteTask(approve)同意/el-button el-button typedanger clickcompleteTask(reject)拒绝/el-button /el-form-item /el-form /div /el-dialog /div /template script setup langts import { ref, onMounted } from vue import request from /utils/request import { ElMessage } from element-plus interface Task { id: string name: string processInstanceId: string createTime: string } const activeTab ref(todo) const todoTasks refTask[]([]) const candidateTasks refTask[]([]) const dialogVisible ref(false) const currentTask refTask | null(null) const taskForm ref({ comment: }) const loadMyTodoTasks async () { const res await request.get(/task/my-todo) todoTasks.value res.data } const loadCandidateTasks async () { const res await request.get(/task/candidate) candidateTasks.value res.data } const handleTask (task: Task) { currentTask.value task dialogVisible.value true } const claimTask async (task: Task) { await request.post(/task/${task.id}/claim) ElMessage.success(任务认领成功) loadCandidateTasks() loadMyTodoTasks() // 认领后可能会出现在待办列表 } const completeTask async (outcome: string) { if (!currentTask.value) return const variables { approve: outcome approve, // 这个变量名对应BPMN图中排他网关的条件表达式 ${approve} comment: taskForm.value.comment } await request.post(/task/${currentTask.value.id}/complete, { variables }) ElMessage.success(任务完成) dialogVisible.value false taskForm.value.comment loadMyTodoTasks() // 刷新列表 } onMounted(() { loadMyTodoTasks() loadCandidateTasks() }) /script4.3 后端任务办理与流程变量设置前端点击“办理”后需要调用后端接口完成任务。这个操作可能会设置流程变量驱动流程向下一个节点流转。RestController RequestMapping(/api/task) public class TaskController { // ... 其他代码 PostMapping(/{taskId}/complete) public Result completeTask(PathVariable String taskId, RequestBody CompleteTaskRequest request) { MapString, Object variables request.getVariables(); // 可以添加一些审计信息如办理人、办理时间 variables.put(approver, SecurityUtils.getCurrentUserId()); variables.put(approveTime, new Date()); // 完成任务并设置变量 taskService.complete(taskId, variables); // 通常还会添加一条评论Comment if (StringUtils.hasText(request.getComment())) { taskService.addComment(taskId, null, request.getComment()); } return Result.success(任务完成); } PostMapping(/{taskId}/claim) public Result claimTask(PathVariable String taskId) { String currentUserId SecurityUtils.getCurrentUserId(); taskService.claim(taskId, currentUserId); return Result.success(任务认领成功); } }这里的关键是taskService.complete(taskId, variables)。variables中的approve变量会被流程引擎读取用于判断排他网关的走向。如果approve为true流程走向“通过”分支如果为false则走向“拒绝”分支。这样就实现了一个完整的、带分支判断的审批流。5. 流程可视化与历史数据追溯对于一个管理系统能看到流程图当前走到哪一步以及历史的审批记录是刚需。Flowable提供了强大的API支持。5.1 获取流程实例当前活动节点与高亮我们需要一个接口给定一个流程实例ID返回其当前的流程图并高亮显示正在活动的节点。GetMapping(/process-instance/{processInstanceId}/diagram) public void getProcessDiagram(PathVariable String processInstanceId, HttpServletResponse response) throws IOException { // 1. 获取流程定义ID ProcessInstance processInstance runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); if (processInstance null) { response.sendError(HttpStatus.NOT_FOUND.value(), 流程实例不存在或已结束); return; } String processDefinitionId processInstance.getProcessDefinitionId(); // 2. 获取流程图原始SVG/PNG流这里以PNG为例 BpmnModel bpmnModel repositoryService.getBpmnModel(processDefinitionId); ProcessDiagramGenerator diagramGenerator new DefaultProcessDiagramGenerator(); ListString highLightedActivities runtimeService.getActiveActivityIds(processInstanceId); // 高亮连线可选 ListString highLightedFlows Collections.emptyList(); InputStream imageStream diagramGenerator.generateDiagram( bpmnModel, highLightedActivities, highLightedFlows, 宋体, // 字体解决中文乱码 宋体, 宋体 ); // 3. 输出图片到响应流 response.setContentType(image/png); IOUtils.copy(imageStream, response.getOutputStream()); response.flushBuffer(); imageStream.close(); }这个接口返回一张PNG图片。前端可以直接用img标签的src属性指向这个接口地址就能显示带高亮的流程图。5.2 前端集成流程图查看器为了更好的交互我们可以集成一个BPMN查看器。bpmn-js是一个优秀的选择。首先在前端安装npm install bpmn-js然后创建一个ProcessDiagramViewer.vue组件template div div refbpmnContainer styleheight: 600px; border: 1px solid #ccc;/div div v-ifcurrentActivities.length h4当前活动节点/h4 ul li v-foractivity in currentActivities :keyactivity{{ activity }}/li /ul /div /div /template script setup langts import { ref, onMounted, onUnmounted } from vue import BpmnViewer from bpmn-js/lib/Viewer import request from /utils/request import { ElMessage } from element-plus const props defineProps{ processInstanceId: string }() const bpmnContainer refHTMLElement() const currentActivities refstring[]([]) let viewer: BpmnViewer | null null const loadDiagram async () { try { // 1. 获取BPMN XML定义 const defRes await request.get(/process-definition/xml?processInstanceId${props.processInstanceId}) const bpmnXML defRes.data // 2. 初始化查看器并导入XML viewer new BpmnViewer({ container: bpmnContainer.value }) await viewer.importXML(bpmnXML) // 3. 获取当前活动节点并高亮 const activityRes await request.get(/process-instance/${props.processInstanceId}/activities) currentActivities.value activityRes.data const canvas viewer.get(canvas) currentActivities.value.forEach(activityId { canvas.addMarker(activityId, highlight) // highlight是一个CSS类名 }) } catch (error) { ElMessage.error(加载流程图失败) console.error(error) } } // 需要后端新增一个接口根据流程实例ID查询其对应的流程定义XML // 也需要一个接口查询当前活动节点ID列表 onMounted(() { loadDiagram() }) onUnmounted(() { if (viewer) { viewer.destroy() } }) /script style .highlight { fill: #ff0000 !important; /* 高亮颜色 */ stroke: #ff0000 !important; } /style5.3 历史数据查询与审批记录流程跑完后我们需要追溯谁在什么时候做了什么。Flowable的HistoryService提供了完整的历史查询。RestController RequestMapping(/api/history) public class HistoryController { Autowired private HistoryService historyService; GetMapping(/task/{processInstanceId}) public Result getHistoricTasks(PathVariable String processInstanceId) { ListHistoricTaskInstance tasks historyService.createHistoricTaskInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricTaskInstanceStartTime().asc() .list(); ListHistoricTaskVO vos tasks.stream().map(task - { HistoricTaskVO vo new HistoricTaskVO(); vo.setName(task.getName()); vo.setAssignee(task.getAssignee()); vo.setStartTime(task.getStartTime()); vo.setEndTime(task.getEndTime()); vo.setDurationInMillis(task.getDurationInMillis()); // 还可以关联查询审批意见Comment ListComment comments taskService.getTaskComments(task.getId()); vo.setComments(comments.stream().map(Comment::getFullMessage).collect(Collectors.toList())); return vo; }).collect(Collectors.toList()); return Result.success(vos); } GetMapping(/variable/{processInstanceId}) public Result getHistoricVariables(PathVariable String processInstanceId) { ListHistoricVariableInstance variables historyService.createHistoricVariableInstanceQuery() .processInstanceId(processInstanceId) .list(); // 将变量列表转换为更易读的Map形式 MapString, Object varMap variables.stream() .collect(Collectors.toMap( HistoricVariableInstance::getVariableName, HistoricVariableInstance::getValue )); return Result.success(varMap); } }前端可以创建一个“流程历史”页面以时间线或表格的形式展示这些历史任务和变量变化让整个审批过程一目了然。这极大地增强了系统的可审计性和透明度。6. 进阶配置与实战避坑指南把基础功能跑通只是第一步要让这个组合在生产环境稳定运行还需要处理一些进阶问题和“坑”。6.1 异步执行器与事务管理Flowable默认使用异步执行器来处理定时器、异步任务等。在Spring Boot中它自动集成了Spring的事务管理。但你需要了解一个关键点用户任务User Task的创建和完成是在同一个事务里的。这意味着如果你在完成任务taskService.complete后紧接着执行一个非常耗时的业务操作这个操作也会被包裹在Flowable的事务里可能导致数据库连接持有时间过长。最佳实践将核心的业务逻辑如更新业务状态、发送通知放在TaskListener或ExecutionListener中并考虑使用Async注解使其异步执行或者使用消息队列解耦。确保流程引擎的事务尽可能短小。6.2 多租户与数据隔离如果你的系统需要服务多个互不干扰的客户租户Flowable提供了多租户支持。可以在部署流程定义和启动流程实例时指定tenantId。// 部署时指定租户 Deployment deployment repositoryService.createDeployment() .addBytes(fileName, file.getBytes()) .tenantId(tenantA) .deploy(); // 查询时过滤租户 ListProcessDefinition list repositoryService.createProcessDefinitionQuery() .latestVersion() .processDefinitionTenantId(tenantA) .list();所有查询API都支持.tenantId()方法。前端需要在用户登录后将租户信息传递给后端后端将其存储在安全上下文如ThreadLocal中在所有流程引擎操作中自动带上这个tenantId。6.3 自定义用户与组身份管理Flowable自带一套简单的用户、组角色管理表ACT_ID_*但在实际项目中我们通常已有自己的用户体系。这时你需要实现Flowable的IdentityService相关接口或者更常见的做法是完全绕过Flowable的身份表。具体做法是在查询任务时不使用taskAssignee或taskCandidateUser而是使用taskCandidateGroupIn结合变量查询。例如将“部门经理”这个角色映射为一个流程变量deptManagerRoleId。在查询待办时先根据当前用户查出他所属的所有角色ID列表然后ListString groupIds getUserGroupIds(currentUserId); // 自定义方法获取用户角色ID列表 ListTask tasks taskService.createTaskQuery() .taskCandidateGroupIn(groupIds) // 查询候选组在这些角色中的任务 .list();对于任务指派claim你仍然可以调用taskService.claim(taskId, userId)这里的userId是你自己系统的用户ID字符串Flowable会把它存到ACT_RU_TASK表的ASSIGNEE_字段里但这只是作为一个字符串标识并不需要存在于ACT_ID_USER表中。6.4 流程版本控制与升级当你修改了BPMN图并重新部署时Flowable会为同一个processDefinitionKey创建新版本。默认情况下新启动的流程实例会使用最新版本但正在运行的老实例会继续使用它们启动时的版本。这是符合预期的行为。如果你需要将运行中的老实例迁移到新版本这称为流程实例迁移Flowable提供了API但操作复杂且有风险。我个人的经验是对于核心业务流程尽量在设计初期考虑周全避免上线后频繁修改流程定义结构如增减节点。对于业务规则的调整尽量通过流程变量或外部业务逻辑来控制而不是修改BPMN图本身。6.5 前端路由与Keep-alive缓存问题在Vue前端使用router-view配合keep-alive可以缓存组件状态提升切换体验。但在处理流程任务列表这种数据频繁变化的页面时需要注意缓存可能导致数据不更新。template router-view v-slot{ Component } keep-alive :includecachedComponents component :isComponent / /keep-alive /router-view /template你需要仔细管理cachedComponents这个数组确保像任务办理页面这样的组件在关闭后从缓存中移除。或者在任务列表组件的activated生命周期钩子中手动触发数据刷新import { onActivated } from vue onActivated(() { // 从缓存中再次激活时刷新数据 loadMyTodoTasks() })另一个常见问题是在el-table中使用v-if切换路由子组件时表格的滚动条位置可能会复位。如果希望保持滚动位置需要手动记录和恢复scrollTop值或者考虑使用Element Plus Table的scroll-to-row等相关方法进行控制但这通常需要比较精细的前端状态管理。6.6 性能监控与Actuator端点Spring Boot Actuator提供了丰富的监控端点。Flowable也暴露了一些端点如/actuator/flowable需要配置management.endpoints.web.exposure.includeflowable来查看引擎状态、作业数量等。但在生产环境请务必保护这些端点不要直接暴露到公网。可以通过Spring Security限制访问IP或角色。如果你完全不需要这些端点可以在application.properties中关闭Actuator的HTTP暴露management.endpoints.web.exposure.exclude*。但请注意关闭HTTP端点后通过JMX的监控可能仍然可用具体取决于你的部署环境和安全策略。7. 部署与集成考量项目开发完成后最终要部署上线。这里有几个关键决策点。7.1 数据库选型与连接池配置Flowable支持几乎所有主流数据库MySQL, PostgreSQL, Oracle, SQL Server等。生产环境强烈建议使用MySQL 5.7或PostgreSQL 9.6。在application.properties中配置好数据源Spring Boot默认使用HikariCP配置项如下spring.datasource.urljdbc:mysql://localhost:3306/flowable_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai spring.datasource.usernameyour_username spring.datasource.passwordyour_password spring.datasource.hikari.maximum-pool-size20 # 根据实际负载调整 spring.datasource.hikari.minimum-idle10连接池大小设置非常重要需要根据你的应用并发量和数据库性能来调整。一个常见的经验公式是连接数 ((核心数 * 2) 有效磁盘数)。对于4核服务器初始可以设置为10-20。7.2 前后端分离部署前端Vue项目通过npm run build打包生成静态文件在dist目录。你可以将这些文件放到Nginx或Apache的静态资源目录下。后端Spring Boot打包成可执行的JAR文件使用java -jar命令运行或者部署到Tomcat等Servlet容器。关键是要解决跨域问题。在开发环境Vue的dev server可以配置代理。在生产环境有两种主流方案Nginx反向代理将前端的/和后端的/api/路径都代理到同一个域名下由Nginx根据路径转发请求从根本上避免跨域。server { listen 80; server_name your-domain.com; location / { root /path/to/vue/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }后端配置CORS在Spring Boot中通过CrossOrigin注解或全局的WebMvcConfigurer配置允许前端域名访问。7.3 流程引擎的集群部署在高可用场景下你可能需要部署多个Spring Boot应用实例并让它们共享同一个Flowable数据库。Flowable引擎本身是无状态的所有状态都保存在数据库里因此天然支持水平扩展。需要注意的点作业执行器Async Executor多个引擎实例会竞争数据库中的异步作业。Flowable使用数据库锁机制来确保一个作业只被一个实例执行。默认配置是没问题的但你需要确保各个实例的系统时间同步NTP。事件日志如果使用了事件监听EventListener并且监听器里有业务逻辑要确保这些逻辑是幂等的或者通过消息队列来串行化处理避免多个实例重复处理同一个流程事件。7.4 流程数据备份与清理Flowable的历史表ACT_HI_*会随着时间推移变得非常庞大。Flowable提供了内置的历史数据清理功能可以通过配置定时任务来执行。# 启用历史清理 flowable.history-levelaudit # 或 full但auto会禁用清理功能 flowable.async-executor-activatetrue # 配置清理周期默认不会自动清理需要自己调用API或配置作业更常见的做法是编写一个定期的数据库归档脚本将超过一定时间如6个月的历史数据迁移到备份库并从主库中删除。在执行任何数据删除操作前务必先备份你可以使用Flowable的ManagementService提供的createTablePageQuery和delete方法但操作需要极其谨慎最好在DBA的指导下进行。从零开始整合Spring Boot、Vue和Flowable构建一个可用的工作流应用核心脉络就是后端通过Flowable Starter快速获得引擎能力并围绕其丰富的Service构建REST API前端用VueElement Plus搭建管理界面通过Axios调用API两者通过流程定义、实例、任务、历史这几个核心概念串联起来。过程中最大的挑战往往不是技术整合而是对BPMN 2.0规范的理解如网关、事件、监听器的使用和对Flowable API特性的掌握如变量作用域、身份服务替代方案。我的建议是先从画一个简单的线性流程图开始把启动、审批、查看历史这个闭环跑通然后再逐步尝试引入排他网关、并行网关、调用活动等复杂元素以及监听器、委托表达式等高级特性。每增加一个功能都充分测试其边界情况这样构建出来的系统才会稳健可靠。
返回列表