Browse Source

perf(flow): 引入任务标题快照缓存优化查询性能

- 在工单扩展表新增 titleJson 字段,按节点存储已解析的标题快照
- 任务创建时由 FlowUserTaskListener 自动生成标题并落库
- 新增 resolveTitleCached 方法,优先读取快照,取不到回退实时解析
- 列表查询批量预载工单扩展对象,消除逐行查询导致的 N+1 问题
- 优化 resolveTitle 内部业务数据加载,避免占位符循环中重复查询
- 首节点标题在工单创建后补写,解决 create 事件先于建单的时序问题
- 对存量无快照数据自动走回退分支,无需刷数据
feature/2026-08/0812-ccc-dev
chenchuchuan 3 weeks ago
parent
commit
b650a365ba
  1. 27
      common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java
  2. 32
      common/common-flow/src/main/java/apelet/common/flow/listener/FlowUserTaskListener.java
  3. 8
      common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrderExt.java
  4. 31
      common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java
  5. 11
      common/common-flow/src/main/java/apelet/common/flow/service/FlowWorkOrderService.java
  6. 97
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java
  7. 41
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowWorkOrderServiceImpl.java

27
common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java

@ -219,7 +219,9 @@ public class FlowOperationController {
} }
FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId); FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId);
FlowEntry flowEntry = workOrder == null ? new FlowEntry() : flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey()); FlowEntry flowEntry = workOrder == null ? new FlowEntry() : flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey());
taskInfoVo.setTitle(flowApiService.resolveTitle(flowTaskExt.getTitleTemplate(), processInstanceId, workOrder, flowEntry)); // 单条详情:直接单查一次扩展对象即可,无 N+1 风险。
FlowWorkOrderExt workOrderExt = workOrder == null ? null : flowWorkOrderService.getFlowWorkOrderExtByWorkOrderId(workOrder.getWorkOrderId());
taskInfoVo.setTitle(flowApiService.resolveTitleCached(flowTaskExt.getTitleTemplate(), processInstanceId, workOrder, flowEntry, taskInfoVo.getTaskKey(), workOrderExt));
} }
return ResponseResult.success(taskInfoVo); return ResponseResult.success(taskInfoVo);
} }
@ -840,6 +842,11 @@ public class FlowOperationController {
flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet); flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet);
Map<String, FlowWorkOrder> workOrderMap = Map<String, FlowWorkOrder> workOrderMap =
workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c)); workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c));
// 标题快照按工单批量预载(一次 IN 查询覆盖整页),避免逐行查扩展对象造成 N+1。
Map<Long, FlowWorkOrderExt> workOrderExtMap = flowWorkOrderService
.getFlowWorkOrderExtByWorkOrderIds(workOrderList.stream()
.map(FlowWorkOrder::getWorkOrderId).filter(Objects::nonNull).collect(Collectors.toSet()))
.stream().collect(Collectors.toMap(FlowWorkOrderExt::getWorkOrderId, c -> c, (k1, k2) -> k1));
Set<String> processDefinitionIdSet = taskInstanceList.stream() Set<String> processDefinitionIdSet = taskInstanceList.stream()
.map(HistoricTaskInstance::getProcessDefinitionId).collect(Collectors.toSet()); .map(HistoricTaskInstance::getProcessDefinitionId).collect(Collectors.toSet());
List<FlowEntryPublish> flowEntryPublishList = List<FlowEntryPublish> flowEntryPublishList =
@ -872,8 +879,11 @@ public class FlowOperationController {
if (flowEntryPublish != null && flowTaskExt != null) { if (flowEntryPublish != null && flowTaskExt != null) {
FlowEntry flowEntry = flowEntryMap.get(flowEntryPublish.getEntryId()); FlowEntry flowEntry = flowEntryMap.get(flowEntryPublish.getEntryId());
if (flowEntry != null) { if (flowEntry != null) {
result.put("title", flowApiService.resolveTitle( Object taskDefinitionKey = result.get("taskDefinitionKey");
flowTaskExt.getTitleTemplate(), instanceId, flowWorkOrder, flowEntry)); result.put("title", flowApiService.resolveTitleCached(
flowTaskExt.getTitleTemplate(), instanceId, flowWorkOrder, flowEntry,
taskDefinitionKey == null ? null : taskDefinitionKey.toString(),
workOrderExtMap.get(flowWorkOrder.getWorkOrderId())));
} }
} }
} }
@ -928,6 +938,11 @@ public class FlowOperationController {
flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet); flowWorkOrderService.getInList(INSTANCE_ID, instanceIdSet);
Map<String, FlowWorkOrder> workOrderMap = Map<String, FlowWorkOrder> workOrderMap =
workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c)); workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c));
// 标题快照按工单批量预载(一次 IN 查询覆盖整页),避免逐行查扩展对象造成 N+1。
Map<Long, FlowWorkOrderExt> workOrderExtMap = flowWorkOrderService
.getFlowWorkOrderExtByWorkOrderIds(workOrderList.stream()
.map(FlowWorkOrder::getWorkOrderId).filter(Objects::nonNull).collect(Collectors.toSet()))
.stream().collect(Collectors.toMap(FlowWorkOrderExt::getWorkOrderId, c -> c, (k1, k2) -> k1));
Set<String> processDefinitionIdSet = pageData.getDataList().stream() Set<String> processDefinitionIdSet = pageData.getDataList().stream()
.map(HistoricProcessInstance::getProcessDefinitionId).collect(Collectors.toSet()); .map(HistoricProcessInstance::getProcessDefinitionId).collect(Collectors.toSet());
Map<String, FlowEntryPublish> flowEntryPublishMap = new HashMap<>(); Map<String, FlowEntryPublish> flowEntryPublishMap = new HashMap<>();
@ -974,8 +989,10 @@ public class FlowOperationController {
if (flowEntryPublish != null && flowTaskExt != null) { if (flowEntryPublish != null && flowTaskExt != null) {
FlowEntry flowEntry = flowEntryMap.get(flowEntryPublish.getEntryId()); FlowEntry flowEntry = flowEntryMap.get(flowEntryPublish.getEntryId());
if (flowEntry != null) { if (flowEntry != null) {
data.put("title", flowApiService.resolveTitle( data.put("title", flowApiService.resolveTitleCached(
flowTaskExt.getTitleTemplate(), instance.getId(), workOrder, flowEntry)); flowTaskExt.getTitleTemplate(), instance.getId(), workOrder, flowEntry,
initTaskKeyMap.get(instance.getProcessDefinitionId()),
workOrderExtMap.get(workOrder.getWorkOrderId())));
} }
} }
} }

