|
|
|
@ -23,6 +23,7 @@ import apelet.common.flow.util.BaseFlowIdentityExtHelper; |
|
|
|
import apelet.common.flow.util.FlowCustomExtFactory; |
|
|
|
import apelet.common.flow.util.FlowCustomExtFactory; |
|
|
|
import apelet.common.flow.vo.FlowTaskVo; |
|
|
|
import apelet.common.flow.vo.FlowTaskVo; |
|
|
|
import apelet.common.flow.vo.FlowUserInfoVo; |
|
|
|
import apelet.common.flow.vo.FlowUserInfoVo; |
|
|
|
|
|
|
|
import apelet.common.flow.vo.TaskInfoVo; |
|
|
|
import cn.hutool.core.collection.CollUtil; |
|
|
|
import cn.hutool.core.collection.CollUtil; |
|
|
|
import cn.hutool.core.convert.Convert; |
|
|
|
import cn.hutool.core.convert.Convert; |
|
|
|
import cn.hutool.core.lang.Assert; |
|
|
|
import cn.hutool.core.lang.Assert; |
|
|
|
@ -45,6 +46,7 @@ import org.flowable.common.engine.api.delegate.Expression; |
|
|
|
import org.flowable.common.engine.impl.de.odysseus.el.ExpressionFactoryImpl; |
|
|
|
import org.flowable.common.engine.impl.de.odysseus.el.ExpressionFactoryImpl; |
|
|
|
import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleContext; |
|
|
|
import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleContext; |
|
|
|
import org.flowable.common.engine.impl.identity.Authentication; |
|
|
|
import org.flowable.common.engine.impl.identity.Authentication; |
|
|
|
|
|
|
|
import org.flowable.common.engine.api.query.NativeQuery; |
|
|
|
import org.flowable.common.engine.impl.javax.el.ExpressionFactory; |
|
|
|
import org.flowable.common.engine.impl.javax.el.ExpressionFactory; |
|
|
|
import org.flowable.common.engine.impl.javax.el.ValueExpression; |
|
|
|
import org.flowable.common.engine.impl.javax.el.ValueExpression; |
|
|
|
import org.flowable.engine.*; |
|
|
|
import org.flowable.engine.*; |
|
|
|
@ -123,6 +125,8 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
private FlowVariableLogService flowVariableLogService; |
|
|
|
private FlowVariableLogService flowVariableLogService; |
|
|
|
@Autowired |
|
|
|
@Autowired |
|
|
|
private FlowTaskTimeoutJobService flowTaskTimeoutJobService; |
|
|
|
private FlowTaskTimeoutJobService flowTaskTimeoutJobService; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
private FlowTaskInstanceExtService flowTaskInstanceExtService; |
|
|
|
|
|
|
|
|
|
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -690,8 +694,8 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public MyPageData<Task> getTaskListByUserName( |
|
|
|
public MyPageData<Task> getTaskListByUserName(String username, String definitionKey, String definitionName, |
|
|
|
String username, String definitionKey, String definitionName, String taskName, MyPageParam pageParam) { |
|
|
|
String taskName, String title, MyPageParam pageParam) { |
|
|
|
TaskQuery query = this.createQuery(); |
|
|
|
TaskQuery query = this.createQuery(); |
|
|
|
if (StrUtil.isNotBlank(definitionKey)) { |
|
|
|
if (StrUtil.isNotBlank(definitionKey)) { |
|
|
|
query.processDefinitionKey(definitionKey); |
|
|
|
query.processDefinitionKey(definitionKey); |
|
|
|
@ -705,10 +709,13 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
this.buildCandidateCondition(query, username); |
|
|
|
this.buildCandidateCondition(query, username); |
|
|
|
query.orderByTaskCreateTime().desc(); |
|
|
|
query.orderByTaskCreateTime().desc(); |
|
|
|
Map<String, Boolean> multiInstanceTaskMap = new HashMap<>(); |
|
|
|
Map<String, Boolean> multiInstanceTaskMap = new HashMap<>(); |
|
|
|
long totalCount = this.countOwnedTaskList(query, username, multiInstanceTaskMap); |
|
|
|
// 标题过滤只能在 Java 侧做:标题存在我们自己的表里,Flowable 的查询构造器表达不了;
|
|
|
|
|
|
|
|
// 而本列表的分页本来就是「批扫 + 逐条过滤」的(会签实例对候选者全体可见),跟着同一个后置过滤加最自然。
|
|
|
|
|
|
|
|
TitleFilterContext titleFilter = new TitleFilterContext(title); |
|
|
|
|
|
|
|
long totalCount = this.countOwnedTaskList(query, username, multiInstanceTaskMap, titleFilter); |
|
|
|
int firstResult = (pageParam.getPageNum() - 1) * pageParam.getPageSize(); |
|
|
|
int firstResult = (pageParam.getPageNum() - 1) * pageParam.getPageSize(); |
|
|
|
List<Task> taskList = this.listOwnedTaskPage( |
|
|
|
List<Task> taskList = this.listOwnedTaskPage( |
|
|
|
query, username, firstResult, pageParam.getPageSize(), multiInstanceTaskMap); |
|
|
|
query, username, firstResult, pageParam.getPageSize(), multiInstanceTaskMap, titleFilter); |
|
|
|
return new MyPageData<>(taskList, totalCount); |
|
|
|
return new MyPageData<>(taskList, totalCount); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -716,7 +723,8 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
public long getTaskCountByUserName(String username) { |
|
|
|
public long getTaskCountByUserName(String username) { |
|
|
|
TaskQuery query = this.createQuery(); |
|
|
|
TaskQuery query = this.createQuery(); |
|
|
|
this.buildCandidateCondition(query, username); |
|
|
|
this.buildCandidateCondition(query, username); |
|
|
|
return this.countOwnedTaskList(query, username, new HashMap<>()); |
|
|
|
// 待办数量是角标,不按标题过滤
|
|
|
|
|
|
|
|
return this.countOwnedTaskList(query, username, new HashMap<>(), new TitleFilterContext(null)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -724,7 +732,8 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
* 多实例会签节点在流程图中将全部会签人同时配置为候选者,导致每个实例对所有人可见。 |
|
|
|
* 多实例会签节点在流程图中将全部会签人同时配置为候选者,导致每个实例对所有人可见。 |
|
|
|
* 这里只保留会签实例归属人自己的那一条,其他会签人的实例不参与统计。 |
|
|
|
* 这里只保留会签实例归属人自己的那一条,其他会签人的实例不参与统计。 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private long countOwnedTaskList(TaskQuery query, String username, Map<String, Boolean> multiInstanceTaskMap) { |
|
|
|
private long countOwnedTaskList(TaskQuery query, String username, Map<String, Boolean> multiInstanceTaskMap, |
|
|
|
|
|
|
|
TitleFilterContext titleFilter) { |
|
|
|
long totalCount = 0; |
|
|
|
long totalCount = 0; |
|
|
|
int cursor = 0; |
|
|
|
int cursor = 0; |
|
|
|
while (true) { |
|
|
|
while (true) { |
|
|
|
@ -733,8 +742,10 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
cursor += batch.size(); |
|
|
|
cursor += batch.size(); |
|
|
|
|
|
|
|
this.loadTitles(batch, titleFilter); |
|
|
|
for (Task task : batch) { |
|
|
|
for (Task task : batch) { |
|
|
|
if (this.isTaskOwnedByUser(task, username, multiInstanceTaskMap)) { |
|
|
|
if (this.isTaskOwnedByUser(task, username, multiInstanceTaskMap) |
|
|
|
|
|
|
|
&& this.isTitleMatched(task, titleFilter)) { |
|
|
|
totalCount++; |
|
|
|
totalCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -747,7 +758,8 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
* 多实例会签节点的实例对候选者全体可见,分页时跳过不属于当前用户的其他会签实例。 |
|
|
|
* 多实例会签节点的实例对候选者全体可见,分页时跳过不属于当前用户的其他会签实例。 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private List<Task> listOwnedTaskPage( |
|
|
|
private List<Task> listOwnedTaskPage( |
|
|
|
TaskQuery query, String username, int firstResult, int pageSize, Map<String, Boolean> multiInstanceTaskMap) { |
|
|
|
TaskQuery query, String username, int firstResult, int pageSize, Map<String, Boolean> multiInstanceTaskMap, |
|
|
|
|
|
|
|
TitleFilterContext titleFilter) { |
|
|
|
List<Task> resultList = new LinkedList<>(); |
|
|
|
List<Task> resultList = new LinkedList<>(); |
|
|
|
int skipCount = firstResult; |
|
|
|
int skipCount = firstResult; |
|
|
|
int cursor = 0; |
|
|
|
int cursor = 0; |
|
|
|
@ -757,8 +769,10 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
cursor += batch.size(); |
|
|
|
cursor += batch.size(); |
|
|
|
|
|
|
|
this.loadTitles(batch, titleFilter); |
|
|
|
for (Task task : batch) { |
|
|
|
for (Task task : batch) { |
|
|
|
if (!this.isTaskOwnedByUser(task, username, multiInstanceTaskMap)) { |
|
|
|
if (!this.isTaskOwnedByUser(task, username, multiInstanceTaskMap) |
|
|
|
|
|
|
|
|| !this.isTitleMatched(task, titleFilter)) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
if (skipCount > 0) { |
|
|
|
if (skipCount > 0) { |
|
|
|
@ -775,6 +789,83 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
|
|
|
|
* 按批懒加载标题:只查本批里还没加载过的实例,避免逐条查库。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param batch 本批任务。 |
|
|
|
|
|
|
|
* @param titleFilter 标题过滤上下文,关键字为空时直接跳过、不做任何查询。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private void loadTitles(Collection<Task> batch, TitleFilterContext titleFilter) { |
|
|
|
|
|
|
|
if (!titleFilter.enabled()) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Set<String> toLoadIdSet = new HashSet<>(); |
|
|
|
|
|
|
|
for (Task task : batch) { |
|
|
|
|
|
|
|
String processInstanceId = task.getProcessInstanceId(); |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(processInstanceId) && titleFilter.loadedInstanceIdSet.add(processInstanceId)) { |
|
|
|
|
|
|
|
toLoadIdSet.add(processInstanceId); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (CollUtil.isNotEmpty(toLoadIdSet)) { |
|
|
|
|
|
|
|
titleFilter.titleMap.putAll(flowTaskInstanceExtService.getMapByProcessInstanceIds(toLoadIdSet)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 判断任务标题是否命中关键字。 |
|
|
|
|
|
|
|
* <p>标题只认 zz_flow_task_instance_ext 的落库值:没有标题行的一律不匹配,与列表展示口径一致。</p> |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param task 任务对象。 |
|
|
|
|
|
|
|
* @param titleFilter 标题过滤上下文,关键字为空时视为命中。 |
|
|
|
|
|
|
|
* @return 是否命中。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private boolean isTitleMatched(Task task, TitleFilterContext titleFilter) { |
|
|
|
|
|
|
|
if (!titleFilter.enabled()) { |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
FlowTaskInstanceExt taskInstanceExt = |
|
|
|
|
|
|
|
titleFilter.titleMap.get(task.getProcessInstanceId() + ":" + task.getTaskDefinitionKey()); |
|
|
|
|
|
|
|
return taskInstanceExt != null && StrUtil.isNotBlank(taskInstanceExt.getTitle()) |
|
|
|
|
|
|
|
&& StrUtil.contains(taskInstanceExt.getTitle(), titleFilter.keyword); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 待办列表标题过滤的上下文。 |
|
|
|
|
|
|
|
* <p>标题存在 zz_flow_task_instance_ext 上,Flowable 的查询构造器表达不了这个条件,只能在 Java 侧过滤; |
|
|
|
|
|
|
|
* 而过滤时又要按实例懒加载标题、避免逐条查库。关键字、已加载实例集合、标题缓存这三样永远成组出现, |
|
|
|
|
|
|
|
* 抽成一个对象,免得在几个方法之间来回传六个参数。</p> |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private static class TitleFilterContext { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 标题关键字;为空表示不按标题过滤。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private final String keyword; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 已经加载过标题的实例Id,避免同一实例重复查库。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private final Set<String> loadedInstanceIdSet = new HashSet<>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 标题缓存,key 为 processInstanceId + ":" + taskKey。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private final Map<String, FlowTaskInstanceExt> titleMap = new HashMap<>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private TitleFilterContext(String keyword) { |
|
|
|
|
|
|
|
this.keyword = keyword; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 是否按标题过滤。关键字为空时整个上下文不产生任何查询。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @return 需要按标题过滤返回 true。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private boolean enabled() { |
|
|
|
|
|
|
|
return StrUtil.isNotBlank(keyword); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 判断任务是否属于当前用户。 |
|
|
|
* 判断任务是否属于当前用户。 |
|
|
|
* 多实例会签实例的元素变量 assignee 即该实例真正的归属人。 |
|
|
|
* 多实例会签实例的元素变量 assignee 即该实例真正的归属人。 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@ -906,11 +997,9 @@ 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。
|
|
|
|
// 节点标题按实例批量预载(一次 IN 查询覆盖整页),避免逐行查询造成 N+1。
|
|
|
|
Map<Long, FlowWorkOrderExt> workOrderExtMap = flowWorkOrderService |
|
|
|
Map<String, FlowTaskInstanceExt> taskInstanceExtMap = |
|
|
|
.getFlowWorkOrderExtByWorkOrderIds(workOrderList.stream() |
|
|
|
flowTaskInstanceExtService.getMapByProcessInstanceIds(procInstanceIdSet); |
|
|
|
.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<>(); |
|
|
|
@ -943,9 +1032,8 @@ 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.resolveTitleCached(flowTaskExt.getTitleTemplate(), |
|
|
|
flowTaskVo.setTitle(this.resolveTitleCached(flowWorkOrder, task.getTaskDefinitionKey(), |
|
|
|
task.getProcessInstanceId(), flowWorkOrder, flowEntry, task.getTaskDefinitionKey(), |
|
|
|
taskInstanceExtMap.get(task.getProcessInstanceId() + ":" + task.getTaskDefinitionKey()))); |
|
|
|
flowWorkOrder == null ? null : workOrderExtMap.get(flowWorkOrder.getWorkOrderId()))); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
flowTaskVoList.add(flowTaskVo); |
|
|
|
flowTaskVoList.add(flowTaskVo); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1022,8 +1110,16 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
String startUser, |
|
|
|
String startUser, |
|
|
|
String beginDate, |
|
|
|
String beginDate, |
|
|
|
String endDate, |
|
|
|
String endDate, |
|
|
|
|
|
|
|
String title, |
|
|
|
MyPageParam pageParam, |
|
|
|
MyPageParam pageParam, |
|
|
|
boolean finishedOnly) throws ParseException { |
|
|
|
boolean finishedOnly) throws ParseException { |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(title)) { |
|
|
|
|
|
|
|
// 标题存在 zz_flow_task_instance_ext 上,Flowable 的查询构造器表达不了这个条件;
|
|
|
|
|
|
|
|
// 而本接口的分页与总数全由 SQL 决定,Java 侧再过滤会把分页搞坏,所以改走原生 join。
|
|
|
|
|
|
|
|
return this.getHistoricProcessInstanceListByTitle( |
|
|
|
|
|
|
|
processDefinitionKey, processDefinitionName, startUser, beginDate, endDate, title, |
|
|
|
|
|
|
|
pageParam, finishedOnly); |
|
|
|
|
|
|
|
} |
|
|
|
HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery(); |
|
|
|
HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery(); |
|
|
|
if (StrUtil.isNotBlank(processDefinitionKey)) { |
|
|
|
if (StrUtil.isNotBlank(processDefinitionKey)) { |
|
|
|
query.processDefinitionKey(processDefinitionKey); |
|
|
|
query.processDefinitionKey(processDefinitionKey); |
|
|
|
@ -1062,13 +1158,87 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
return new MyPageData<>(instanceList, totalCount); |
|
|
|
return new MyPageData<>(instanceList, totalCount); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 按首节点标题关键字查询历史流程实例,走 Flowable 的原生 join 查询。 |
|
|
|
|
|
|
|
* <p>过滤条件逐个复刻自 {@code HistoricProcessInstanceQuery}(源码 HistoricProcessInstance.xml), |
|
|
|
|
|
|
|
* 漏一个就是 bug,且只在带关键字时复现: |
|
|
|
|
|
|
|
* processDefinitionKey→DEF.KEY_;processDefinitionName→DEF.NAME_;startedBy→RES.START_USER_ID_; |
|
|
|
|
|
|
|
* startedAfter/startedBefore→RES.START_TIME_ >= / <= (注意是闭区间); |
|
|
|
|
|
|
|
* finished→RES.END_TIME_ is not null;tenantId/withoutTenantId→RES.TENANT_ID_ 等值、或空串/NULL; |
|
|
|
|
|
|
|
* orderByProcessInstanceStartTime().desc()→order by RES.START_TIME_ desc。</p> |
|
|
|
|
|
|
|
* <p>标题口径与列表展示保持一致 —— 只取<b>首节点</b>的标题。首节点 taskKey 存在发布版本的 |
|
|
|
|
|
|
|
* {@code zz_flow_entry_publish.init_task_info} 里,随流程定义而变,所以要在 SQL 里用 JSON 函数解析。 |
|
|
|
|
|
|
|
* init_task_info 是 TEXT,可能不是合法 JSON,先用 CASE + JSON_VALID 挡一道,避免脏数据让整个查询报错。</p> |
|
|
|
|
|
|
|
* <p>表名 ACT_HI_PROCINST / ACT_RE_PROCDEF 必须写死 —— Flowable 原生查询不支持 ${prefix} 占位符。 |
|
|
|
|
|
|
|
* 本工程未配置 flowable 的 database-table-prefix,将来若配置,此处表名需同步修改; |
|
|
|
|
|
|
|
* Flowable 升级时也需复查列名与语义。</p> |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private MyPageData<HistoricProcessInstance> getHistoricProcessInstanceListByTitle( |
|
|
|
|
|
|
|
String processDefinitionKey, String processDefinitionName, String startUser, |
|
|
|
|
|
|
|
String beginDate, String endDate, String title, MyPageParam pageParam, |
|
|
|
|
|
|
|
boolean finishedOnly) throws ParseException { |
|
|
|
|
|
|
|
Map<String, Object> params = new HashMap<>(8); |
|
|
|
|
|
|
|
params.put("title", "%" + this.escapeLikeKeyword(title) + "%"); |
|
|
|
|
|
|
|
StringBuilder where = new StringBuilder(256); |
|
|
|
|
|
|
|
where.append(" WHERE TT.TITLE LIKE #{title}"); |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(processDefinitionKey)) { |
|
|
|
|
|
|
|
where.append(" AND DEF.KEY_ = #{processDefinitionKey}"); |
|
|
|
|
|
|
|
params.put("processDefinitionKey", processDefinitionKey); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(processDefinitionName)) { |
|
|
|
|
|
|
|
where.append(" AND DEF.NAME_ = #{processDefinitionName}"); |
|
|
|
|
|
|
|
params.put("processDefinitionName", processDefinitionName); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(startUser)) { |
|
|
|
|
|
|
|
where.append(" AND RES.START_USER_ID_ = #{startUser}"); |
|
|
|
|
|
|
|
params.put("startUser", startUser); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(beginDate)) { |
|
|
|
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); |
|
|
|
|
|
|
|
where.append(" AND RES.START_TIME_ >= #{beginDate}"); |
|
|
|
|
|
|
|
params.put("beginDate", sdf.parse(beginDate)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(endDate)) { |
|
|
|
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); |
|
|
|
|
|
|
|
where.append(" AND RES.START_TIME_ <= #{endDate}"); |
|
|
|
|
|
|
|
params.put("endDate", sdf.parse(endDate)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this.appendTenantCondition(where, params); |
|
|
|
|
|
|
|
if (finishedOnly) { |
|
|
|
|
|
|
|
where.append(" AND RES.END_TIME_ is not null"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// RES.* 之外必须带上 DEF 的四个别名列:原生查询复用的是 historicProcessInstanceResultMap,
|
|
|
|
|
|
|
|
// 列表回去要用 getProcessDefinitionKey()/getName(),漏了这几列这两项就是空的。
|
|
|
|
|
|
|
|
String fromJoin = " FROM ACT_HI_PROCINST RES" |
|
|
|
|
|
|
|
+ " LEFT OUTER JOIN ACT_RE_PROCDEF DEF ON RES.PROC_DEF_ID_ = DEF.ID_" |
|
|
|
|
|
|
|
+ " INNER JOIN zz_flow_entry_publish FP ON FP.process_definition_id = RES.PROC_DEF_ID_" |
|
|
|
|
|
|
|
+ " INNER JOIN zz_flow_task_instance_ext TT ON TT.PROCESS_INSTANCE_ID = RES.ID_" |
|
|
|
|
|
|
|
+ " AND TT.TASK_KEY = CASE WHEN JSON_VALID(FP.init_task_info)" |
|
|
|
|
|
|
|
+ " THEN JSON_UNQUOTE(JSON_EXTRACT(FP.init_task_info, '$.taskKey')) ELSE NULL END"; |
|
|
|
|
|
|
|
return this.executeNativeTitleQuery( |
|
|
|
|
|
|
|
historyService.createNativeHistoricProcessInstanceQuery().sql( |
|
|
|
|
|
|
|
"SELECT RES.*, DEF.KEY_ AS PROC_DEF_KEY_, DEF.NAME_ AS PROC_DEF_NAME_," |
|
|
|
|
|
|
|
+ " DEF.VERSION_ AS PROC_DEF_VERSION_, DEF.DEPLOYMENT_ID_ AS DEPLOYMENT_ID_" |
|
|
|
|
|
|
|
+ fromJoin + where + " ORDER BY RES.START_TIME_ DESC"), |
|
|
|
|
|
|
|
historyService.createNativeHistoricProcessInstanceQuery().sql("SELECT COUNT(*)" + fromJoin + where), |
|
|
|
|
|
|
|
params, pageParam); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public MyPageData<HistoricTaskInstance> getHistoricTaskInstanceFinishedList( |
|
|
|
public MyPageData<HistoricTaskInstance> getHistoricTaskInstanceFinishedList( |
|
|
|
String processDefinitionName, |
|
|
|
String processDefinitionName, |
|
|
|
String beginDate, |
|
|
|
String beginDate, |
|
|
|
String endDate, |
|
|
|
String endDate, |
|
|
|
|
|
|
|
String title, |
|
|
|
MyPageParam pageParam) throws ParseException { |
|
|
|
MyPageParam pageParam) throws ParseException { |
|
|
|
String loginName = TokenData.takeFromRequest().getLoginName(); |
|
|
|
String loginName = TokenData.takeFromRequest().getLoginName(); |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(title)) { |
|
|
|
|
|
|
|
// 标题存在 zz_flow_task_instance_ext 上,Flowable 的查询构造器表达不了这个条件;
|
|
|
|
|
|
|
|
// 而本接口的分页/排序/总数由 count()+listPage() 决定,Java 侧再过滤会让 totalCount 与页内容错乱,
|
|
|
|
|
|
|
|
// 所以带关键字时改走原生 join 查询,把过滤/排序/分页/count 全部下推到 SQL。
|
|
|
|
|
|
|
|
return this.getHistoricTaskInstanceFinishedListByTitle( |
|
|
|
|
|
|
|
processDefinitionName, beginDate, endDate, title, loginName, pageParam); |
|
|
|
|
|
|
|
} |
|
|
|
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() |
|
|
|
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery() |
|
|
|
.taskAssignee(loginName) |
|
|
|
.taskAssignee(loginName) |
|
|
|
.finished(); |
|
|
|
.finished(); |
|
|
|
@ -1100,6 +1270,109 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
return new MyPageData<>(instanceList, totalCount); |
|
|
|
return new MyPageData<>(instanceList, totalCount); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 按节点标题关键字查询当前用户已办理的任务,走 Flowable 的原生 join 查询。 |
|
|
|
|
|
|
|
* <p>过滤条件逐个复刻自 {@code HistoricTaskInstanceQuery}(源码 HistoricTaskInstance.xml),漏一个就是 bug, |
|
|
|
|
|
|
|
* 且只在带关键字时复现: |
|
|
|
|
|
|
|
* taskAssignee→ASSIGNEE_;finished→END_TIME_ is not null;taskCompletedAfter/Before→END_TIME_ > / <; |
|
|
|
|
|
|
|
* processDefinitionName→ACT_RE_PROCDEF 的 exists 子查询;taskTenantId/taskWithoutTenantId→TENANT_ID_ 等值、 |
|
|
|
|
|
|
|
* 或空串/NULL;orderByHistoricTaskInstanceEndTime().desc()→order by END_TIME_ desc。</p> |
|
|
|
|
|
|
|
* <p>表名 ACT_HI_TASKINST 必须写死 —— Flowable 的原生查询不支持 ${prefix} 占位符(实测会被渲染成字面量)。 |
|
|
|
|
|
|
|
* 本工程未配置 flowable 的 database-table-prefix,将来若配置,此处表名需同步修改; |
|
|
|
|
|
|
|
* Flowable 升级时也需复查上述列名与语义。</p> |
|
|
|
|
|
|
|
* <p>另:join 条件命中 zz_flow_task_instance_ext 的唯一键 (process_instance_id, task_key),走 eq_ref; |
|
|
|
|
|
|
|
* 但 LIKE '%kw%' 用不上索引,且选择性差,MySQL 有可能选错驱动表导致匹配集全量 filesort。 |
|
|
|
|
|
|
|
* 上线前用 EXPLAIN 确认驱动表是 ACT_HI_TASKINST,必要时把 INNER JOIN 改为 STRAIGHT_JOIN。</p> |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private MyPageData<HistoricTaskInstance> getHistoricTaskInstanceFinishedListByTitle( |
|
|
|
|
|
|
|
String processDefinitionName, String beginDate, String endDate, String title, |
|
|
|
|
|
|
|
String loginName, MyPageParam pageParam) throws ParseException { |
|
|
|
|
|
|
|
Map<String, Object> params = new HashMap<>(8); |
|
|
|
|
|
|
|
params.put("assignee", loginName); |
|
|
|
|
|
|
|
params.put("title", "%" + this.escapeLikeKeyword(title) + "%"); |
|
|
|
|
|
|
|
StringBuilder where = new StringBuilder(256); |
|
|
|
|
|
|
|
where.append(" WHERE RES.ASSIGNEE_ = #{assignee} AND RES.END_TIME_ is not null AND TT.TITLE LIKE #{title}"); |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(beginDate)) { |
|
|
|
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); |
|
|
|
|
|
|
|
where.append(" AND RES.END_TIME_ > #{beginDate}"); |
|
|
|
|
|
|
|
params.put("beginDate", sdf.parse(beginDate)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(endDate)) { |
|
|
|
|
|
|
|
SimpleDateFormat sdf = new SimpleDateFormat(MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); |
|
|
|
|
|
|
|
where.append(" AND RES.END_TIME_ < #{endDate}"); |
|
|
|
|
|
|
|
params.put("endDate", sdf.parse(endDate)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(processDefinitionName)) { |
|
|
|
|
|
|
|
where.append(" AND exists (select 1 from ACT_RE_PROCDEF D") |
|
|
|
|
|
|
|
.append(" where RES.PROC_DEF_ID_ = D.ID_ and D.NAME_ = #{processDefinitionName})"); |
|
|
|
|
|
|
|
params.put("processDefinitionName", processDefinitionName); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this.appendTenantCondition(where, params); |
|
|
|
|
|
|
|
String fromJoin = " FROM ACT_HI_TASKINST RES INNER JOIN zz_flow_task_instance_ext TT" |
|
|
|
|
|
|
|
+ " ON TT.PROCESS_INSTANCE_ID = RES.PROC_INST_ID_ AND TT.TASK_KEY = RES.TASK_DEF_KEY_"; |
|
|
|
|
|
|
|
return this.executeNativeTitleQuery( |
|
|
|
|
|
|
|
historyService.createNativeHistoricTaskInstanceQuery() |
|
|
|
|
|
|
|
.sql("SELECT RES.*" + fromJoin + where + " ORDER BY RES.END_TIME_ DESC"), |
|
|
|
|
|
|
|
historyService.createNativeHistoricTaskInstanceQuery().sql("SELECT COUNT(*)" + fromJoin + where), |
|
|
|
|
|
|
|
params, pageParam); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 转义 LIKE 的通配符,避免用户输入里的 % 和 _ 被当成通配符。 |
|
|
|
|
|
|
|
* <p>反斜杠是 MySQL 的默认转义符,自身要先转义。</p> |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param keyword 用户输入的关键字。 |
|
|
|
|
|
|
|
* @return 转义后的关键字。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private String escapeLikeKeyword(String keyword) { |
|
|
|
|
|
|
|
return keyword.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 执行「list + count 两条同 FROM/WHERE 的原生查询」,两处原生标题查询共用。 |
|
|
|
|
|
|
|
* <p>count 与 list 必须共用同一套 FROM/WHERE、且 count 不能带 ORDER BY,否则 totalCount 与页内容会对不上。</p> |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param listQuery 已设置好 sql 的 list 查询。 |
|
|
|
|
|
|
|
* @param countQuery 已设置好 sql 的 count 查询。 |
|
|
|
|
|
|
|
* @param params 绑参,会同时应用到两条查询。 |
|
|
|
|
|
|
|
* @param pageParam 分页对象。 |
|
|
|
|
|
|
|
* @param <R> 查询返回的行类型。 |
|
|
|
|
|
|
|
* @return 分页结果。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private <R> MyPageData<R> executeNativeTitleQuery( |
|
|
|
|
|
|
|
NativeQuery<?, R> listQuery, NativeQuery<?, R> countQuery, |
|
|
|
|
|
|
|
Map<String, Object> params, MyPageParam pageParam) { |
|
|
|
|
|
|
|
for (Map.Entry<String, Object> entry : params.entrySet()) { |
|
|
|
|
|
|
|
listQuery.parameter(entry.getKey(), entry.getValue()); |
|
|
|
|
|
|
|
countQuery.parameter(entry.getKey(), entry.getValue()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
long totalCount = countQuery.count(); |
|
|
|
|
|
|
|
int firstResult = (pageParam.getPageNum() - 1) * pageParam.getPageSize(); |
|
|
|
|
|
|
|
return new MyPageData<>(listQuery.listPage(firstResult, pageParam.getPageSize()), totalCount); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 追加租户过滤条件,两处原生标题查询共用。 |
|
|
|
|
|
|
|
* <p>语义与 Flowable 的 taskTenantId / taskWithoutTenantId(历史流程侧是 processInstanceTenantId / |
|
|
|
|
|
|
|
* processInstanceWithoutTenantId)保持一致:有租户Id走等值;既没有租户Id也没有 appCode 时按"无租户"处理 |
|
|
|
|
|
|
|
* (空串或 NULL);否则把 appCode 当租户。</p> |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param where 正在拼装的 WHERE 片段。 |
|
|
|
|
|
|
|
* @param params 绑参容器,tenantId 参数由本方法写入。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private void appendTenantCondition(StringBuilder where, Map<String, Object> params) { |
|
|
|
|
|
|
|
TokenData tokenData = TokenData.takeFromRequest(); |
|
|
|
|
|
|
|
if (tokenData.getTenantId() != null) { |
|
|
|
|
|
|
|
where.append(" AND RES.TENANT_ID_ = #{tenantId}"); |
|
|
|
|
|
|
|
params.put("tenantId", tokenData.getTenantId().toString()); |
|
|
|
|
|
|
|
} else if (StrUtil.isBlank(tokenData.getAppCode())) { |
|
|
|
|
|
|
|
where.append(" AND (RES.TENANT_ID_ = '' or RES.TENANT_ID_ is null)"); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
where.append(" AND RES.TENANT_ID_ = #{tenantId}"); |
|
|
|
|
|
|
|
params.put("tenantId", tokenData.getAppCode()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public List<HistoricActivityInstance> getHistoricActivityInstanceList(String processInstanceId) { |
|
|
|
public List<HistoricActivityInstance> getHistoricActivityInstanceList(String processInstanceId) { |
|
|
|
return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); |
|
|
|
return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); |
|
|
|
@ -1183,6 +1456,9 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void deleteProcessInstance(String processInstanceId) { |
|
|
|
public void deleteProcessInstance(String processInstanceId) { |
|
|
|
historyService.deleteHistoricProcessInstance(processInstanceId); |
|
|
|
historyService.deleteHistoricProcessInstance(processInstanceId); |
|
|
|
|
|
|
|
// deleteHistoricProcessInstance 会级联物理删除该实例的全部 act_hi_taskinst,
|
|
|
|
|
|
|
|
// 节点扩展里的标题行不跟着删就成了永远无用的孤儿行。
|
|
|
|
|
|
|
|
flowTaskInstanceExtService.deleteByProcessInstanceId(processInstanceId); |
|
|
|
flowMessageService.removeByProcessInstanceId(processInstanceId); |
|
|
|
flowMessageService.removeByProcessInstanceId(processInstanceId); |
|
|
|
FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId); |
|
|
|
FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId); |
|
|
|
if (workOrder == null) { |
|
|
|
if (workOrder == null) { |
|
|
|
@ -2695,22 +2971,14 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String resolveTitleCached(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder, |
|
|
|
public String resolveTitleCached(FlowWorkOrder workOrder, String taskKey, FlowTaskInstanceExt taskInstanceExt) { |
|
|
|
FlowEntry flowEntry, String taskKey, FlowWorkOrderExt workOrderExt) { |
|
|
|
// 标题只以 zz_flow_task_instance_ext 的落库值为准,不再回退到按模板实时解析。
|
|
|
|
if (workOrder != null && workOrderExt != null && StrUtil.isNotBlank(taskKey) |
|
|
|
// 这样"这个节点到底有没有落库"在列表上一眼可见,排查/联调时不会被实时兜底掩盖。
|
|
|
|
&& StrUtil.isNotBlank(workOrderExt.getTitleJson())) { |
|
|
|
if (workOrder == null || taskInstanceExt == null || StrUtil.isBlank(taskKey) |
|
|
|
try { |
|
|
|
|| StrUtil.isBlank(taskInstanceExt.getTitle())) { |
|
|
|
String cached = JSON.parseObject(workOrderExt.getTitleJson()).getString(taskKey); |
|
|
|
return null; |
|
|
|
if (StrUtil.isNotBlank(cached)) { |
|
|
|
|
|
|
|
return cached; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
// 快照解析失败不能影响列表整体,降级为实时解析。
|
|
|
|
|
|
|
|
log.warn("标题快照解析失败,降级为实时解析。workOrderId [{}], taskKey [{}]", |
|
|
|
|
|
|
|
workOrder.getWorkOrderId(), taskKey, e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
return this.resolveTitle(titleTemplate, processInstanceId, workOrder, flowEntry); |
|
|
|
return taskInstanceExt.getTitle(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -2720,13 +2988,13 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
} |
|
|
|
} |
|
|
|
String nodeKey = taskKey; |
|
|
|
String nodeKey = taskKey; |
|
|
|
if (StrUtil.isBlank(nodeKey)) { |
|
|
|
if (StrUtil.isBlank(nodeKey)) { |
|
|
|
// 首节点补写场景:create 事件触发时工单还没创建,走到这里工单已建,取当前活动任务节点即可。
|
|
|
|
// 首节点补写场景:create 事件触发时工单还没创建(工单在 flowApiService.start 返回之后才建),
|
|
|
|
Task activeTask = taskService.createTaskQuery() |
|
|
|
// 走到这里流程可能已经推进过了(autoskip 节点在 start() 内部就被 complete),
|
|
|
|
.processInstanceId(workOrder.getProcessInstanceId()).active().singleResult(); |
|
|
|
// 所以不能取「当前活动任务」,要取该定义的起始节点,与历史流程列表的口径保持一致。
|
|
|
|
if (activeTask == null) { |
|
|
|
nodeKey = this.getInitTaskKey(processDefinitionId); |
|
|
|
|
|
|
|
if (StrUtil.isBlank(nodeKey)) { |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
nodeKey = activeTask.getTaskDefinitionKey(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
FlowEntry flowEntry = StrUtil.isBlank(workOrder.getProcessDefinitionKey()) ? null |
|
|
|
FlowEntry flowEntry = StrUtil.isBlank(workOrder.getProcessDefinitionKey()) ? null |
|
|
|
: flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey()); |
|
|
|
: flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey()); |
|
|
|
@ -2740,11 +3008,38 @@ public class FlowApiServiceImpl implements FlowApiService { |
|
|
|
if (StrUtil.isBlank(title)) { |
|
|
|
if (StrUtil.isBlank(title)) { |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
flowWorkOrderService.saveTaskTitle(workOrder.getWorkOrderId(), nodeKey, title); |
|
|
|
flowTaskInstanceExtService.saveTaskTitle(workOrder.getProcessInstanceId(), processDefinitionId, nodeKey, title); |
|
|
|
return title; |
|
|
|
return title; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
|
|
|
|
* 取指定流程定义的起始节点 taskKey。 |
|
|
|
|
|
|
|
* <p>来源是该发布版本的 initTaskInfo,与历史流程列表(FlowOperationController#listHistoricProcessInstance) |
|
|
|
|
|
|
|
* 取首节点标题模板的口径保持一致。</p> |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param processDefinitionId 流程定义Id。 |
|
|
|
|
|
|
|
* @return 起始节点 taskKey,取不到返回null。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private String getInitTaskKey(String processDefinitionId) { |
|
|
|
|
|
|
|
if (StrUtil.isBlank(processDefinitionId)) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
List<FlowEntryPublish> publishList = |
|
|
|
|
|
|
|
flowEntryService.getFlowEntryPublishList(CollUtil.newHashSet(processDefinitionId)); |
|
|
|
|
|
|
|
for (FlowEntryPublish publish : publishList) { |
|
|
|
|
|
|
|
if (!StrUtil.equals(publish.getProcessDefinitionId(), processDefinitionId) |
|
|
|
|
|
|
|
|| StrUtil.isBlank(publish.getInitTaskInfo())) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
TaskInfoVo initTaskInfo = JSON.parseObject(publish.getInitTaskInfo(), TaskInfoVo.class); |
|
|
|
|
|
|
|
if (initTaskInfo != null && StrUtil.isNotBlank(initTaskInfo.getTaskKey())) { |
|
|
|
|
|
|
|
return initTaskInfo.getTaskKey(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 首节点生成标题时还没发生过任何任务完成,${submitUser} 尚未写入(它由 completeTask 设置), |
|
|
|
* 首节点生成标题时还没发生过任何任务完成,${submitUser} 尚未写入(它由 completeTask 设置), |
|
|
|
* 直接解析会留下字面量。首节点的"提交人"语义上就是发起人,故此时把模板里的 ${submitUser} 换成 ${initiator}。 |
|
|
|
* 直接解析会留下字面量。首节点的"提交人"语义上就是发起人,故此时把模板里的 ${submitUser} 换成 ${initiator}。 |
|
|
|
* 第 2 个节点起上一个节点的 complete 已写好 submitUser,原样返回。 |
|
|
|
* 第 2 个节点起上一个节点的 complete 已写好 submitUser,原样返回。 |
|
|
|
|