Browse Source

feat(flow): 添加在线表单工作流标题模板和多实例会签功能

- 在BaseFlowOnlineService中添加获取主表名和业务数据的默认方法
- 在BaseOnlineBusinessDataExtHelper中实现表名和业务数据获取辅助方法
- 在FlowApiService中添加标题解析方法并在实现类中完善解析逻辑
- 添加FlowConstant常量定义标题模板键值
- 在FlowTaskExt模型和映射文件中添加标题模板字段
- 在FlowEntryController和FlowOperationController中处理标题模板
- 实现MultiInstanceAssigneeListener多实例会签节点审批人监听器
- 在FlowEntryServiceImpl和FlowTaskExtServiceImpl中完善多实例节点处理
- 在FlowOnlineBusinessServiceImpl中实现业务数据获取方法
- 更新相关依赖版本并调整Nacos配置
- 修改日志配置并添加应用启动成功事件监听
feature/2026-07-ccc-dev
chenchuchuan 4 days ago
parent
commit
cf41dad6cf
  1. 10
      application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java
  2. 6
      application-tenant/tenant-admin/src/main/resources/bootstrap.yml
  3. 2
      application-tenant/tenant-admin/src/main/resources/logback-spring.xml
  4. 2
      common/common-flow-online/pom.xml
  5. 11
      common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java
  6. 30
      common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java
  7. 20
      common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java
  8. 5
      common/common-flow/src/main/java/apelet/common/flow/constant/FlowConstant.java
  9. 19
      common/common-flow/src/main/java/apelet/common/flow/controller/FlowEntryController.java
  10. 2
      common/common-flow/src/main/java/apelet/common/flow/controller/FlowOperationController.java
  11. 5
      common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml
  12. 76
      common/common-flow/src/main/java/apelet/common/flow/listener/MultiInstanceAssigneeListener.java
  13. 2
      common/common-flow/src/main/java/apelet/common/flow/model/FlowEntry.java
  14. 6
      common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java
  15. 12
      common/common-flow/src/main/java/apelet/common/flow/service/FlowApiService.java
  16. 15
      common/common-flow/src/main/java/apelet/common/flow/service/FlowTaskExtService.java
  17. 69
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowApiServiceImpl.java
  18. 14
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java
  19. 32
      common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowTaskExtServiceImpl.java
  20. 20
      common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java
  21. 6
      common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java
  22. 2
      common/common-generator/pom.xml
  23. 2
      common/common-online/pom.xml
  24. 4
      pom.xml

10
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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling; 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) { public static void main(String[] args) {
SpringApplication.run(TenantAdminApplication.class, args); SpringApplication.run(TenantAdminApplication.class, args);
} }
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
System.out.println("\n==============================================");
System.out.println("============= 项目启动成功 SUCCESS ============");
System.out.println("==============================================\n");
}
} }

6
application-tenant/tenant-admin/src/main/resources/bootstrap.yml

@ -10,6 +10,12 @@ spring:
exclude: exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure - com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
cloud:
nacos:
discovery:
enabled: false
config:
enabled: false
config: config:
import: import:
- classpath:tenant-admin-dev.yml - classpath:tenant-admin-dev.yml

2
application-tenant/tenant-admin/src/main/resources/logback-spring.xml

@ -5,7 +5,7 @@ scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
--> -->
<configuration scan="false" scanPeriod="60 seconds" debug="false"> <configuration scan="false" scanPeriod="60 seconds" debug="false">
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> <!-- <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>-->
<springProperty scope="context" name="logging.level" source="logging.level" defaultValue="info"/> <springProperty scope="context" name="logging.level" source="logging.level" defaultValue="info"/>
<!-- 定义日志的根目录 --> <!-- 定义日志的根目录 -->

2
common/common-flow-online/pom.xml

@ -28,7 +28,7 @@
<dependency> <dependency>
<groupId>apelet</groupId> <groupId>apelet</groupId>
<artifactId>common-orm</artifactId> <artifactId>common-orm</artifactId>
<version>1.0.0</version> <version>1.0.2</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