32
common/common-flow/src/main/java/apelet/common/flow/listener/FlowUserTaskListener.java

@ -2,6 +2,9 @@ package apelet.common.flow.listener;
import apelet.common.core.util.ApplicationContextHolder; import apelet.common.core.util.ApplicationContextHolder;
import apelet.common.flow.constant.FlowConstant; import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.model.FlowWorkOrder;
import apelet.common.flow.service.FlowApiService;
import apelet.common.flow.service.FlowWorkOrderService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RuntimeService; import org.flowable.engine.RuntimeService;
import org.flowable.engine.delegate.TaskListener; import org.flowable.engine.delegate.TaskListener;
@ -21,6 +24,12 @@ public class FlowUserTaskListener implements TaskListener {
private final transient RuntimeService runtimeService = private final transient RuntimeService runtimeService =
ApplicationContextHolder.getBean(RuntimeService.class); ApplicationContextHolder.getBean(RuntimeService.class);
private final transient FlowWorkOrderService flowWorkOrderService =
ApplicationContextHolder.getBean(FlowWorkOrderService.class);
private final transient FlowApiService flowApiService =
ApplicationContextHolder.getBean(FlowApiService.class);
@Override @Override
public void notify(DelegateTask delegateTask) { public void notify(DelegateTask delegateTask) {
Map<String, Object> variables = delegateTask.getVariables(); Map<String, Object> variables = delegateTask.getVariables();
@ -28,5 +37,28 @@ public class FlowUserTaskListener implements TaskListener {
delegateTask.setAssignee(variables.get(FlowConstant.DELEGATE_ASSIGNEE_VAR).toString()); delegateTask.setAssignee(variables.get(FlowConstant.DELEGATE_ASSIGNEE_VAR).toString());
runtimeService.removeVariableLocal(delegateTask.getExecutionId(), FlowConstant.DELEGATE_ASSIGNEE_VAR); runtimeService.removeVariableLocal(delegateTask.getExecutionId(), FlowConstant.DELEGATE_ASSIGNEE_VAR);
} }
this.buildTaskTitle(delegateTask);
}
/**
* 任务创建时按该节点配置的标题模板生成标题,写入工单扩展的 title_json,查询时直接读取。
* <p>首节点的 create 事件发生在工单创建之前(工单在 flowApiService.start 返回之后才建),此处取不到工单,
* 交由 FlowWorkOrderService#saveNew 在建单后补写,所以这里直接返回。</p>
*/
private void buildTaskTitle(DelegateTask delegateTask) {
try {
FlowWorkOrder workOrder =
flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(delegateTask.getProcessInstanceId());
if (workOrder == null) {
// 首节点:工单尚未创建,交由 saveNew 补写。
return;
}
flowApiService.buildAndSaveTaskTitle(
workOrder, delegateTask.getProcessDefinitionId(), delegateTask.getTaskDefinitionKey());
} catch (Exception e) {
// 标题属于展示字段,生成失败不能阻断流程流转。
log.warn("生成节点标题快照失败。processInstanceId [{}], taskKey [{}]",
delegateTask.getProcessInstanceId(), delegateTask.getTaskDefinitionKey(), e);
}
} }
} }

