一、事故現場
某年某月的一個凌晨,運維部打來電話:
“領導審批節點找不到人,流程卡住了!”
打開 BPMN 一看,差點原地去世——
<flowable:userCandidateGroups value="{SpecialtyResolver.getSpecialtyArgs('ZH','director')}"/>
少寫了一個 $,Flowable 把它當成普通字符串,結果解析不到任何候選人,任務孤零零地躺在 ACT_RU_TASK,無人可簽收。
更絕望的是:
- 流程實例已經跑到一半;
- 不能重新部署(歷史數據要對應舊定義);
- 不能手動改數據庫(生產庫動刀需三級審批)。
於是,有了下面這段“熱補丁”代碼。
二、解決思路(不碰數據庫、不重啓流程)
Flowable 在「完成任務」與「創建下一任務」之間,會拋出 TASK_CREATE 事件;
我們只需要:
- 攔截下一節點;
- 判斷是不是“寫死表達式”;
- 現場補回
$重新計算; - 把候選人寫回任務。
全程在 Java 代碼裏完成,零 SQL、零 downtime。
三、核心代碼(可直接複製)
/**
* 生產熱修復:已發實例節點候選人表達式寫死
* 適用 Flowable 6.x 任意版本
*/
private void fixFlow(Task task) {
// 1. 拿到下一節點 execution
DelegateExecution nextExec = (DelegateExecution) runtimeService
.createExecutionQuery()
.executionId(task.getExecutionId())
.singleResult();
if (nextExec == null) return;
// 2. 取 BPMN 模型
BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
String activityId = nextExec.getCurrentActivityId();
FlowElement element = bpmnModel.getMainProcess().getFlowElement(activityId);
// 3. 只修 UserTask
if (!(element instanceof UserTask)) return;
UserTask userTask = (UserTask) element;
// 4. 判斷是不是“寫死”的表達式
String raw = userTask.getCandidateGroups();
if (raw == null || !raw.startsWith("{SpecialtyResolver")) return;
// 5. 補回 $ 重新計算
String el = "${" + raw.substring(1);
ExpressionManager mgr = CommandContextUtil
.getProcessEngineConfiguration()
.getExpressionManager();
Object result = mgr.createExpression(el).getValue(nextExec);
// 6. 寫回候選人(任務已創建,直接 add)
Task nextTask = taskService.createTaskQuery()
.executionId(nextExec.getId())
.singleResult();
if (nextTask == null) return;
if (result instanceof Collection) {
((Collection<?>) result).forEach(u ->
taskService.addCandidateUser(nextTask.getId(), u.toString()));
} else if (result instanceof String) {
Arrays.stream(((String) result).split(","))
.map(String::trim)
.forEach(u -> taskService.addCandidateUser(nextTask.getId(), u));
}
log.info("熱修復完成,任務 {} 已寫入候選人", nextTask.getId());
}
調用時機:
在 completeTask() 末尾加一行 fixFlow(task); 即可,事務內執行,保證原子性。
四、效果演示
|
場景
|
修復前
|
修復後
|
|
舊實例
|
任務無候選人,流程卡住
|
候選人立即出現,可正常簽收
|
|
新實例
|
同樣生效,無需重新部署
|
零 downtime 上線
|
五、小結
- 表達式寫錯 ≠ 世界末日,事件驅動可以現場打補丁;
- 不碰數據庫、不重啓服務,生產庫也能安心改;
- 代碼只改一次,歷史實例 + 未來實例一起受益。
下次再手滑,記得先把 $ 鍵焊死!