北京平台网站建设方案,免费公众号编辑模板,好网站制作,一级a做爰片免费视频网站Flowable 6.8.0实战#xff1a;如何给用户任务添加逾期处理下拉框#xff08;附完整代码#xff09; 最近在重构一个内部审批系统#xff0c;产品经理提了个需求#xff1a;希望能在流程设计时#xff0c;就为每个审批节点设定好任务逾期后的处理策略。比如#xff0c;一…Flowable 6.8.0实战如何给用户任务添加逾期处理下拉框附完整代码最近在重构一个内部审批系统产品经理提了个需求希望能在流程设计时就为每个审批节点设定好任务逾期后的处理策略。比如一个采购申请超过3天没处理是自动转给上级还是发送提醒邮件或者直接标记为“已超时”进入特殊处理队列。这个需求听起来简单但在Flowable的标准用户任务属性里并没有现成的“逾期处理方式”下拉框。这意味着我们需要自己动手从设计器前端到流程引擎后端打通一条自定义属性的通道。如果你也遇到过类似场景——需要在Flowable设计器里添加非标准的、带交互的复杂属性比如下拉框、单选框并且确保这些属性值能被后端正确解析和使用那么这篇文章正是为你准备的。我将基于Flowable 6.8.0版本拆解整个实现过程从前端的Stencilset配置、Angular模板改造到后端的Java扩展类编写最后还会分享几个我踩过的坑和调试技巧。整个过程不涉及对Flowable核心引擎的魔改而是遵循其扩展机制属于中高级开发者可以驾驭的定制化操作。1. 理解Flowable设计器的属性扩展机制在动手写代码之前我们得先搞清楚Flowable设计器Modeler是怎么管理那些属性面板的。很多人一上来就找Java类其实前端才是入口。Flowable-UI模块提供了一个高度可配置的前端设计器其核心配置文件是stencilset_bpmn.json。这个文件定义了设计器中所有图形元素如用户任务、网关以及它们对应的属性包。你可以把它想象成一个“零件库”说明书。stencils数组里列出了所有可用的“零件”比如UserTask而每个零件可以关联多个propertyPackages属性包。属性包里则定义了具体的属性项比如ID、名称、类型和默认值。这里有个关键点属性类型type与前端的渲染控件绑定。在properties.js文件中定义了一系列渲染器renderer将我们在stencilset_bpmn.json中定义的type如String,Boolean映射到具体的HTML表单控件。例如type: String通常渲染为文本输入框。type: Boolean渲染为复选框。type: fd-multiinstance_type则渲染为一个下拉选择框这是系统内置的多实例类型属性。我们要实现的“逾期处理方式”下拉框本质上就是创建一个新的类型比如fd-overtimehandlertype并为其编写对应的下拉框渲染逻辑。注意修改前端配置后通常需要重新构建或重启Flowable-UI应用才能生效。如果你是在开发环境建议使用热加载或快速重启的方式来验证修改。2. 前端改造添加下拉框属性到设计器前端改造分为三个核心步骤定义属性包、编写属性渲染器、集成到设计器界面。我们以添加一个“逾期处理方式”下拉框为例其可选值为“无操作”、“转交上级”、“发送提醒”。2.1 第一步在stencilset_bpmn.json中定义属性包找到flowable-ui-modeler-logic模块下的/src/main/resources/stencilset_bpmn.json文件。我们需要在顶层的propertyPackages数组中新增一个包。{ name: usertaskovertimehandlertypepackage, properties: [{ id: overtimehandlertype, type: fd-overtimehandlertype, title: 逾期处理方式, value: 0, description: 定义用户任务逾期后的系统处理策略, popular: true }] }参数解析name: 属性包的名称保持唯一性即可。id: 属性的键key后续后端会通过这个键来读取值。type:这是关键我们自定义的类型fd-overtimehandlertype需要与后续properties.js中的定义匹配。value: 默认值我们设为0对应“无操作”。popular: 设为true会让这个属性出现在属性面板的“常用”区域。接下来需要让“用户任务”这个图形元素拥有这个属性包。在stencils数组中找到id为UserTask的对象在其propertyPackages数组末尾添加我们刚定义的包名usertaskovertimehandlertypepackage。2.2 第二步在properties.js中注册自定义类型打开flowable-ui-modeler-frontend模块下的/src/main/resources/static/modeler/editor-app/configuration/properties.js文件。这个文件包含了所有属性类型的渲染器定义。我们需要添加一个新的渲染器配置。通常做法是在文件中搜索Boolean或fd-找一个合适的位置插入。为了保持代码清晰我建议在文件末尾的返回对象之前添加。// 在properties.js文件的返回对象中找到或添加以下配置 { // ... 其他已有配置 ... fd-overtimehandlertype: { template: div ng-include\editor-app/configuration/properties/overtime-handler-type.html\/div, controller: [$scope, function($scope) { // 控制器逻辑可以在这里定义例如从后端加载下拉选项如果需要 $scope.overtimeHandlerOptions [ { value: 0, label: 无操作 }, { value: 1, label: 自动转交上级 }, { value: 2, label: 发送系统与邮件提醒 }, { value: 3, label: 标记为异常并终止流程 } ]; // 确保属性值初始化 if (!$scope.property.value) { $scope.property.value 0; } }] } }代码说明键名fd-overtimehandlertype必须与stencilset_bpmn.json中定义的type完全一致。template指定了渲染此属性所需的HTML模板路径。我们将其指向一个即将创建的独立模板文件这样结构更清晰。controller是一个AngularJS控制器在这里我们定义了下拉框的选项数组$scope.overtimeHandlerOptions并设置了默认值。2.3 第三步创建AngularJS HTML模板在properties.js文件同级的properties目录下通常路径是.../configuration/properties/创建一个新文件overtime-handler-type.html。div classform-group label classcontrol-label{{property.title}}/label div classcontrol-wrapper select classform-control ng-modelproperty.value ng-optionsoption.value as option.label for option in overtimeHandlerOptions /select p classhelp-block ng-ifproperty.description{{property.description}}/p /div /div模板要点使用了Bootstrap的form-group样式与Flowable设计器原有风格保持一致。ng-modelproperty.value将下拉框的选择值双向绑定到属性对象上这是属性值能够被保存的关键。ng-options指令根据控制器中定义的overtimeHandlerOptions动态生成下拉选项。2.4 第四步确保前端资源被加载通常只要模板文件放在正确路径并被ng-include引用AngularJS就能加载它。但为了万无一失可以检查一下flowable-ui-modeler-frontend的index.html或构建脚本确保没有特殊的资源加载白名单限制。标准配置下无需额外操作。完成以上四步后重启你的Flowable-UI应用。打开流程设计器拖入一个“用户任务”查看其属性面板。你应该能在“常用”或对应的分组里看到“逾期处理方式”下拉框并且可以进行选择。前端改造步骤关键文件核心操作验证方式1. 定义属性包stencilset_bpmn.json在propertyPackages和UserTask的包列表中新增查看设计器JSON结构2. 注册渲染器properties.js添加fd-overtimehandlertype配置项浏览器控制台检查渲染器是否注册3. 创建模板overtime-handler-type.html编写下拉框HTML和Angular指令直接预览HTML结构4. 集成验证重启应用在设计器中查看用户任务属性可视化操作并保存模型3. 后端扩展解析与使用自定义属性前端设计器能展示和保存属性了但这些自定义数据如何被流程引擎识别和使用呢这就需要扩展后端的JSON-BPMN转换器。Flowable设计器保存的模型是JSON格式引擎执行需要的是BPMN XML格式中间的转换过程就是我们的切入点。3.1 第一步创建自定义UserTask转换器我们需要继承UserTaskJsonConverter类重写两个核心方法convertElementToJson: 将BPMN模型对象引擎侧转换为设计器JSON时调用用于将我们存储在BPMN扩展元素中的值写入JSON属性。convertJsonToElement: 将设计器JSON转换为BPMN模型对象时调用用于将JSON中的自定义属性值提取出来存入BPMN模型的扩展元素中。package com.yourcompany.flowable.extensions.converter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.flowable.bpmn.constants.BpmnXMLConstants; import org.flowable.bpmn.model.*; import org.flowable.editor.language.json.converter.UserTaskJsonConverter; import org.flowable.editor.language.json.converter.BpmnJsonConverterContext; import java.util.List; import java.util.Map; public class CustomUserTaskJsonConverter extends UserTaskJsonConverter { // 定义自定义属性的常量键必须与前端 stencilset_bpmn.json 中的 id 一致 public static final String OVER_TIME_HANDLER_TYPE overtimehandlertype; // 你可以在这里添加更多自定义属性 // public static final String MY_CUSTOM_FIELD mycustomfield; Override protected void convertElementToJson(ObjectNode propertiesNode, BaseElement baseElement, BpmnJsonConverterContext converterContext) { // 先调用父类方法处理标准属性 super.convertElementToJson(propertiesNode, baseElement, converterContext); UserTask userTask (UserTask) baseElement; MapString, ListExtensionElement extensionElements userTask.getExtensionElements(); // 将扩展元素中的自定义属性值写入JSON节点 writeExtensionElementToJson(extensionElements, OVER_TIME_HANDLER_TYPE, propertiesNode); // writeExtensionElementToJson(extensionElements, MY_CUSTOM_FIELD, propertiesNode); } Override protected FlowElement convertJsonToElement(JsonNode elementNode, JsonNode modelNode, MapString, JsonNode shapeMap, BpmnJsonConverterContext converterContext) { // 先调用父类方法转换基础元素 FlowElement flowElement super.convertJsonToElement(elementNode, modelNode, shapeMap, converterContext); UserTask userTask (UserTask) flowElement; // 将JSON节点中的自定义属性值读出来存入BPMN扩展元素 addExtensionElementFromJson(userTask, elementNode, OVER_TIME_HANDLER_TYPE); // addExtensionElementFromJson(userTask, elementNode, MY_CUSTOM_FIELD); return userTask; } /** * 辅助方法将扩展元素值写入JSON */ private void writeExtensionElementToJson(MapString, ListExtensionElement extensionElements, String propertyName, ObjectNode propertiesNode) { if (extensionElements ! null extensionElements.containsKey(propertyName)) { ListExtensionElement elementList extensionElements.get(propertyName); if (elementList ! null !elementList.isEmpty()) { String value elementList.get(0).getElementText(); setPropertyValue(propertyName, value, propertiesNode); } } } /** * 辅助方法从JSON创建扩展元素 */ private void addExtensionElementFromJson(UserTask userTask, JsonNode elementNode, String propertyName) { String propertyValue getPropertyValueAsString(propertyName, elementNode); if (propertyValue ! null !propertyValue.isEmpty()) { ExtensionElement extensionElement new ExtensionElement(); extensionElement.setName(propertyName); // 使用Flowable扩展命名空间这是标准做法 extensionElement.setNamespacePrefix(flowable); extensionElement.setNamespace(BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE); extensionElement.setElementText(propertyValue); userTask.addExtensionElement(extensionElement); } } // 静态注册方法至关重要 public static void fillTypes(MapString, Class? extends BaseBpmnJsonConverter convertersToBpmnMap, MapClass? extends BaseElement, Class? extends BaseBpmnJsonConverter convertersToJsonMap) { // 注册到JSON-BPMN的映射 convertersToBpmnMap.put(STENCIL_TASK_USER, CustomUserTaskJsonConverter.class); // 注册到BPMN-JSON的映射 convertersToJsonMap.put(UserTask.class, CustomUserTaskJsonConverter.class); } }3.2 第二步创建自定义BpmnJsonConverter并注册为了让系统使用我们的转换器需要创建一个顶层的自定义BpmnJsonConverter。package com.yourcompany.flowable.extensions.converter; import org.flowable.editor.language.json.converter.BpmnJsonConverter; public class CustomBpmnJsonConverter extends BpmnJsonConverter { static { // 在静态块中注册自定义转换器 // 这会覆盖父类中对于UserTask的默认转换器映射 CustomUserTaskJsonConverter.fillTypes(convertersToBpmnMap, convertersToJsonMap); } }注册是整个后端扩展的“临门一脚”。你需要让Spring容器使用你的CustomBpmnJsonConverter而不是默认的。具体方法取决于你的项目结构方案A如果你是独立应用在配置类中声明BeanConfiguration public class FlowableExtensionConfig { Bean public BpmnJsonConverter bpmnJsonConverter() { // 返回自定义的转换器实例 return new CustomBpmnJsonConverter(); } // 如果你的应用通过某个Service使用转换器可能需要覆盖那个Service Bean // 例如覆盖 RepositoryService 相关的配置 }方案B更稳妥的方式——利用Flowable的SPI机制推荐在src/main/resources/META-INF/services/目录下创建一个文件名为org.flowable.editor.language.json.converter.BpmnJsonConverter。文件内容只有一行com.yourcompany.flowable.extensions.converter.CustomBpmnJsonConverter这样Flowable在加载时就会通过Java的ServiceLoader机制自动使用你的实现类。这是对原生代码侵入性最小的方式。3.3 第三步在流程执行中获取并使用自定义属性现在自定义属性已经能正确存储在BPMN XML的扩展元素里了。在流程运行时如何获取它呢import org.flowable.engine.RepositoryService; import org.flowable.engine.RuntimeService; import org.flowable.engine.TaskService; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.api.Task; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowElement; import org.flowable.bpmn.model.UserTask; import org.flowable.bpmn.model.ExtensionElement; import java.util.List; import java.util.Map; // ... 在你的Service或Listener中 ... public void handleTaskOvertime(String taskId) { TaskService taskService ... // 注入或获取TaskService RepositoryService repositoryService ... // 注入或获取RepositoryService Task task taskService.createTaskQuery().taskId(taskId).singleResult(); String processDefinitionId task.getProcessDefinitionId(); // 获取BPMN模型 BpmnModel bpmnModel repositoryService.getBpmnModel(processDefinitionId); FlowElement flowElement bpmnModel.getFlowElement(task.getTaskDefinitionKey()); if (flowElement instanceof UserTask) { UserTask userTask (UserTask) flowElement; MapString, ListExtensionElement extensionElements userTask.getExtensionElements(); if (extensionElements ! null) { ListExtensionElement handlerTypeElements extensionElements.get(overtimehandlertype); if (handlerTypeElements ! null !handlerTypeElements.isEmpty()) { String handlerType handlerTypeElements.get(0).getElementText(); System.out.println(当前任务的逾期处理方式为: handlerType); // 根据handlerType执行不同的业务逻辑 switch (handlerType) { case 0: // 无操作 break; case 1: // 自动转交上级逻辑 escalateToManager(task); break; case 2: // 发送提醒逻辑 sendReminder(task); break; case 3: // 终止流程逻辑 terminateProcess(task); break; default: // 处理未知值 break; } } } } }通过以上代码我们就在流程运行时动态地读取了设计阶段设定的“逾期处理方式”并据此执行不同的业务逻辑。这使得流程定义变得更加灵活和强大。4. 完整代码示例与部署验证为了确保所有环节无缝衔接我提供了一个简化的端到端验证Demo。这个Demo会模拟设计器保存的JSON并用我们的自定义转换器进行解析最后打印出扩展属性。4.1 模拟设计器JSON文件在测试资源的model-json/目录下创建model-with-overtime.json文件。{ processId: testProcess, processName: 测试流程, processReferenceId: testProcess, stencil: { id: BPMNDiagram }, childShapes: [ { resourceId: sid-start, stencil: { id: StartEvent }, outgoing: [{resourceId: flow-to-task}] }, { resourceId: sid-user-task, stencil: { id: UserTask }, properties: { overrideid: userTask1, name: 审批节点, documentation: , overtimehandlertype: 2, // 这是我们自定义的属性值设为2发送提醒 formkeydefinition: , assignee: ${initiator} }, outgoing: [{resourceId: flow-to-end}] }, { resourceId: sid-end, stencil: { id: EndEvent } } ], bounds: {lowerRight: {x: 1200, y: 1050}, upperLeft: {x: 0, y: 0}} }4.2 编写验证测试类package com.yourcompany.flowable.extensions.test; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.ExtensionElement; import org.flowable.bpmn.model.UserTask; import org.flowable.editor.language.json.converter.BpmnJsonConverter; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import java.io.InputStream; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; SpringBootTest public class CustomConverterIntegrationTest { Test public void testOvertimeHandlerPropertyConversion() throws Exception { // 1. 加载模拟的JSON模型 InputStream jsonStream getClass().getClassLoader() .getResourceAsStream(model-json/model-with-overtime.json); ObjectMapper objectMapper new ObjectMapper(); JsonNode modelNode objectMapper.readTree(jsonStream); // 2. 使用自定义转换器通过SPI或Bean注入这里假设已生效 BpmnJsonConverter converter new BpmnJsonConverter(); // 实际上这里应该是CustomBpmnJsonConverter的实例 // 注意在真实Spring上下文中应该通过Autowired注入配置好的converter Bean BpmnModel bpmnModel converter.convertToBpmnModel(modelNode); // 3. 提取用户任务并验证自定义属性 UserTask userTask (UserTask) bpmnModel.getMainProcess() .getFlowElement(userTask1); // 对应JSON中的overrideid MapString, ListExtensionElement extensions userTask.getExtensionElements(); assertThat(extensions).isNotNull(); assertThat(extensions).containsKey(overtimehandlertype); ListExtensionElement handlerElements extensions.get(overtimehandlertype); assertThat(handlerElements).hasSize(1); String handlerValue handlerElements.get(0).getElementText(); System.out.println(解析到的逾期处理方式值: handlerValue); assertThat(handlerValue).isEqualTo(2); // 验证值与JSON中设置的一致 // 4. 可选将BpmnModel转回XML查看扩展元素是否被正确写入 BpmnXMLConverter xmlConverter new BpmnXMLConverter(); byte[] xmlBytes xmlConverter.convertToXML(bpmnModel); String xmlString new String(xmlBytes); System.out.println(生成的BPMN XML片段包含扩展元素:); // 查找包含 overtimehandlertype 的XML行 xmlString.lines() .filter(line - line.contains(overtimehandlertype)) .forEach(System.out::println); // 断言XML中包含我们的扩展属性 assertThat(xmlString).contains(flowable:overtimehandlertype); assertThat(xmlString).contains(2); // 值被包裹在标签中 } }运行这个测试如果一切顺利你会看到控制台输出解析到的属性值并确认它已被正确写入BPMN XML的扩展元素中。这证明了从前端设计到后端存储的完整链路已经打通。4.3 部署与生产环境注意事项在实际项目部署时有几个细节需要特别注意前端构建如果你修改了Flowable-UI的前端源码stencilset_bpmn.json,properties.js, HTML模板需要重新构建前端资源。通常Flowable-UI项目使用Maven或Gradle插件来打包前端资源。确保执行了正确的构建命令如npm run build或mvn clean package -Pprod。版本一致性确保你扩展的Flowable前端Modeler版本与后端引擎版本一致都是6.8.0。混合版本可能导致JSON结构或API不兼容。自定义转换器的加载顺序如果通过SPIMETA-INF/services方式注册自定义转换器请确保你的Jar包在类路径中并且没有其他依赖覆盖了该服务文件。有时多个模块包含同名服务文件会导致加载不确定性。属性命名规范自定义属性的ID如overtimehandlertype建议使用小写字母和数字避免特殊字符。因为它在不同格式JSON、XML间传递简单的命名可以减少转义问题。向后兼容如果你已经在生产环境有旧的流程定义添加新属性后旧的流程定义在重新打开编辑时不会自动拥有新属性。你需要考虑是否要批量更新或者让新属性在旧定义中可为空。我在第一次实施时就因为忘记重新构建前端资源导致修改的stencilset_bpmn.json没有生效排查了半天。另一个常见的坑是在后端转换器中读取和写入扩展元素时使用的命名空间前缀必须一致否则会出现“读不到”或“写不进”的问题。我上面的示例代码使用了Flowable的标准命名空间flowable这是最稳妥的做法。