8
common/common-flow/src/main/java/apelet/common/flow/model/FlowWorkOrderExt.java

@ -40,6 +40,14 @@ public class FlowWorkOrderExt {
private String businessData; private String businessData;
/** /**
* 节点标题快照,JSON:{"taskKey": "解析后的标题"}。
* <p>工单是「一个流程实例一行」,标题则是「实例 × 节点」级,故按 BPMN 节点Id(taskKey) 存成 JSON 承载多节点。
* 由 FlowUserTaskListener 在任务创建时写入,查询时优先读取,取不到再回退实时解析。</p>
*/
@TableField(value = "title_json")
private String titleJson;
/**
* 更新时间。 * 更新时间。
*/ */
@TableField(value = "update_time") @TableField(value = "update_time")

31
common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java

@ -5,6 +5,7 @@ import apelet.common.flow.model.FlowEntry;
import apelet.common.flow.model.FlowTaskComment; import apelet.common.flow.model.FlowTaskComment;
import apelet.common.flow.model.FlowTaskExt; import apelet.common.flow.model.FlowTaskExt;
import apelet.common.flow.model.FlowWorkOrder; import apelet.common.flow.model.FlowWorkOrder;
import apelet.common.flow.model.FlowWorkOrderExt;
import apelet.common.flow.object.AnalyzedNode; import apelet.common.flow.object.AnalyzedNode;
import apelet.common.flow.object.FlowApiOption; import apelet.common.flow.object.FlowApiOption;
import apelet.common.flow.vo.FlowTaskVo; import apelet.common.flow.vo.FlowTaskVo;
@ -658,4 +659,34 @@ public interface FlowApiService {
* @return 解析后的标题,非在线表单工作流返回null。 * @return 解析后的标题,非在线表单工作流返回null。
*/ */
String resolveTitle(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder, FlowEntry flowEntry); String resolveTitle(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder, FlowEntry flowEntry);
/**
* 优先读取已生成的标题快照,取不到再回退 {@link #resolveTitle} 实时解析。
* <p>快照在任务创建时由 {@link #buildAndSaveTaskTitle} 写入工单扩展的 titleJson,按 taskKey 定位。
* 存量数据(改造前已跑的实例)没有快照,会自动走回退分支,因此无需刷数据。</p>
*
* @param titleTemplate 节点配置的标题模板,回退时使用。
* @param processInstanceId 流程实例Id。
* @param workOrder 流程工单对象,为空时直接回退。
* @param flowEntry 流程对象。
* @param taskKey BPMN 节点Id,用于在快照中定位。
* @param workOrderExt 工单扩展对象(调用方批量预载,避免逐行查询),可为空。
* @return 解析后的标题。
*/
String resolveTitleCached(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder,
FlowEntry flowEntry, String taskKey, FlowWorkOrderExt workOrderExt);
/**
* 生成指定节点的标题快照并落库。
* <p>取自该节点配置的 title_template,用当时的流程变量与在线表单业务数据解析后写入
* {@code zz_flow_work_order_ext.title_json}。非在线表单流程、工单为空、标题为空时不写入。</p>
* <p>注意:首节点的 create 事件触发时工单尚未创建(工单在 flowApiService.start 返回后才建),
* 该场景下 taskKey 传空由本方法自行取当前活动任务节点,供工单创建后补写首节点标题使用。</p>
*
* @param workOrder 流程工单对象。
* @param processDefinitionId 流程定义Id,用于定位节点模板。
* @param taskKey BPMN 节点Id,为空时取该实例当前的活动任务节点。
* @return 写入的标题;未写入返回null。
*/
String buildAndSaveTaskTitle(FlowWorkOrder workOrder, String processDefinitionId, String taskKey);
} }