11
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.object.*;
import apelet.common.flow.online.listener.ApprovalSettingsByCreateListener; import apelet.common.flow.online.listener.ApprovalSettingsByCreateListener;
import apelet.common.flow.online.service.FlowEntryOnlineService; 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.BaseFlowIdentityExtHelper;
import apelet.common.flow.util.FlowCustomExtFactory; import apelet.common.flow.util.FlowCustomExtFactory;
import apelet.common.flow.util.FlowRedisKeyUtil; import apelet.common.flow.util.FlowRedisKeyUtil;
@ -258,6 +261,12 @@ public class FlowEntryOnlineServiceImpl implements FlowEntryOnlineService {
FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList); FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList);
userTask.setCandidateGroups(candidateGroupList); userTask.setCandidateGroups(candidateGroupList);
} }
// 多实例会签节点:如果没有通过前置"会签"按钮写入 assigneeList,
// 则自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。
if (userTask.hasMultiInstanceLoopCharacteristics()) {
flowApiService.addExecutionListener(userTask, MultiInstanceAssigneeListener.class, "start", null);
}
this.processFlowTaskExtListener(userTask, t); this.processFlowTaskExtListener(userTask, t);
} }
} }

30
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.model.FlowWorkOrder;
import apelet.common.flow.online.object.TransactionalFlowBusinessData; import apelet.common.flow.online.object.TransactionalFlowBusinessData;
import apelet.common.flow.util.FlowCustomExtFactory; 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.exception.OnlineRuntimeException;
import apelet.common.online.model.*; import apelet.common.online.model.*;
import apelet.common.online.model.constant.FieldKind; import apelet.common.online.model.constant.FieldKind;
import apelet.common.online.service.*; import apelet.common.online.service.*;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 在线表单和流程监听器进行数据对接时的服务实现类 * 在线表单和流程监听器进行数据对接时的服务实现类
@ -36,11 +41,16 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService {
@Autowired @Autowired
private OnlineTableService onlineTableService; private OnlineTableService onlineTableService;
@Autowired @Autowired
private OnlinePageService onlinePageService;
@Autowired
private OnlineDatasourceService onlineDatasourceService; private OnlineDatasourceService onlineDatasourceService;
@Autowired @Autowired
private OnlineDatasourceRelationService onlineDatasourceRelationService; private OnlineDatasourceRelationService onlineDatasourceRelationService;
@Autowired @Autowired
private OnlineOperationService onlineOperationService; private OnlineOperationService onlineOperationService;
@Autowired
private IOnlFormHeadService onlFormHeadService;
@PostConstruct @PostConstruct
public void doRegister() { public void doRegister() {
@ -92,6 +102,26 @@ public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService {
onlineOperationService.delete(onlineTable, relationList, dataId); 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<String, Object> dataMap = onlineOperationService.getMasterData(onlineTable, null, null, workOrder.getBusinessKey());
return dataMap == null ? null : JSON.toJSONString(dataMap);
}
private void handleTransactionalFlowBusinessData(FlowWorkOrder workOrder, String desc) { private void handleTransactionalFlowBusinessData(FlowWorkOrder workOrder, String desc) {
TransactionalFlowBusinessData eventData = TransactionalFlowBusinessData.getFromRequestAttribute(); TransactionalFlowBusinessData eventData = TransactionalFlowBusinessData.getFromRequestAttribute();
if (eventData != null) { if (eventData != null) {

20
common/common-flow/src/main/java/apelet/common/flow/base/service/BaseFlowOnlineService.java

@ -23,4 +23,24 @@ public interface BaseFlowOnlineService {
* @param workOrder 工单对象 * @param workOrder 工单对象
*/ */
void deleteBusinessData(FlowWorkOrder 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;
}
} }

5
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"; 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"; public static final String REJECT_TO_SOURCE_DATA_VAR = "rejectData";

19
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.MyModelUtil;
import apelet.common.core.util.MyPageUtil; import apelet.common.core.util.MyPageUtil;
import apelet.common.core.validator.UpdateGroup; import apelet.common.core.validator.UpdateGroup;
import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.constant.FlowTaskType; import apelet.common.flow.constant.FlowTaskType;
import apelet.common.flow.dto.FlowEntryDto; import apelet.common.flow.dto.FlowEntryDto;
import apelet.common.flow.model.FlowEntry; import apelet.common.flow.model.FlowEntry;
@ -24,12 +25,14 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.github.pagehelper.page.PageMethod; import com.github.pagehelper.page.PageMethod;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.springdoc.api.annotations.ParameterObject; import org.springdoc.api.annotations.ParameterObject;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@ -37,7 +40,10 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.groups.Default; import javax.validation.groups.Default;
import javax.xml.stream.XMLStreamException; 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.setAssignee(userTask.getAssignee());
taskInfoVo.setTaskKey(userTask.getId()); taskInfoVo.setTaskKey(userTask.getId());
taskInfoVo.setTaskType(FlowTaskType.USER_TYPE); taskInfoVo.setTaskType(FlowTaskType.USER_TYPE);
// 返回初始标题(标题模板,运行时再解析变量)。
Map<String, String> attributeMap = JSON.parseObject(userTask.getFormKey(), new TypeReference<Map<String, String>>() {
});
if (MapUtil.isNotEmpty(attributeMap)) {
java.lang.String value = attributeMap.get(FlowConstant.USER_TASK_TITLE_TEMPLATE_KEY);
if (StringUtil.isNotEmpty(value)) {
taskInfoVo.setTitle(value);
}
}
Map<String, List<ExtensionElement>> extensionMap = userTask.getExtensionElements(); Map<String, List<ExtensionElement>> extensionMap = userTask.getExtensionElements();
if (MapUtil.isNotEmpty(extensionMap)) { if (MapUtil.isNotEmpty(extensionMap)) {
taskInfoVo.setOperationList(flowTaskExtService.buildOperationListExtensionElement(extensionMap)); taskInfoVo.setOperationList(flowTaskExtService.buildOperationListExtensionElement(extensionMap));

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

@ -211,6 +211,8 @@ public class FlowOperationController {
if (StrUtil.isNotBlank(flowTaskExt.getVariableListJson())) { if (StrUtil.isNotBlank(flowTaskExt.getVariableListJson())) {
taskInfoVo.setVariableList(JSON.parseArray(flowTaskExt.getVariableListJson(), JSONObject.class)); 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); return ResponseResult.success(taskInfoVo);
} }

5
common/common-flow/src/main/java/apelet/common/flow/dao/mapper/FlowTaskExtMapper.xml

@ -14,6 +14,7 @@
<result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/> <result column="candidate_usernames" jdbcType="VARCHAR" property="candidateUsernames"/>
<result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/> <result column="copy_list_json" jdbcType="VARCHAR" property="copyListJson"/>
<result column="extra_data_json" jdbcType="VARCHAR" property="extraDataJson"/> <result column="extra_data_json" jdbcType="VARCHAR" property="extraDataJson"/>
<result column="title_template" jdbcType="VARCHAR" property="titleTemplate"/>
</resultMap> </resultMap>
<insert id="insertList"> <insert id="insertList">
@ -30,7 +31,9 @@
#{item.deptIds}, #{item.deptIds},
#{item.candidateUsernames}, #{item.candidateUsernames},
#{item.copyListJson}, #{item.copyListJson},
#{item.extraDataJson}) #{item.extraDataJson},
#{item.titleTemplate}
)
</foreach> </foreach>
</insert> </insert>
</mapper> </mapper>

76
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<String> 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<String> deptUsernameSet = extHelper.getUsernameListByDeptIds(CollUtil.newHashSet(StrUtil.split(flowTaskExt.getDeptIds(), ",")));
if (CollUtil.isNotEmpty(deptUsernameSet)) {
candidateUsernames.addAll(deptUsernameSet);
}
}
// 再看角色是否存在,通过角色id 去取用户登录名
if (StrUtil.isNotBlank(flowTaskExt.getRoleIds())) {
Set<String> 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);
}
}

2
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.annotation.RelationOneToOne;
import apelet.common.core.base.mapper.BaseModelMapper; import apelet.common.core.base.mapper.BaseModelMapper;
import apelet.common.flow.model.constant.FlowEntryStatus;
import apelet.common.flow.vo.FlowEntryVo; import apelet.common.flow.vo.FlowEntryVo;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
@ -73,6 +74,7 @@ public class FlowEntry {
/** /**
* 流程状态 * 流程状态
* @see FlowEntryStatus
*/ */
@TableField(value = "status") @TableField(value = "status")
private Integer status; private Integer status;

6
common/common-flow/src/main/java/apelet/common/flow/model/FlowTaskExt.java

@ -85,4 +85,10 @@ public class FlowTaskExt {
*/ */
@TableField(value = "extra_data_json") @TableField(value = "extra_data_json")
private String extraDataJson; private String extraDataJson;
/**
* 节点标题模板 ${startUserName}发起的报销单单号${orderNo}
*/
@TableField(value = "title_template")
private String titleTemplate;
} }

12
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.core.object.*;
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.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;
@ -634,4 +635,15 @@ public interface FlowApiService {
* @return 用户任务 * @return 用户任务
*/ */
UserTask getUserTask(String processDefinitionId, String taskKey); UserTask getUserTask(String processDefinitionId, String taskKey);
/**
* 解析用户任务节点的标题未配置标题模板时使用默认标题默认标题中的表名取自在线表单主表
* 非在线表单路由表单工作流不返回标题
*
* @param titleTemplate 节点配置的标题模板为空时使用默认标题
* @param processInstanceId 流程实例Id
* @param workOrder 流程工单对象
* @return 解析后的标题非在线表单工作流返回null
*/
String resolveTitle(String titleTemplate, String processInstanceId, FlowWorkOrder workOrder);
} }

15
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.object.FlowElementExtProperty;
import apelet.common.flow.vo.FlowUserInfoVo; import apelet.common.flow.vo.FlowUserInfoVo;
import com.alibaba.fastjson.JSONObject; 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 org.flowable.task.api.TaskInfo;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* 流程任务扩展数据操作服务接口 * 流程任务扩展数据操作服务接口
@ -44,6 +48,15 @@ public interface FlowTaskExtService extends IBaseService<FlowTaskExt, String> {
List<FlowTaskExt> getByProcessDefinitionId(String processDefinitionId); List<FlowTaskExt> getByProcessDefinitionId(String processDefinitionId);
/** /**
* 查询指定的流程定义的任务扩展对象
*
* @param processDefinitionIdList 流程引擎的定义Id
* @return 查询结果
*/
List<FlowTaskExt> getByProcessDefinitionId(Set<String> processDefinitionIdList);
/**
* 获取任务扩展信息中的候选人用户信息列表 * 获取任务扩展信息中的候选人用户信息列表
* *
* @param processInstanceId 流程引擎的实例Id * @param processInstanceId 流程引擎的实例Id

69
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.MyCommonUtil;
import apelet.common.core.util.MyDateUtil; import apelet.common.core.util.MyDateUtil;
import apelet.common.flow.cmd.RestartProcessInstanceCmd; 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.exception.FlowOperationException;
import apelet.common.flow.model.*; import apelet.common.flow.model.*;
import apelet.common.flow.object.*; 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.map.MapUtil;
import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
@ -34,8 +38,8 @@ import lombok.Cleanup;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; 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.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;
@ -49,7 +53,10 @@ import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery; import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl; import org.flowable.engine.impl.persistence.entity.ExecutionEntityImpl;
import org.flowable.engine.repository.ProcessDefinition; 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.identitylink.api.IdentityLink;
import org.flowable.task.api.Task; import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskInfo;
@ -792,6 +799,8 @@ 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));
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));
for (Task task : taskList) { for (Task task : taskList) {
FlowTaskVo flowTaskVo = new FlowTaskVo(); FlowTaskVo flowTaskVo = new FlowTaskVo();
flowTaskVo.setTaskId(task.getId()); flowTaskVo.setTaskId(task.getId());
@ -816,6 +825,10 @@ public class FlowApiServiceImpl implements FlowApiService {
if (flowWorkOrder != null) { if (flowWorkOrder != null) {
flowTaskVo.setIsDraft(flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT)); flowTaskVo.setIsDraft(flowWorkOrder.getFlowStatus().equals(FlowTaskStatus.DRAFT));
flowTaskVo.setWorkOrderCode(flowWorkOrder.getWorkOrderCode()); 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); flowTaskVoList.add(flowTaskVo);
} }
@ -2441,4 +2454,54 @@ public class FlowApiServiceImpl implements FlowApiService {
} }
return null; 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<String> 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}";
}
} }

14
common/common-flow/src/main/java/apelet/common/flow/service/impl/FlowEntryServiceImpl.java

@ -364,6 +364,15 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
startUserNameVariable.setBuiltin(true); startUserNameVariable.setBuiltin(true);
startUserNameVariable.setCreateTime(now); startUserNameVariable.setCreateTime(now);
flowEntryVariableService.saveNew(startUserNameVariable); 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<FlowEntryVariable> entryVariableList, Long entryPublishId) { private void insertEntryPublishVariables(List<FlowEntryVariable> entryVariableList, Long entryPublishId) {
@ -482,6 +491,11 @@ public class FlowEntryServiceImpl extends BaseService<FlowEntry, Long> implement
FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList); FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList);
userTask.setCandidateGroups(candidateGroupList); userTask.setCandidateGroups(candidateGroupList);
} }
// 多实例会签节点:如果没有通过前置"会签"按钮写入 assigneeList,
// 则自动从 zz_flow_task_ext.candidate_usernames 中读取审批人列表。
if (userTask.hasMultiInstanceLoopCharacteristics()) {
flowApiService.addExecutionListener(userTask, MultiInstanceAssigneeListener.class, "start", null);
}
this.processFlowTaskExtListener(userTask, t); this.processFlowTaskExtListener(userTask, t);
} }
} }

32
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.FlowApprovalType;
import apelet.common.flow.constant.FlowConstant; import apelet.common.flow.constant.FlowConstant;
import apelet.common.flow.dao.FlowTaskExtMapper; 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.FlowElementExtProperty;
import apelet.common.flow.object.FlowTaskMultiSignAssign; import apelet.common.flow.object.FlowTaskMultiSignAssign;
import apelet.common.flow.object.FlowUserTaskExtData; 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.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; 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 com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import jodd.util.StringUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*; import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.task.api.TaskInfo; import org.flowable.task.api.TaskInfo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -90,11 +96,18 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
@Override @Override
public List<FlowTaskExt> getByProcessDefinitionId(String processDefinitionId) { public List<FlowTaskExt> getByProcessDefinitionId(String processDefinitionId) {
FlowTaskExt filter = new FlowTaskExt(); return this.getByProcessDefinitionId(Collections.singleton(processDefinitionId));
filter.setProcessDefinitionId(processDefinitionId); }
return flowTaskExtMapper.selectList(new QueryWrapper<>(filter));
@Override
public List<FlowTaskExt> getByProcessDefinitionId(Set<String> processDefinitionIdList) {
if (CollUtil.isEmpty(processDefinitionIdList)) {
return Collections.emptyList();
}
return flowTaskExtMapper.selectList(new LambdaQueryWrapper<FlowTaskExt>().in(FlowTaskExt::getProcessDefinitionId, processDefinitionIdList));
} }
@Override @Override
public List<FlowUserInfoVo> getCandidateUserInfoList( public List<FlowUserInfoVo> getCandidateUserInfoList(
String processInstanceId, String processInstanceId,
@ -265,6 +278,15 @@ public class FlowTaskExtServiceImpl extends BaseService<FlowTaskExt, String> imp
if (extraDataJson != null) { if (extraDataJson != null) {
flowTaskExt.setExtraDataJson(extraDataJson.toJSONString()); flowTaskExt.setExtraDataJson(extraDataJson.toJSONString());
} }
// 解析 titleTemplate 扩展属性。
Map<String, String> attributeMap = JSON.parseObject(userTask.getFormKey(), new TypeReference<Map<String, String>>() {
});
if (MapUtil.isNotEmpty(attributeMap)) {
java.lang.String value = attributeMap.get(FlowConstant.USER_TASK_TITLE_TEMPLATE_KEY);
if (StringUtil.isNotEmpty(value)) {
flowTaskExt.setTitleTemplate(value);
}
}
Map<String, List<ExtensionElement>> extensionMap = userTask.getExtensionElements(); Map<String, List<ExtensionElement>> extensionMap = userTask.getExtensionElements();
if (MapUtil.isEmpty(extensionMap)) { if (MapUtil.isEmpty(extensionMap)) {
return flowTaskExt; return flowTaskExt;

20
common/common-flow/src/main/java/apelet/common/flow/util/BaseOnlineBusinessDataExtHelper.java

@ -48,4 +48,24 @@ public class BaseOnlineBusinessDataExtHelper {
onlineBusinessService.deleteBusinessData(workOrder); 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);
}
} }

6
common/common-flow/src/main/java/apelet/common/flow/vo/FlowTaskVo.java

@ -122,4 +122,10 @@ public class FlowTaskVo {
*/ */
@Schema(description = "是否为草稿状态") @Schema(description = "是否为草稿状态")
private Boolean isDraft; private Boolean isDraft;
/**
* 解析后的任务标题
*/
@Schema(description = "解析后的任务标题")
private String title;
} }

2
common/common-generator/pom.xml

@ -59,7 +59,7 @@
<dependency> <dependency>
<groupId>apelet</groupId> <groupId>apelet</groupId>
<artifactId>common-orm</artifactId> <artifactId>common-orm</artifactId>
<version>1.0.0</version> <version>1.0.2</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>

2
common/common-online/pom.xml

@ -76,7 +76,7 @@
<dependency> <dependency>
<groupId>apelet</groupId> <groupId>apelet</groupId>
<artifactId>common-orm</artifactId> <artifactId>common-orm</artifactId>
<version>1.0.0</version> <version>1.0.2</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

4
pom.xml

@ -135,13 +135,13 @@
<repository> <repository>
<id>nexus</id> <id>nexus</id>
<name>nexus</name> <name>nexus</name>
<url>http://58.48.135.45:8081/repository/maven-releases/</url> <url>http://192.168.100.25:8081/repository/maven-releases/</url>
</repository> </repository>
<snapshotRepository> <snapshotRepository>
<id>maven-snapshots</id> <id>maven-snapshots</id>
<name>maven-snapshots</name> <name>maven-snapshots</name>
<url>http://58.48.135.45:8081/repository/maven-snapshots/</url> <url>http://192.168.100.25:8081/repository/maven-snapshots/</url>
</snapshotRepository> </snapshotRepository>
</distributionManagement> </distributionManagement>

Loading…
Cancel
Save