前言
????????到本篇為止,我們已經完成了流程定義以及其 BPMN XML 本身的查詢和新增功能,那我們有有了XML之后就可以開始著手研究實現 Flowable7對流程的各種操作了,比如部署,掛起,發起等等。
????????首先第一步,我們本篇文章先來探討一下 BPMN XML 的部署(deploy)的知識點以及基于當前框架的實踐操作。
一、部署(deploy)是什么?
????????部署(deploy)是 Flowable7 中將流程定義、表單、決策規則等資源打包、解析、校驗并持久化到引擎數據庫的關鍵環節。
二、部署(deploy)發揮的作用
①?資源解析與合法性校驗
????????Flowable 在部署階段將 BPMN XML 轉為內存模型(BpmnModel
),并對流程結構(如 StartEvent、連線正確性等)進行基礎校驗,以避免運行時異常?。
②?持久化部署數據
????????部署操作會寫入多張數據庫表:
-
ACT_RE_DEPLOYMENT:部署元信息;
-
ACT_GE_BYTEARRAY:流程定義、圖像等二進制資源;
-
ACT_RE_PROCDEF:流程定義記錄,包含 key、version、deploymentId 等字段?。
③?流程定義注冊
????????只有部署后的流程定義才能被 RuntimeService.startProcessInstanceByKey()
識別并實例化,部署是流程從“設計”到“執行”的必要橋梁?。?
④?版本管理
????????每次部署同一流程 key 時,Flowable 會在 ACT_RE_PROCDEF.version
上遞增版本號,舊版可繼續服務已運行實例,新版可用于新實例,實現灰度發布和快速回滾?。
⑤?集群高可用
????????集群模式下,各節點共享同一數據庫的部署信息,確保任意節點都能加載相同流程定義,實現負載均衡與故障切換?。
⑥?審計與追蹤
????????部署記錄與資源持久化后,可通過 API 或管理界面查詢歷史部署、導出流程圖等,對合規審計和故障排查至關重要?。
三、執行部署(deploy)的必要性
①?流程實例啟動前提
????????未部署的流程定義對引擎“不可見”,無法創建實例,也無法執行任何任務或事件?。
②?提前發現設計問題
????????部署時的解析和校驗能在 CI/CD 流程中及早捕獲模型缺陷,降低生產環境風險?。
③?支持多版本并存
????????通過部署版控,可平滑升級流程、驗證新版行為,并在出現問題時迅速回滾到舊版,提高系統可靠性?。
④?資源統一管理
????????將流程、表單與決策規則集中存儲并分類(如設置 name
、category
、tenantId
),便于檢索與運維管理?。
⑤?程序化與自動化
????????無論是通過 Java API、Spring Boot 自動部署還是 REST 接口,部署都可集成至 DevOps 管道,確保每次版本發布都能穩定、可控。
四、后端:完成部署功能
① 創建一個常量類
package com.ceair.constant;/*** @author wangbaohai* @ClassName Flowable7Constants* @description: Flowable7 常量類* @date 2025年04月18日* @version: 1.0.0*/
public class Flowable7Constants {/*** 流程 xml 文件后綴*/public static final String BPMN_XML_SUFFIX = ".bpmn20.xml";}
② 創建一個流程狀態枚舉類
package com.ceair.enums;import lombok.Getter;/*** @author wangbaohai* @ClassName ProcessStatus* @description: 流程狀態* @date 2025年04月18日* @version: 1.0.0*/
@Getter
public enum ProcessStatus {/*** 0:草稿*/DRAFT(0, "草稿"),/*** 0:發布*/PUBLISH(1, "發布"),/*** 0:草稿*/DEACTIVATE(2, "停用");/*** 狀態碼*/private final Integer code;/*** 狀態描述*/private final String value;ProcessStatus(Integer code, String value) {this.code = code;this.value = value;}}
③ 創建一個流程引擎配置類
主要是為了讓Flowable支持中文,具體什么字體可以自由指定,我使用【宋體】
package com.ceair.config;import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.springframework.context.annotation.Configuration;/*** @author wangbaohai* @ClassName FlowableConfig* @description: Flowable配置類* @date 2025年04月18日* @version: 1.0.0*/
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {/*** 配置流程引擎的字體設置,以解決流程圖中中文顯示亂碼的問題。** @param engineConfiguration SpringProcessEngineConfiguration 對象,用于配置流程引擎的相關屬性。* 該參數不能為空,且應為已初始化的配置對象。** 此方法通過設置活動字體、標簽字體和注解字體為“宋體”,確保生成的流程圖中中文能夠正確顯示。*/@Overridepublic void configure(SpringProcessEngineConfiguration engineConfiguration) {// 設置流程圖中活動節點的字體為宋體,避免中文亂碼問題engineConfiguration.setActivityFontName("宋體");// 設置流程圖中標簽的字體為宋體,確保標簽中的中文正常顯示engineConfiguration.setLabelFontName("宋體");// 設置流程圖中注解的字體為宋體,防止注解內容出現中文亂碼engineConfiguration.setAnnotationFontName("宋體");}
}
④ 創建一個請求類
package com.ceair.entity.request;import lombok.Data;import java.io.Serial;
import java.io.Serializable;/*** @author wangbaohai* @ClassName DeployProcessDefinitionXmlReq* @description: 部署流程定義XML請求對象* @date 2025年04月18日* @version: 1.0.0*/
@Data
public class DeployProcessDefinitionXmlReq implements Serializable {@Serialprivate static final long serialVersionUID = 1L;/*** 流程唯一標識(業務中使用的 key)*/private String processKey;/*** 流程版本號*/private Integer processVersion;}
⑤ 創建一個部署服務
/*** 部署BPMN XML文件的函數。** 該函數接收一個包含流程定義XML部署請求的對象,并返回部署結果。** @param deployProcessDefinitionXmlReq 包含流程定義XML部署請求的參數對象。* 該對象通常包含BPMN XML文件的內容、部署的相關配置信息等。** @return Boolean 返回部署結果。* - 如果部署成功,返回true;* - 如果部署失敗,返回false。*/
Boolean deployBpmnXml(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq);
⑥ 創建服務的實現
private final RepositoryService repositoryService;/*** 部署 BPMN XML 流程定義。** @param deployProcessDefinitionXmlReq 包含部署流程定義所需信息的請求對象。* 必須包含有效的流程定義標識和相關信息。* @return 返回布爾值,表示部署是否成功。如果方法執行完成且未拋出異常,則返回 true。* @throws BusinessException 如果在部署過程中發生業務異常,則拋出此異常。* 異常信息會記錄日志并重新拋出。* @throws Exception 如果在部署過程中發生未知異常,則包裝為 BusinessException 拋出。*/
@Override
public Boolean deployBpmnXml(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {try {// 校驗請求參數的合法性,確保輸入數據符合業務要求validateRequest(deployProcessDefinitionXmlReq);// 查詢流程定義信息,獲取與請求參數匹配的流程定義對象ProcessDefinition processDefinition = fetchProcessDefinition(deployProcessDefinitionXmlReq);// 獲取流程定義對應的 BPMN XML 數據,用于后續部署操作String bpmnXml = fetchBpmnXml(processDefinition);// 使用 Flowable 的 RepositoryService 部署流程定義repositoryService.createDeployment().addString(processDefinition.getProcessKey() + Flowable7Constants.BPMN_XML_SUFFIX, bpmnXml).name(processDefinition.getProcessName()).key(processDefinition.getProcessKey()).deploy();// 將流程定義的狀態變更為 1:發布processDefinition.setProcessStatus(ProcessStatus.PUBLISH.getCode());updateById(processDefinition);} catch (BusinessException e) {// 記錄業務異常日志,并重新拋出異常以便調用方處理log.error("部署流程時出現業務異常,具體異常原因: {}", e.getMessage(), e);throw e;} catch (Exception e) {// 記錄未知異常日志,并將異常包裝為 BusinessException 拋出log.error("部署流程時出現未知異常,具體異常原因: {}", e.getMessage(), e);throw new BusinessException("部署流程時出現未知異常", e);}return true;
}// 參數校驗方法
private void validateRequest(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {if (deployProcessDefinitionXmlReq == null|| StringUtils.isBlank(deployProcessDefinitionXmlReq.getProcessKey())|| deployProcessDefinitionXmlReq.getProcessVersion() == null) {log.error("部署流程時參數校驗失敗,請重新選擇流程定義");throw new BusinessException("部署流程時參數校驗失敗,請重新選擇流程定義");}
}// 查詢流程定義信息方法
private ProcessDefinition fetchProcessDefinition(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {ProcessDefinition processDefinition = lambdaQuery().eq(ProcessDefinition::getProcessKey, deployProcessDefinitionXmlReq.getProcessKey()).eq(ProcessDefinition::getProcessVersion, deployProcessDefinitionXmlReq.getProcessVersion()).one();if (processDefinition == null) {log.error("部署流程時流程定義已不存在");throw new BusinessException("部署流程時流程定義已不存在");}return processDefinition;
}// 獲取 BPMN XML 數據方法
private String fetchBpmnXml(ProcessDefinition processDefinition) {Optional<BpmnDefinitions> bpmnDefinitions = bpmnDefinitionRepository.findById(processDefinition.getXmlMongoId());return bpmnDefinitions.map(def -> {String bpmnXml = def.getBpmnXml();if (StringUtils.isBlank(bpmnXml)) {log.error("部署流程時流程定義的 BPMN XML 數據為空");throw new BusinessException("部署流程時流程定義的 BPMN XML 數據為空");}return bpmnXml;}).orElseThrow(() -> {log.error("部署流程時流程定義的 BPMN XML 數據為空");return new BusinessException("部署流程時流程定義的 BPMN XML 數據為空");});
}
⑦ 創建接口
/*** 部署流程定義XML的接口方法。** 該方法通過接收一個部署流程定義XML的請求對象,調用服務層方法完成流程定義的部署。* 如果部署過程中發生異常,會根據異常類型返回具體的錯誤信息。** @param deployProcessDefinitionXmlReq 部署流程定義XML請求對象,包含部署所需的必要信息(必填)。* @return 返回一個Result對象,包含布爾值表示部署是否成功。* - 如果成功,返回Result.success(true)。* - 如果失敗,返回Result.error(),并附帶具體的錯誤信息。*/
@PreAuthorize("hasAnyAuthority('/api/v1/definition/deployProcessDefinitionXml')")
@Parameter(name="deployProcessDefinitionXmlReq", description = "部署流程定義XML請求對象", required = true)
@Operation(summary = "部署流程定義XML")
@PostMapping("/deployProcessDefinitionXml")
public Result<Boolean> deployProcessDefinitionXml(@RequestBody DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {try {// 調用服務層方法部署流程定義XML,并返回成功結果return Result.success(processDefinitionService.deployBpmnXml(deployProcessDefinitionXmlReq));} catch (BusinessException e) {// 捕獲業務邏輯異常,返回具體的錯誤信息return Result.error("部署流程定義失敗,發生已知業務異常,具體原因:" + e);} catch (Exception e) {// 捕獲其他未知異常,返回通用錯誤信息return Result.error("部署流程定義失敗,發生未知業務異常,具體原因:" + e);}
}
五、前端:完成部署界面
① 創建請求對象
// 發布流程 xml 請求對象
export interface PublishProcessDefinitionXmlReq {processKey?: string // 流程唯一標識(業務中使用的 key)processVersion?: number
}
② 創建請求接口
// 發布流程定義 xml
export function publishProcessDefinition(data: PublishProcessDefinitionXmlReq) {return request.post<any>({url: '/pm-process/api/v1/definition/deployProcessDefinitionXml',data,})
}
③ 引入請求接口
④ 創建按鈕方法
/*** 發布流程定義的異步函數。** @param {ProcessDefinitionVO} row - 包含流程定義信息的對象,需提供 processKey 和 processVersion 屬性。* @returns {Promise<void>} - 無返回值,函數主要通過調用接口和更新狀態來完成操作。*/
async function onPublishProcessDefinition(row: ProcessDefinitionVO) {try {// 調用發布流程定義的接口,并傳遞流程鍵和版本信息const result: any = await publishProcessDefinition({processKey: row.processKey,processVersion: row.processVersion,})// 根據接口返回的結果判斷發布是否成功,并執行相應操作if (result.success && result.code === 200) {// 如果發布成功,提示用戶操作成功ElMessage({message: '發布成功',type: 'success',})// 初始化分頁參數為默認值:當前頁為第一頁,每頁顯示10條數據currentPage.value = 1pageSize.value = 10// 清空流程名稱的輸入框內容processName.value = ''// 重新獲取流程定義數據以刷新頁面getProcessDefinitionPageData()}else {// 如果發布失敗,提示用戶具體的錯誤信息ElMessage({message: result.message,})}}catch (error) {// 捕獲異常并提取錯誤信息,確保提供有意義的錯誤提示let errorMessage = '未知錯誤'if (error instanceof Error) {errorMessage = error.message}// 顯示異常錯誤消息,提示用戶發布失敗的具體原因ElMessage({message: `發布失敗: ${errorMessage || '未知錯誤'}`,type: 'error',})}
}
⑤ 操作列增加按鈕
<!-- 發布流程功能按鈕 -->
<el-button type="primary" @click="onPublishProcessDefinition(scope.row)">
? 發布
</el-button>
六、添加權限?
① 增加按鈕
② 綁定角色權限
七、XML開啟允許發布
填寫基礎信息并且按照圖中勾選即可
八、執行發布操作以及驗證
驗證 【ACT_RE_DEPLOYMENT】表中的NAME和KEY字段都和我們自己的流程定義的對應
驗證 【ACT_GE_BYTEARRAY】
驗證【ACT_RE_PROCDEF】
后記
本篇文章的前后端倉庫地址請查詢專欄第一篇文章
本文的后端分支是 process-4
本文的前端分支是 process-6