diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java index b1eee94..e071533 100644 --- a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java +++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java @@ -4,9 +4,11 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.context.event.EventListener; /** * 租户运营管理服务启动类。 @@ -24,4 +26,12 @@ public class TenantAdminApplication { public static void main(String[] args) { SpringApplication.run(TenantAdminApplication.class, args); } + + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationReady() { + System.out.println("\n=============================================="); + System.out.println("============= 项目启动成功 SUCCESS ============"); + System.out.println("==============================================\n"); + } } diff --git a/application-tenant/tenant-admin/src/main/resources/bootstrap.yml b/application-tenant/tenant-admin/src/main/resources/bootstrap.yml index f1901e3..94fc172 100644 --- a/application-tenant/tenant-admin/src/main/resources/bootstrap.yml +++ b/application-tenant/tenant-admin/src/main/resources/bootstrap.yml @@ -10,6 +10,12 @@ spring: exclude: - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure + cloud: + nacos: + discovery: + enabled: false + config: + enabled: false config: import: - classpath:tenant-admin-dev.yml diff --git a/application-tenant/tenant-admin/src/main/resources/logback-spring.xml b/application-tenant/tenant-admin/src/main/resources/logback-spring.xml index 68533c6..0230258 100644 --- a/application-tenant/tenant-admin/src/main/resources/logback-spring.xml +++ b/application-tenant/tenant-admin/src/main/resources/logback-spring.xml @@ -5,7 +5,7 @@ scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没 debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> - + diff --git a/common/common-flow-online/pom.xml b/common/common-flow-online/pom.xml index 5904893..e1d1481 100644 --- a/common/common-flow-online/pom.xml +++ b/common/common-flow-online/pom.xml @@ -28,7 +28,7 @@ apelet common-orm - 1.0.0 + 1.0.2 \ No newline at end of file diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java index 57ca761..429557f 100644 --- a/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java @@ -12,7 +12,10 @@ import apelet.common.flow.model.constant.FlowEntryStatus; import apelet.common.flow.object.*; import apelet.common.flow.online.listener.ApprovalSettingsByCreateListener; import apelet.common.flow.online.service.FlowEntryOnlineService; -import apelet.common.flow.service.*; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowCategoryService; +import apelet.common.flow.service.FlowEntryVariableService; +import apelet.common.flow.service.FlowTaskExtService; import apelet.common.flow.util.BaseFlowIdentityExtHelper; import apelet.common.flow.util.FlowCustomExtFactory; import apelet.common.flow.util.FlowRedisKeyUtil; @@ -258,6 +261,12 @@ public class FlowEntryOnlineServiceImpl implements FlowEntryOnlineService { FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList); userTask.setCandidateGroups(candidateGroupList); } + // 多实例会签节点:如果没有通过前置"会签"按钮写入 assigneeList, + // 则自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。 + if (userTask.hasMultiInstanceLoopCharacteristics()) { + flowApiService.addExecutionListener(userTask, MultiInstanceAssigneeListener.class, "start", null); + } + this.processFlowTaskExtListener(userTask, t); } } diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java index 00ac7ea..47baa3c 100644 --- a/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java @@ -6,19 +6,24 @@ import apelet.common.flow.base.service.BaseFlowOnlineService; import apelet.common.flow.model.FlowWorkOrder; import apelet.common.flow.online.object.TransactionalFlowBusinessData; import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.generator.model.OnlFormHead; +import apelet.common.generator.service.IOnlFormHeadService; import apelet.common.online.exception.OnlineRuntimeException; import apelet.common.online.model.*; import apelet.common.online.model.constant.FieldKind; import apelet.common.online.service.*; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; +import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; import java.util.List; +import java.util.Map; /** * 在线表单和流程监听器进行数据对接时的服务实现类。 @@ -36,11 +41,16 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService { @Autowired private OnlineTableService onlineTableService; @Autowired + private OnlinePageService onlinePageService; + @Autowired private OnlineDatasourceService onlineDatasourceService; @Autowired private OnlineDatasourceRelationService onlineDatasourceRelationService; @Autowired private OnlineOperationService onlineOperationService; + @Autowired + private IOnlFormHeadService onlFormHeadService; + @PostConstruct public void doRegister() { @@ -92,6 +102,26 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService { onlineOperationService.delete(onlineTable, relationList, dataId); } + @Override + public String getMasterTableName(Long pageId) { + OnlinePage page = onlinePageService.getById(pageId); + if (page == null || page.getMasterTableId() == null) { + return null; + } + OnlFormHead onlFormHead = onlFormHeadService.getById(page.getMasterTableId()); + return onlFormHead == null || StringUtils.isBlank(onlFormHead.getTableTxt()) ? null : onlFormHead.getTableTxt(); + } + + @Override + public String getBusinessData(FlowWorkOrder workOrder) { + OnlineTable onlineTable = onlineTableService.getOnlineTableFromCache(workOrder.getOnlineTableId()); + if (onlineTable == null || workOrder.getBusinessKey() == null) { + return null; + } + Map dataMap = onlineOperationService.getMasterData(onlineTable, null, null, workOrder.getBusinessKey()); + return dataMap == null ? null : JSON.toJSONString(dataMap); + } + private void handleTransactionalFlowBusinessData(FlowWorkOrder workOrder, String desc) { TransactionalFlowBusinessData eventData = TransactionalFlowBusinessData.getFromRequestAttribute(); if (eventData != null) { diff --git a/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java b/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java index f86922f..2788ec2 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java +++ b/common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java @@ -23,4 +23,24 @@ public interface BaseFlowOnlineService { * @param workOrder 工单对象。 */ void deleteBusinessData(FlowWorkOrder workOrder); + + /** + * 根据在线表单页面Id,获取在线表单主表的表名。 + * + * @param pageId 在线表单页面Id。 + * @return 主表表名,如果无法获取返回null。 + */ + default String getMasterTableName(Long pageId) { + return null; + } + + /** + * 获取在线表单工作流的业务主表数据。 + * + * @param workOrder 工单对象。 + * @return JSON格式的业务主表数据,如果无法获取返回null。 + */ + default String getBusinessData(FlowWorkOrder workOrder) { + return null; + } } diff --git a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java index 0e0a96a..faaf71c 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java +++ b/common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java @@ -209,6 +209,11 @@ public class FlowConstant { public static final String USER_TASK_REJECT_TYPE_KEY = "rejectType"; /** + * 流程用户任务标题模板的Key。 + */ + public static final String USER_TASK_TITLE_TEMPLATE_KEY = "title"; + + /** * 驳回时携带的变量数据。 */ public static final String REJECT_TO_SOURCE_DATA_VAR = "rejectData"; diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java index fc8ef22..089459b 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java @@ -7,6 +7,7 @@ import apelet.common.core.util.MyCommonUtil; import apelet.common.core.util.MyModelUtil; import apelet.common.core.util.MyPageUtil; import apelet.common.core.validator.UpdateGroup; +import apelet.common.flow.constant.FlowConstant; import apelet.common.flow.constant.FlowTaskType; import apelet.common.flow.dto.FlowEntryDto; import apelet.common.flow.model.FlowEntry; @@ -24,12 +25,14 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; import com.github.pagehelper.page.PageMethod; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import io.swagger.v3.oas.annotations.tags.Tag; +import jodd.util.StringUtil; import lombok.extern.slf4j.Slf4j; -import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; import org.springdoc.api.annotations.ParameterObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -37,7 +40,10 @@ import org.springframework.web.bind.annotation.*; import javax.validation.groups.Default; import javax.xml.stream.XMLStreamException; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * 工作流流程定义接口。 @@ -443,6 +449,15 @@ public class FlowEntryController { taskInfoVo.setAssignee(userTask.getAssignee()); taskInfoVo.setTaskKey(userTask.getId()); taskInfoVo.setTaskType(FlowTaskType.USER_TYPE); + // 返回初始标题(标题模板,运行时再解析变量)。 + Map attributeMap = JSON.parseObject(userTask.getFormKey(), new TypeReference>() { + }); + if (MapUtil.isNotEmpty(attributeMap)) { + java.lang.String value = attributeMap.get(FlowConstant.USER_TASK_TITLE_TEMPLATE_KEY); + if (StringUtil.isNotEmpty(value)) { + taskInfoVo.setTitle(value); + } + } Map> extensionMap = userTask.getExtensionElements(); if (MapUtil.isNotEmpty(extensionMap)) { taskInfoVo.setOperationList(flowTaskExtService.buildOperationListExtensionElement(extensionMap)); diff --git a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java index f9f488c..3199fe4 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java +++ b/common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java @@ -211,6 +211,8 @@ public class FlowOperationController { if (StrUtil.isNotBlank(flowTaskExt.getVariableListJson())) { taskInfoVo.setVariableList(JSON.parseArray(flowTaskExt.getVariableListJson(), JSONObject.class)); } + FlowWorkOrder workOrder = flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(processInstanceId); + taskInfoVo.setTitle(flowApiService.resolveTitle(flowTaskExt.getTitleTemplate(), processInstanceId, workOrder)); } return ResponseResult.success(taskInfoVo); } diff --git a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml index 8ab8c5d..4109e4c 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml +++ b/common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml @@ -14,6 +14,7 @@ + @@ -30,7 +31,9 @@ #{item.deptIds}, #{item.candidateUsernames}, #{item.copyListJson}, - #{item.extraDataJson}) + #{item.extraDataJson}, + #{item.titleTemplate} + ) diff --git a/common/common-flow/src/main/java/apelet/common/flow/listener/MultiInstanceAssigneeListener.java b/common/common-flow/src/main/java/apelet/common/flow/listener/MultiInstanceAssigneeListener.java new file mode 100644 index 0000000..f89de9c --- /dev/null +++ b/common/common-flow/src/main/java/apelet/common/flow/listener/MultiInstanceAssigneeListener.java @@ -0,0 +1,76 @@ +package apelet.common.flow.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.service.FlowTaskExtService; +import apelet.common.flow.util.BaseFlowIdentityExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * 多实例会签节点审批人监听器。 + * 当多实例节点没有通过前置"会签"按钮写入 assigneeList 时, + * 自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。 + * + * @author guifc + * @date 2025-07-29 + */ +@Slf4j +public class MultiInstanceAssigneeListener implements ExecutionListener { + + private final transient FlowTaskExtService flowTaskExtService = ApplicationContextHolder.getBean(FlowTaskExtService.class); + + private final transient FlowCustomExtFactory flowCustomExtFactory = ApplicationContextHolder.getBean(FlowCustomExtFactory.class); + + + @Override + public void notify(DelegateExecution execution) { + // 如果 assigneeList 已经存在(由前置节点的会签按钮写入),则不覆盖 + if (execution.hasVariable("assigneeList")) { + return; + } + FlowTaskExt flowTaskExt = flowTaskExtService.getByProcessDefinitionIdAndTaskId( + execution.getProcessDefinitionId(), execution.getCurrentActivityId()); + if (flowTaskExt == null) { + return; + } + List candidateUsernames = new ArrayList<>(); + if (StrUtil.isNotBlank(flowTaskExt.getCandidateUsernames())) { + candidateUsernames.addAll(StrUtil.split(flowTaskExt.getCandidateUsernames(), ",")); + } else { + BaseFlowIdentityExtHelper extHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + if (extHelper == null) { + return; + } + // 先看部门是否存在,通过部门id 去取用户登录名 + if (StrUtil.isNotBlank(flowTaskExt.getDeptIds())) { + Set deptUsernameSet = extHelper.getUsernameListByDeptIds(CollUtil.newHashSet(StrUtil.split(flowTaskExt.getDeptIds(), ","))); + if (CollUtil.isNotEmpty(deptUsernameSet)) { + candidateUsernames.addAll(deptUsernameSet); + } + } + // 再看角色是否存在,通过角色id 去取用户登录名 + if (StrUtil.isNotBlank(flowTaskExt.getRoleIds())) { + Set roleUsernameSet = extHelper.getUsernameListByRoleIds(CollUtil.newHashSet(StrUtil.split(flowTaskExt.getRoleIds(), ","))); + if (CollUtil.isNotEmpty(roleUsernameSet)) { + candidateUsernames.addAll(roleUsernameSet); + } + } + } + + + if (CollUtil.isEmpty(candidateUsernames)) { + return; + } + execution.setVariable("assigneeList", candidateUsernames); + log.info("MultiInstanceAssigneeListener: set assigneeList from candidate_usernames = {}", candidateUsernames); + } +} diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java index 27b2045..71894c4 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java @@ -2,6 +2,7 @@ package apelet.common.flow.model; import apelet.common.core.annotation.RelationOneToOne; import apelet.common.core.base.mapper.BaseModelMapper; +import apelet.common.flow.model.constant.FlowEntryStatus; import apelet.common.flow.vo.FlowEntryVo; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; @@ -73,6 +74,7 @@ public class FlowEntry { /** * 流程状态。 + * @see FlowEntryStatus */ @TableField(value = "status") private Integer status; diff --git a/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java index 652f45a..e0ee7b5 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java +++ b/common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java @@ -85,4 +85,10 @@ public class FlowTaskExt { */ @TableField(value = "extra_data_json") private String extraDataJson; + + /** + * 节点标题模板,如 ${startUserName}发起的报销单,单号${orderNo}。 + */ + @TableField(value = "title_template") + private String titleTemplate; } diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java index b71f6b2..b57d856 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java @@ -3,6 +3,7 @@ package apelet.common.flow.service; import apelet.common.core.object.*; import apelet.common.flow.model.FlowTaskComment; import apelet.common.flow.model.FlowTaskExt; +import apelet.common.flow.model.FlowWorkOrder; import apelet.common.flow.object.AnalyzedNode; import apelet.common.flow.object.FlowApiOption; import apelet.common.flow.vo.FlowTaskVo; @@ -634,4 +635,15 @@ public interface FlowApiService { * @return 用户任务。 */ UserTask getUserTask(String processDefinitionId, String taskKey); + + /** + * 解析用户任务节点的标题。未配置标题模板时,使用默认标题,默认标题中的表名取自在线表单主表。 + * 非在线表单(路由表单)工作流不返回标题。 + * + * @param titleTemplate 节点配置的标题模板,为空时使用默认标题。 + * @param processInstanceId 流程实例Id。 + * @param workOrder 流程工单对象。 + * @return 解析后的标题,非在线表单工作流返回null。 + */ + String resolveTitle(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder); } diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java index 0fbe843..ef1754c 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java +++ b/common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java @@ -5,11 +5,15 @@ import apelet.common.flow.model.FlowTaskExt; import apelet.common.flow.object.FlowElementExtProperty; import apelet.common.flow.vo.FlowUserInfoVo; import com.alibaba.fastjson.JSONObject; -import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.ExtensionElement; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.UserTask; import org.flowable.task.api.TaskInfo; import java.util.List; import java.util.Map; +import java.util.Set; /** * 流程任务扩展数据操作服务接口。 @@ -44,6 +48,15 @@ public interface FlowTaskExtService extends IBaseService { List getByProcessDefinitionId(String processDefinitionId); /** + * 查询指定的流程定义的任务扩展对象。 + * + * @param processDefinitionIdList 流程引擎的定义Id。 + * @return 查询结果。 + */ + List getByProcessDefinitionId(Set processDefinitionIdList); + + + /** * 获取任务扩展信息中的候选人用户信息列表。 * * @param processInstanceId 流程引擎的实例Id。 diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java index f028fc9..b94b2f9 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java @@ -10,7 +10,10 @@ import apelet.common.core.util.DefaultDataSourceResolver; import apelet.common.core.util.MyCommonUtil; import apelet.common.core.util.MyDateUtil; import apelet.common.flow.cmd.RestartProcessInstanceCmd; -import apelet.common.flow.constant.*; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.constant.FlowBackType; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.constant.FlowTaskStatus; import apelet.common.flow.exception.FlowOperationException; import apelet.common.flow.model.*; import apelet.common.flow.object.*; @@ -25,6 +28,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; @@ -34,8 +38,8 @@ import lombok.Cleanup; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.converter.BpmnXMLConverter; -import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; 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.identity.Authentication; @@ -49,7 +53,10 @@ import org.flowable.engine.history.HistoricProcessInstance; import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; import org.flowable.engine.repository.ProcessDefinition; -import org.flowable.engine.runtime.*; +import org.flowable.engine.runtime.ChangeActivityStateBuilder; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.runtime.ProcessInstanceBuilder; import org.flowable.identitylink.api.IdentityLink; import org.flowable.task.api.Task; import org.flowable.task.api.TaskInfo; @@ -792,6 +799,8 @@ public class FlowApiServiceImpl implements FlowApiService { flowWorkOrderService.getInList("processInstanceId", procInstanceIdSet); Map workOrderMap = workOrderList.stream().collect(Collectors.toMap(FlowWorkOrder::getProcessInstanceId, c -> c)); + List allFlowTaskExtList = flowTaskExtService.getByProcessDefinitionId(processDefinitionIdSet); + Map flowTaskExtMap = allFlowTaskExtList.stream().collect(Collectors.toMap(t -> t.getProcessDefinitionId() + ":" + t.getTaskId(), t -> t, (k1, k2) -> k1)); for (Task task : taskList) { FlowTaskVo flowTaskVo = new FlowTaskVo(); flowTaskVo.setTaskId(task.getId()); @@ -816,6 +825,10 @@ public class FlowApiServiceImpl implements FlowApiService { if (flowWorkOrder != null) { flowTaskVo.setIsDraft(flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT)); flowTaskVo.setWorkOrderCode(flowWorkOrder.getWorkOrderCode()); + FlowTaskExt flowTaskExt = flowTaskExtMap.get(task.getProcessDefinitionId() + ":" + task.getTaskDefinitionKey()); + if (flowTaskExt != null) { + flowTaskVo.setTitle(this.resolveTitle(flowTaskExt.getTitleTemplate(), task.getProcessInstanceId(), flowWorkOrder)); + } } flowTaskVoList.add(flowTaskVo); } @@ -2441,4 +2454,54 @@ public class FlowApiServiceImpl implements FlowApiService { } return null; } + + @Override + public String resolveTitle(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder) { + // 非在线表单(路由表单)工作流不支持自定义标题。 + if (workOrder == null || workOrder.getOnlineTableId() == null) { + return null; + } + if (StrUtil.isBlank(titleTemplate)) { + titleTemplate = this.buildDefaultTitle(workOrder); + } + String result = titleTemplate; + // 1. Flowable 持久化变量: ${initiator}, ${startUserName}, ${submitUser} + List varNames = ReUtil.findAll("\\$\\{(\\w+)\\}", result, 1); + for (String varName : varNames) { + String value = null; + if (StrUtil.isNotBlank(processInstanceId)) { + Object v = runtimeService.getVariable(processInstanceId, varName); + if (v != null) { + value = v.toString(); + } + } + if (value == null && workOrder.getBusinessKey() != null) { + // 2. 业务数据: ${number}, ${amount} 等在线表单字段。 + String bizData = flowCustomExtFactory.getOnlineBusinessDataExtHelper().getBusinessData(workOrder); + if (StrUtil.isNotBlank(bizData)) { + JSONObject biz = JSON.parseObject(bizData); + value = biz.getString(varName); + } + } + if (value != null) { + result = result.replace("${" + varName + "}", value); + } + } + return result; + } + + private String buildDefaultTitle(FlowWorkOrder workOrder) { + String tableName = null; + if (workOrder.getOnlineTableId() != null) { + // 在线表单:FlowEntry.pageId → OnlinePage.masterTableName + FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(workOrder.getProcessDefinitionKey()); + if (flowEntry != null && flowEntry.getPageId() != null) { + tableName = flowCustomExtFactory.getOnlineBusinessDataExtHelper().getTableTxt(flowEntry.getPageId()); + } + } + if (StrUtil.isBlank(tableName)) { + tableName = "业务"; + } + return tableName + "单据,单据编号为${number}"; + } } diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java index 26e31e6..9642c75 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java @@ -364,6 +364,15 @@ public class FlowEntryServiceImpl extends BaseService implement startUserNameVariable.setBuiltin(true); startUserNameVariable.setCreateTime(now); flowEntryVariableService.saveNew(startUserNameVariable); + FlowEntryVariable assigneeListVariable = new FlowEntryVariable(); + assigneeListVariable.setVariableId(idGenerator.nextLongId()); + assigneeListVariable.setEntryId(entryId); + assigneeListVariable.setVariableName("assigneeList"); + assigneeListVariable.setShowName("会签人"); + assigneeListVariable.setVariableType(FlowVariableType.TASK); + assigneeListVariable.setBuiltin(true); + assigneeListVariable.setCreateTime(now); + flowEntryVariableService.saveNew(assigneeListVariable); } private void insertEntryPublishVariables(List entryVariableList, Long entryPublishId) { @@ -482,6 +491,11 @@ public class FlowEntryServiceImpl extends BaseService implement FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList); userTask.setCandidateGroups(candidateGroupList); } + // 多实例会签节点:如果没有通过前置"会签"按钮写入 assigneeList, + // 则自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。 + if (userTask.hasMultiInstanceLoopCharacteristics()) { + flowApiService.addExecutionListener(userTask, MultiInstanceAssigneeListener.class, "start", null); + } this.processFlowTaskExtListener(userTask, t); } } diff --git a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java index 132cd16..e2f5689 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java +++ b/common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java @@ -10,7 +10,10 @@ import apelet.common.core.util.DefaultDataSourceResolver; import apelet.common.flow.constant.FlowApprovalType; import apelet.common.flow.constant.FlowConstant; import apelet.common.flow.dao.FlowTaskExtMapper; -import apelet.common.flow.model.*; +import apelet.common.flow.model.FlowEntryVariable; +import apelet.common.flow.model.FlowMultiInstanceTrans; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTaskExt; import apelet.common.flow.object.FlowElementExtProperty; import apelet.common.flow.object.FlowTaskMultiSignAssign; import apelet.common.flow.object.FlowUserTaskExtData; @@ -25,10 +28,13 @@ import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import jodd.util.StringUtil; import lombok.extern.slf4j.Slf4j; -import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; import org.flowable.task.api.TaskInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -90,11 +96,18 @@ public class FlowTaskExtServiceImpl extends BaseService imp @Override public List getByProcessDefinitionId(String processDefinitionId) { - FlowTaskExt filter = new FlowTaskExt(); - filter.setProcessDefinitionId(processDefinitionId); - return flowTaskExtMapper.selectList(new QueryWrapper<>(filter)); + return this.getByProcessDefinitionId(Collections.singleton(processDefinitionId)); + } + + @Override + public List getByProcessDefinitionId(Set processDefinitionIdList) { + if (CollUtil.isEmpty(processDefinitionIdList)) { + return Collections.emptyList(); + } + return flowTaskExtMapper.selectList(new LambdaQueryWrapper().in(FlowTaskExt::getProcessDefinitionId, processDefinitionIdList)); } + @Override public List getCandidateUserInfoList( String processInstanceId, @@ -265,6 +278,15 @@ public class FlowTaskExtServiceImpl extends BaseService imp if (extraDataJson != null) { flowTaskExt.setExtraDataJson(extraDataJson.toJSONString()); } + // 解析 titleTemplate 扩展属性。 + Map attributeMap = JSON.parseObject(userTask.getFormKey(), new TypeReference>() { + }); + if (MapUtil.isNotEmpty(attributeMap)) { + java.lang.String value = attributeMap.get(FlowConstant.USER_TASK_TITLE_TEMPLATE_KEY); + if (StringUtil.isNotEmpty(value)) { + flowTaskExt.setTitleTemplate(value); + } + } Map> extensionMap = userTask.getExtensionElements(); if (MapUtil.isEmpty(extensionMap)) { return flowTaskExt; diff --git a/common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java b/common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java index fe7d36a..0332516 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java +++ b/common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java @@ -48,4 +48,24 @@ public class BaseOnlineBusinessDataExtHelper { onlineBusinessService.deleteBusinessData(workOrder); } } + + /** + * 根据在线表单页面Id,获取在线表单主表的表名。 + * + * @param pageId 在线表单页面Id。 + * @return 主表表名,如果无法获取返回null。 + */ + public String getTableTxt(Long pageId) { + return this.onlineBusinessService == null ? null : onlineBusinessService.getMasterTableName(pageId); + } + + /** + * 获取在线表单工作流的业务主表数据。 + * + * @param workOrder 工单对象。 + * @return JSON格式的业务主表数据,如果无法获取返回null。 + */ + public String getBusinessData(FlowWorkOrder workOrder) { + return this.onlineBusinessService == null ? null : onlineBusinessService.getBusinessData(workOrder); + } } diff --git a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java index b17cece..3a40a3b 100644 --- a/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java +++ b/common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java @@ -122,4 +122,10 @@ public class FlowTaskVo { */ @Schema(description = "是否为草稿状态") private Boolean isDraft; + + /** + * 解析后的任务标题。 + */ + @Schema(description = "解析后的任务标题") + private String title; } diff --git a/common/common-generator/pom.xml b/common/common-generator/pom.xml index 918370b..22bcac2 100644 --- a/common/common-generator/pom.xml +++ b/common/common-generator/pom.xml @@ -59,7 +59,7 @@ apelet common-orm - 1.0.0 + 1.0.2 compile diff --git a/common/common-online/pom.xml b/common/common-online/pom.xml index 3efb52a..415ae4e 100644 --- a/common/common-online/pom.xml +++ b/common/common-online/pom.xml @@ -76,7 +76,7 @@ apelet common-orm - 1.0.0 + 1.0.2 compile diff --git a/pom.xml b/pom.xml index 5bff485..fc68c9b 100644 --- a/pom.xml +++ b/pom.xml @@ -135,13 +135,13 @@ nexus nexus - http://58.48.135.45:8081/repository/maven-releases/ + http://192.168.100.25:8081/repository/maven-releases/ maven-snapshots maven-snapshots - http://58.48.135.45:8081/repository/maven-snapshots/ + http://192.168.100.25:8081/repository/maven-snapshots/