11
common/common-flow/src/main/java/apelet/common/flow/service/FlowWorkOrderService.java

@ -160,6 +160,17 @@ public interface FlowWorkOrderService extends IBaseService<FlowWorkOrder, Long>
List<FlowWorkOrderExt> getFlowWorkOrderExtByWorkOrderIds(Set<Long> workOrderIds); List<FlowWorkOrderExt> getFlowWorkOrderExtByWorkOrderIds(Set<Long> workOrderIds);
/** /**
* 写入指定工单下某节点的标题快照。
* <p>titleJson 是「实例 × 节点」的映射,写入时按 taskKey 合并(读-改-写),
* 保留其他节点已写入的条目,不能整串覆盖。扩展行不存在时自动创建。</p>
*
* @param workOrderId 工单Id。
* @param taskKey BPMN 节点Id(等价于运行时 taskDefinitionKey)。
* @param title 解析后的标题。
*/
void saveTaskTitle(Long workOrderId, String taskKey, String title);
/**
* 移除草稿工单,同时停止已经启动的流程实例。 * 移除草稿工单,同时停止已经启动的流程实例。
* *
* @param flowWorkOrder 工单对象。 * @param flowWorkOrder 工单对象。

97
common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java

@ -906,6 +906,11 @@ public class FlowApiServiceImpl implements FlowApiService {
flowWorkOrderService.getInList("processInstanceId", procInstanceIdSet); flowWorkOrderService.getInList("processInstanceId", procInstanceIdSet);
Map<String, FlowWorkOrder> workOrderMap = Map<String, FlowWorkOrder> workOrderMap =
workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c)); workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c));
// 标题快照按工单批量预载(一次 IN 查询覆盖整页),避免逐行查扩展对象造成 N+1。
Map<Long, FlowWorkOrderExt> workOrderExtMap = flowWorkOrderService
.getFlowWorkOrderExtByWorkOrderIds(workOrderList.stream()
.map(FlowWorkOrder::getWorkOrderId).filter(Objects::nonNull).collect(Collectors.toSet()))
.stream().collect(Collectors.toMap(FlowWorkOrderExt::getWorkOrderId, c -> c, (k1, k2) -> k1));
List<FlowTaskExt> allFlowTaskExtList = flowTaskExtService.getByProcessDefinitionId(processDefinitionIdSet); List<FlowTaskExt> allFlowTaskExtList = flowTaskExtService.getByProcessDefinitionId(processDefinitionIdSet);
Map<String, FlowTaskExt> flowTaskExtMap = allFlowTaskExtList.stream().collect(Collectors.toMap(t -> t.getProcessDefinitionId() + ":" + t.getTaskId(), t -> t, (k1, k2) -> k1)); Map<String, FlowTaskExt> flowTaskExtMap = allFlowTaskExtList.stream().collect(Collectors.toMap(t -> t.getProcessDefinitionId() + ":" + t.getTaskId(), t -> t, (k1, k2) -> k1));
Map<String, Boolean> multiInstanceTaskMap = new HashMap<>(); Map<String, Boolean> multiInstanceTaskMap = new HashMap<>();
@ -938,7 +943,9 @@ public class FlowApiServiceImpl implements FlowApiService {
FlowTaskExt flowTaskExt = flowTaskExtMap.get(task.getProcessDefinitionId() + ":" + task.getTaskDefinitionKey()); FlowTaskExt flowTaskExt = flowTaskExtMap.get(task.getProcessDefinitionId() + ":" + task.getTaskDefinitionKey());
FlowEntry flowEntry = flowEntryMap.get(flowEntryPublishMap.get(task.getProcessDefinitionId()).getEntryId()); FlowEntry flowEntry = flowEntryMap.get(flowEntryPublishMap.get(task.getProcessDefinitionId()).getEntryId());
if (flowTaskExt != null && flowEntry != null) { if (flowTaskExt != null && flowEntry != null) {
flowTaskVo.setTitle(this.resolveTitle(flowTaskExt.getTitleTemplate(), task.getProcessInstanceId(), flowWorkOrder, flowEntry)); flowTaskVo.setTitle(this.resolveTitleCached(flowTaskExt.getTitleTemplate(),
task.getProcessInstanceId(), flowWorkOrder, flowEntry, task.getTaskDefinitionKey(),
flowWorkOrder == null ? null : workOrderExtMap.get(flowWorkOrder.getWorkOrderId())));
} }
flowTaskVoList.add(flowTaskVo); flowTaskVoList.add(flowTaskVo);
} }
@ -2647,6 +2654,10 @@ public class FlowApiServiceImpl implements FlowApiService {
String result = titleTemplate; String result = titleTemplate;
// 1. Flowable 持久化变量: ${initiator}, ${startUserName}, ${submitUser} // 1. Flowable 持久化变量: ${initiator}, ${startUserName}, ${submitUser}
List<String> varNames = ReUtil.findAll("\\$\\{(\\w+)\\}", result, 1); List<String> varNames = ReUtil.findAll("\\$\\{(\\w+)\\}", result, 1);
// 业务数据只查一次。原来写在占位符循环内部,N 个业务占位符就调 N 次 getBusinessData(重操作:查库 +
// 字典翻译 + 附件列填充 + 关联重排),列表接口再逐行调用,会放大成 M 行 × N 占位符次查询。
JSONObject bizData = null;
boolean bizDataLoaded = false;
for (String varName : varNames) { for (String varName : varNames) {
String value = null; String value = null;
if (StrUtil.isNotBlank(processInstanceId)) { if (StrUtil.isNotBlank(processInstanceId)) {
@ -2667,10 +2678,13 @@ public class FlowApiServiceImpl implements FlowApiService {
} }
if (value == null) { if (value == null) {
// 2. 业务数据: ${number}, ${amount} 等在线表单字段。 // 2. 业务数据: ${number}, ${amount} 等在线表单字段。
String bizData = flowCustomExtFactory.getOnlineBusinessDataExtHelper().getBusinessData(workOrder, flowEntry); if (!bizDataLoaded) {
if (StrUtil.isNotBlank(bizData)) { bizDataLoaded = true;
JSONObject biz = JSON.parseObject(bizData); String bizJson = flowCustomExtFactory.getOnlineBusinessDataExtHelper().getBusinessData(workOrder, flowEntry);
value = biz.getString(varName); bizData = StrUtil.isBlank(bizJson) ? null : JSON.parseObject(bizJson);
}
if (bizData != null) {
value = bizData.getString(varName);
} }
} }
if (value != null) { if (value != null) {
@ -2680,6 +2694,79 @@ public class FlowApiServiceImpl implements FlowApiService {
return result; return result;
} }
@Override
public String resolveTitleCached(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder,
FlowEntry flowEntry, String taskKey, FlowWorkOrderExt workOrderExt) {
if (workOrder != null && workOrderExt != null && StrUtil.isNotBlank(taskKey)
&& StrUtil.isNotBlank(workOrderExt.getTitleJson())) {
try {
String cached = JSON.parseObject(workOrderExt.getTitleJson()).getString(taskKey);
if (StrUtil.isNotBlank(cached)) {
return cached;
}
} catch (Exception e) {
// 快照解析失败不能影响列表整体,降级为实时解析。
log.warn("标题快照解析失败,降级为实时解析。workOrderId [{}], taskKey [{}]",
workOrder.getWorkOrderId(), taskKey, e);
}
}
return this.resolveTitle(titleTemplate, processInstanceId, workOrder, flowEntry);
}
@Override
public String buildAndSaveTaskTitle(FlowWorkOrder workOrder, String processDefinitionId, String taskKey) {
if (workOrder == null || StrUtil.isBlank(workOrder.getProcessInstanceId())) {
return null;
}
String nodeKey = taskKey;
if (StrUtil.isBlank(nodeKey)) {
// 首节点补写场景:create 事件触发时工单还没创建,走到这里工单已建,取当前活动任务节点即可。
Task activeTask = taskService.createTaskQuery()
.processInstanceId(workOrder.getProcessInstanceId()).active().singleResult();
if (activeTask == null) {
return null;
}
nodeKey = activeTask.getTaskDefinitionKey();
}
FlowEntry flowEntry = StrUtil.isBlank(workOrder.getProcessDefinitionKey()) ? null
: flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey());
if (flowEntry == null) {
return null;
}
FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId(processDefinitionId, nodeKey);
String titleTemplate = flowTaskExt == null ? null : flowTaskExt.getTitleTemplate();
titleTemplate = this.replaceSubmitUserWhenAbsent(titleTemplate, workOrder.getProcessInstanceId());
String title = this.resolveTitle(titleTemplate, workOrder.getProcessInstanceId(), workOrder, flowEntry);
if (StrUtil.isBlank(title)) {
return null;
}
flowWorkOrderService.saveTaskTitle(workOrder.getWorkOrderId(), nodeKey, title);
return title;
}
/**
* 首节点生成标题时还没发生过任何任务完成,${submitUser} 尚未写入(它由 completeTask 设置),
* 直接解析会留下字面量。首节点的"提交人"语义上就是发起人,故此时把模板里的 ${submitUser} 换成 ${initiator}。
* 第 2 个节点起上一个节点的 complete 已写好 submitUser,原样返回。
*/
private String replaceSubmitUserWhenAbsent(String titleTemplate, String processInstanceId) {
if (StrUtil.isBlank(titleTemplate) || StrUtil.isBlank(processInstanceId)
|| !titleTemplate.contains("${" + FlowConstant.SUBMIT_USER_VAR + "}")) {
return titleTemplate;
}
Object submitUser = null;
try {
submitUser = runtimeService.getVariable(processInstanceId, FlowConstant.SUBMIT_USER_VAR);
} catch (Exception e) {
submitUser = this.getHistoricProcessInstanceVariable(processInstanceId, FlowConstant.SUBMIT_USER_VAR);
}
if (submitUser != null && StrUtil.isNotBlank(submitUser.toString())) {
return titleTemplate;
}
return titleTemplate.replace("${" + FlowConstant.SUBMIT_USER_VAR + "}",
"${" + FlowConstant.PROC_INSTANCE_INITIATOR_VAR + "}");
}
private String buildDefaultTitle(FlowEntry flowEntry) { private String buildDefaultTitle(FlowEntry flowEntry) {
String tableName = null; String tableName = null;
if (flowEntry != null && flowEntry.getPageId() != null) { if (flowEntry != null && flowEntry.getPageId() != null) {

41
common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowWorkOrderServiceImpl.java

@ -113,6 +113,12 @@ public class FlowWorkOrderServiceImpl extends BaseService<FlowWorkOrder, Long> i
// 并再次提交当前的工单数据。 // 并再次提交当前的工单数据。
flowWorkOrderMapper.insert(flowWorkOrder); flowWorkOrderMapper.insert(flowWorkOrder);
} }
// 首节点的 create 事件在 flowApiService.start 内部就触发了,那时工单还没建、拿不到 workOrderId,
// 所以首节点标题在这里补写一次;第 2..N 个节点由 FlowUserTaskListener 在任务创建时写入。
// 草稿路径 businessKey 为空、主表还没有数据,算不出业务字段,跳过(其首节点标题走读取侧回退)。
if (dataId != null) {
flowApiService.buildAndSaveTaskTitle(flowWorkOrder, flowWorkOrder.getProcessDefinitionId(), null);
}
return flowWorkOrder; return flowWorkOrder;
} }
@ -304,12 +310,47 @@ public class FlowWorkOrderServiceImpl extends BaseService<FlowWorkOrder, Long> i
@Override @Override
public List<FlowWorkOrderExt> getFlowWorkOrderExtByWorkOrderIds(Set<Long> workOrderIds) { public List<FlowWorkOrderExt> getFlowWorkOrderExtByWorkOrderIds(Set<Long> workOrderIds) {
// 空集合会产生非法的 IN () 语句,这里直接短路返回。
if (CollUtil.isEmpty(workOrderIds)) {
return new ArrayList<>();
}
return flowWorkOrderExtMapper.selectList( return flowWorkOrderExtMapper.selectList(
new LambdaQueryWrapper<FlowWorkOrderExt>().in(FlowWorkOrderExt::getWorkOrderId, workOrderIds)); new LambdaQueryWrapper<FlowWorkOrderExt>().in(FlowWorkOrderExt::getWorkOrderId, workOrderIds));
} }
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void saveTaskTitle(Long workOrderId, String taskKey, String title) {
if (workOrderId == null || StrUtil.isBlank(taskKey) || StrUtil.isBlank(title)) {
return;
}
FlowWorkOrderExt existExt = this.getFlowWorkOrderExtByWorkOrderId(workOrderId);
JSONObject titleJson = existExt == null || StrUtil.isBlank(existExt.getTitleJson())
? new JSONObject() : JSON.parseObject(existExt.getTitleJson());
// 同一实例的每个节点各写各的条目,这里必须是合并写入,整串覆盖会抹掉其他节点已写的标题
titleJson.put(taskKey, title);
if (existExt == null) {
// 工单扩展行并非随工单必建(只有草稿路径会建),首次写入标题时需要补一行
FlowWorkOrder flowWorkOrder = flowWorkOrderMapper.selectById(workOrderId);
FlowWorkOrderExt newExt = flowWorkOrder == null
? new FlowWorkOrderExt() : BeanUtil.copyProperties(flowWorkOrder, FlowWorkOrderExt.class);
newExt.setId(idGenerator.nextLongId());
newExt.setWorkOrderId(workOrderId);
newExt.setTitleJson(titleJson.toJSONString());
flowWorkOrderExtMapper.insert(newExt);
return;
}
// 只更新 title_json,避免把 draft_data / business_data 一并覆盖
FlowWorkOrderExt updateExt = new FlowWorkOrderExt();
updateExt.setId(existExt.getId());
updateExt.setTitleJson(titleJson.toJSONString());
updateExt.setUpdateTime(new Date());
updateExt.setUpdateUserId(TokenData.takeFromRequest() == null ? null : TokenData.takeFromRequest().getUserId());
flowWorkOrderExtMapper.updateById(updateExt);
}
@Transactional(rollbackFor = Exception.class)
@Override
public CallResult removeDraft(FlowWorkOrder flowWorkOrder) { public CallResult removeDraft(FlowWorkOrder flowWorkOrder) {
CallResult r = flowApiService.stopProcessInstance(flowWorkOrder.getProcessInstanceId(), "撤销草稿", true); CallResult r = flowApiService.stopProcessInstance(flowWorkOrder.getProcessInstanceId(), "撤销草稿", true);
if (!r.isSuccess()) { if (!r.isSuccess()) {

Loading…
Cancel
Save