From c4633022115784c68b686c26034bc1a1cdbf4899 Mon Sep 17 00:00:00 2001 From: sunquan <287962685@qq.com> Date: Thu, 30 Jul 2026 14:39:15 +0800 Subject: [PATCH] =?UTF-8?q?add=EF=BC=9A=E6=96=B0=E5=A2=9E=E5=8A=A0common-f?= =?UTF-8?q?low-online=20=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/common-flow-online/pom.xml | 34 + .../online/aop/FlowMultiDatabaseWriteAspect.java | 88 ++ .../online/aop/FlowMultiDatabaseWriteExAspect.java | 38 + .../flow/online/config/FlowOnlineAutoConfig.java | 13 + .../flow/online/config/FlowOnlineProperties.java | 20 + .../controller/FlowOnlineOperationController.java | 1437 ++++++++++++++++++++ .../listener/ApprovalSettingsByCreateListener.java | 94 ++ .../object/TransactionalFlowBusinessData.java | 101 ++ .../online/service/FlowEntryOnlineService.java | 13 + .../online/service/FlowOnlineOperationService.java | 163 +++ .../service/impl/FlowEntryOnlineServiceImpl.java | 319 +++++ .../impl/FlowOnlineBusinessServiceImpl.java | 102 ++ .../impl/FlowOnlineOperationServiceImpl.java | 434 ++++++ .../src/main/resources/META-INF/spring.factories | 2 + common/pom.xml | 1 + 15 files changed, 2859 insertions(+) create mode 100644 common/common-flow-online/pom.xml create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteAspect.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteExAspect.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineAutoConfig.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineProperties.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/controller/FlowOnlineOperationController.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/listener/ApprovalSettingsByCreateListener.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/object/TransactionalFlowBusinessData.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowEntryOnlineService.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowOnlineOperationService.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java create mode 100644 common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineOperationServiceImpl.java create mode 100644 common/common-flow-online/src/main/resources/META-INF/spring.factories diff --git a/common/common-flow-online/pom.xml b/common/common-flow-online/pom.xml new file mode 100644 index 0000000..5904893 --- /dev/null +++ b/common/common-flow-online/pom.xml @@ -0,0 +1,34 @@ + + + + common + apelet + 1.0.0 + + 4.0.0 + + common-flow-online + 1.0.0 + common-flow-online + jar + + + + apelet + common-flow + 1.0.0 + + + apelet + common-online + 1.0.0 + + + apelet + common-orm + 1.0.0 + + + \ No newline at end of file diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteAspect.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteAspect.java new file mode 100644 index 0000000..d109b34 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteAspect.java @@ -0,0 +1,88 @@ +package apelet.common.flow.online.aop; + +import apelet.common.core.object.TokenData; +import apelet.common.core.util.AopTargetUtil; +import apelet.common.core.util.MyDateUtil; +import apelet.common.flow.dao.FlowTransProducerMapper; +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTransProducer; +import apelet.common.flow.online.object.TransactionalFlowBusinessData; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +/** + * 拦截流程业务多数据库数据写入的AOP对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Aspect +@Component +@Slf4j +public class FlowMultiDatabaseWriteAspect { + + @Autowired + private ApplicationEventPublisher eventPublisher; + @Autowired + private FlowTransProducerMapper flowTransProducerMapper; + + @Pointcut("execution(public * apelet.common.flow..service.impl..*(..)) " + + "&& @annotation(apelet.common.core.annotation.MultiDatabaseWriteMethod)") + public void multiDatabaseWriteMethodPointCut() { + // 空注释,避免sonar警告 + } + + @Around("multiDatabaseWriteMethodPointCut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + String initMethod = AopTargetUtil.getFullMethodName(joinPoint); + TransactionalFlowBusinessData data = TransactionalFlowBusinessData.getOrCreateFromRequestAttribute(); + if (data.getInitMethod() == null) { + data.setInitMethod(initMethod); + } + try { + // 调用原来的方法 + Object result = joinPoint.proceed(); + if (StrUtil.equals(initMethod, data.getInitMethod()) && CollUtil.isNotEmpty(data.getSqlDataList())) { + this.fillupTransactionalBusinessData(); + FlowTransProducer producerData = BeanUtil.copyProperties(data, FlowTransProducer.class); + producerData.setAppCode(TokenData.takeFromRequest().getAppCode()); + producerData.setTryTimes(1); + String sqlData = JSON.toJSONStringWithDateFormat( + data.getSqlDataList(), MyDateUtil.COMMON_SHORT_DATETIME_FORMAT); + producerData.setSqlData(sqlData); + flowTransProducerMapper.insert(producerData); + eventPublisher.publishEvent(data); + } + return result; + } catch (Exception e) { + TransactionalFlowBusinessData.removeFromRequestAttribute(); + throw e; + } finally { + if (StrUtil.equals(initMethod, data.getInitMethod())) { + TransactionalFlowBusinessData.removeFromRequestAttribute(); + } + } + } + + private void fillupTransactionalBusinessData() { + FlowTaskComment comment = FlowTaskComment.getFromRequest(); + TransactionalFlowBusinessData data = TransactionalFlowBusinessData.getFromRequestAttribute(); + if (comment != null) { + data.setProcessInstanceId(comment.getProcessInstanceId()); + data.setTaskId(comment.getTaskId()); + data.setTaskKey(comment.getTaskKey()); + data.setTaskName(comment.getTaskName()); + data.setTaskComment(comment.getTaskComment()); + } + } +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteExAspect.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteExAspect.java new file mode 100644 index 0000000..d15aca1 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/aop/FlowMultiDatabaseWriteExAspect.java @@ -0,0 +1,38 @@ +package apelet.common.flow.online.aop; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +/** + * 主要用于拦截FlowTransactionBusinessDataListener监听器类抛出的异常。 + * + * @author guifc + * @date 2023-08-04 + */ +@Aspect +@Component +@Order(1) +@Slf4j +public class FlowMultiDatabaseWriteExAspect { + + @Pointcut("execution(public * apelet.common.flow.online.service.impl..*(..)) " + + "&& @annotation(apelet.common.core.annotation.MultiDatabaseWriteMethod)") + public void multiDatabaseWriteMethodPointCut() { + // 空注释,避免sonar警告 + } + + @Around("multiDatabaseWriteMethodPointCut()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + try { + return joinPoint.proceed(); + } catch (Exception e) { + log.error("FlowMultiDatabaseWriteExAspect throw", e); + throw e; + } + } +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineAutoConfig.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineAutoConfig.java new file mode 100644 index 0000000..ffefce4 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineAutoConfig.java @@ -0,0 +1,13 @@ +package apelet.common.flow.online.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +/** + * common-flow-online模块的自动配置引导类。 + * + * @author guifc + * @date 2023-08-04 + */ +@EnableConfigurationProperties({FlowOnlineProperties.class}) +public class FlowOnlineAutoConfig { +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineProperties.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineProperties.java new file mode 100644 index 0000000..550bbc7 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/config/FlowOnlineProperties.java @@ -0,0 +1,20 @@ +package apelet.common.flow.online.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 在线表单工作流模块的配置对象。 + * + * @author guifc + * @date 2023-08-04 + */ +@Data +@ConfigurationProperties(prefix = "common-flow-online") +public class FlowOnlineProperties { + + /** + * 在线表单的URL前缀。 + */ + private String urlPrefix; +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/controller/FlowOnlineOperationController.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/controller/FlowOnlineOperationController.java new file mode 100644 index 0000000..2c5070f --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/controller/FlowOnlineOperationController.java @@ -0,0 +1,1437 @@ +package apelet.common.flow.online.controller; + +import apelet.common.core.annotation.DisableDataFilter; +import apelet.common.core.annotation.MyRequestBody; +import apelet.common.core.constant.ErrorCodeEnum; +import apelet.common.core.object.*; +import apelet.common.core.util.*; +import apelet.common.flow.constant.*; +import apelet.common.flow.dto.FlowTaskCommentDto; +import apelet.common.flow.dto.FlowWorkOrderDto; +import apelet.common.flow.exception.FlowOperationException; +import apelet.common.flow.model.*; +import apelet.common.flow.model.constant.FlowMessageType; +import apelet.common.flow.online.service.FlowEntryOnlineService; +import apelet.common.flow.online.service.FlowOnlineOperationService; +import apelet.common.flow.service.*; +import apelet.common.flow.util.FlowOperationHelper; +import apelet.common.flow.vo.*; +import apelet.common.generator.utils.OrmGenDataSourceUtil; +import apelet.common.log.annotation.OperationLog; +import apelet.common.log.model.constant.SysOperationLogType; +import apelet.common.online.config.OnlineProperties; +import apelet.common.online.dto.OnlineFilterDto; +import apelet.common.online.model.*; +import apelet.common.online.model.constant.FieldFilterType; +import apelet.common.online.model.constant.FieldKind; +import apelet.common.online.model.constant.RelationType; +import apelet.common.online.service.*; +import apelet.common.online.util.OnlineOperationHelper; +import apelet.common.orm.impl.Selector; +import apelet.common.orm.impl.SelectorItem; +import apelet.common.redis.cache.SessionCacheHelper; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.github.pagehelper.page.PageMethod; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.*; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import javax.xml.stream.XMLStreamException; +import java.io.IOException; +import java.io.Serializable; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 工作流在线表单流程操作接口。 + * + * @author guifc + * @date 2023-08-04 + */ +@Tag(name = "工作流在线表单流程操作接口") +@Slf4j +@RestController +@RequestMapping("${common-flow.urlPrefix}/flowOnlineOperation") +@ConditionalOnProperty(name = "common-flow.operationEnabled", havingValue = "true") +public class FlowOnlineOperationController { + + @Autowired + private FlowEntryService flowEntryService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowOperationHelper flowOperationHelper; + @Autowired + private FlowOnlineOperationService flowOnlineOperationService; + @Autowired + private FlowWorkOrderService flowWorkOrderService; + @Autowired + private FlowMessageService flowMessageService; + @Autowired + private FlowTransProducerService flowTransProducerService; + @Autowired + private OnlineFormService onlineFormService; + @Autowired + private OnlinePageService onlinePageService; + @Autowired + private OnlineOperationService onlineOperationService; + @Autowired + private OnlineTableService onlineTableService; + @Autowired + private OnlineDatasourceService onlineDatasourceService; + @Autowired + private OnlineOperationHelper onlineOperationHelper; + @Autowired + private OnlineProperties onlineProperties; + @Autowired + private SessionCacheHelper sessionCacheHelper; + @Autowired + private FlowTaskExtService flowTaskExtService; + @Autowired + private FlowEntryOnlineService flowEntryOnlineService; + + @Autowired + private OrmGenDataSourceUtil ormGenDataSourceUtil; + + private static final String ONE_TO_MANY_VAR_SUFFIX = "List"; + + + @DisableDataFilter + @PostMapping("/getOpreatingFlow") + public ResponseResult> getOpreatingFlow( + @MyRequestBody(required = true) String formId, + @MyRequestBody(required = true) String billId + ) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(FlowWorkOrder::getBusinessKey, billId); + queryWrapper.in(FlowWorkOrder::getFlowStatus, + FlowTaskStatus.SUBMITTED,FlowTaskStatus.APPROVING,FlowTaskStatus.FINISHED); + queryWrapper.orderByDesc(FlowWorkOrder::getCreateTime); + FlowWorkOrder flowWorkOrder = flowWorkOrderService.getOne(queryWrapper); + if(flowWorkOrder != null){ + + Map datamap = new HashMap<>(); + datamap.put("processDefinitionId", flowWorkOrder.getProcessDefinitionId()); + datamap.put("processInstanceId",flowWorkOrder.getProcessInstanceId()); + return ResponseResult.success(datamap); + }else { + String errorMessage = "没有查到运行流程!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + + } + + /** + * 根据指定流程的主版本,发起一个流程实例,同时作为第一个任务节点的执行人,执行第一个用户任务。 + * 该接口无需数据权限过滤,因此用DisableDataFilter注解标注。如果当前系统没有支持数据权限过滤,该注解不会有任何影响。 + * 注:流程设计页面的"启动"按钮,调用该接口可以启动任何流程用于流程配置后的测试验证。 + * + * @param processDefinitionKey 流程定义标识。 + * @param flowTaskCommentDto 审批意见。 + * @param taskVariableData 流程任务变量数据。 + * @param masterData 流程审批相关的主表数据。 + * @param slaveData 流程审批相关的多个从表数据。 + * @param copyData 传阅数据,格式为type和id,type的值参考FlowConstant中的常量值。 + * @return 应答结果对象。 + */ + @DisableDataFilter + @OperationLog(type = SysOperationLogType.START_FLOW) + @PostMapping("/startPreview") + public ResponseResult startPreview( + @MyRequestBody(required = true) String processDefinitionKey, + @MyRequestBody(required = true) FlowTaskCommentDto flowTaskCommentDto, + @MyRequestBody JSONObject taskVariableData, + @MyRequestBody(required = true) JSONObject masterData, + @MyRequestBody JSONObject slaveData, + @MyRequestBody JSONObject copyData) { + return this.startAndTake( + processDefinitionKey, flowTaskCommentDto, taskVariableData, masterData, slaveData, copyData); + } + + /** + * 通过业务主表主键的方式启动流程。 + * + * @param processDefinitionKey 流程定义标识。 + * @param id 业务表主键Id。 + * @return 应答结果对象。 + */ + @DisableDataFilter + @OperationLog(type = SysOperationLogType.START_FLOW) + @PostMapping("/startWithBusinessKey") + public ResponseResult startWithBusinessKey( + @MyRequestBody String processDefinitionKey, + @MyRequestBody(required = true) String id + ,@MyRequestBody JSONObject masterData, + @MyRequestBody JSONObject slaveData, + @MyRequestBody(required = true) Long formId + ,@MyRequestBody String datasourceVariableName, + @MyRequestBody Long datasourceId + + ) { + OnlineForm OnlineForm = onlineFormService.getOnlineFormFromCache(formId); + OnlineTable onlineTable = onlineTableService.getOnlineTableFromCache(OnlineForm.getMasterTableId()); + Selector selector = new Selector(); + selector.getList().add(new SelectorItem("billstatus")); + ObjectValue objectValue = ormGenDataSourceUtil.queryOne(onlineTable.getTableName(),Long.valueOf(id),selector); + if(objectValue == null){ + String errorMessage = "无效数据!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if(!objectValue.get("billstatus").equals("A")){ + throw new RuntimeException("只有保存状态才能提交"); + } + String processDefinitionId = null; + if(StringUtils.isNotEmpty(processDefinitionKey)){ + ResponseResult> verifyResult = + this.verifyAndGetFlowEntryPublishAndDatasource(processDefinitionKey); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + OnlineDatasource datasource = verifyResult.getData().getSecond(); + if (!onlineOperationService.existId(datasource.getMasterTable(), id)) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + if (flowWorkOrderService.existUnfinished(processDefinitionKey, id)) { + String errorMessage = "数据验证失败,该业务数据Id存在尚未完成审批的流程实例,同一业务数据主键不能同时重复提交审批!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + processDefinitionId = + verifyResult.getData().getFirst().getProcessDefinitionId(); + } + if(masterData != null){ + if(masterData.get("id") != null){ + this.updateData(masterData,slaveData,datasourceVariableName,datasourceId); + }else{ + this.addData(masterData,slaveData,datasourceVariableName,datasourceId); + } + } + flowOnlineOperationService.startWithBusinessKeyExt(processDefinitionId,id,formId); + return ResponseResult.success(); + } + + private ResponseResult updateData( + JSONObject masterData, + JSONObject slaveData, + String datasourceVariableName, + Long datasourceId){ + + ResponseResult datasourceResult = + onlineOperationHelper.verifyAndGetDatasource(datasourceId); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + OnlineDatasource datasource = datasourceResult.getData(); + /*if (!datasource.getVariableName().equals(datasourceVariableName)) { + ContextUtil.getHttpResponse().setStatus(HttpServletResponse.SC_FORBIDDEN); + return ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION); + }*/ + OnlineTable masterTable = datasource.getMasterTable(); + if (slaveData == null) { + if (!onlineOperationService.update(masterTable, masterData)) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + } else { + ResponseResult>> slaveDataListResult = + onlineOperationHelper.buildSlaveDataList(datasourceId, slaveData); + if (!slaveDataListResult.isSuccess()) { + return ResponseResult.errorFrom(slaveDataListResult); + } + onlineOperationService.updateWithRelation( + masterTable, masterData, datasourceId, slaveDataListResult.getData()); + } + return null; + + } + + private ResponseResult addData( + JSONObject masterData, + JSONObject slaveData, + String datasourceVariableName, + Long datasourceId){ + // 验证数据源的合法性,同时获取主表对象。 + ResponseResult datasourceResult = + onlineOperationHelper.verifyAndGetDatasource(datasourceId); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + OnlineDatasource datasource = datasourceResult.getData(); + /*if (!datasource.getVariableName().equals(datasourceVariableName)) { + ContextUtil.getHttpResponse().setStatus(HttpServletResponse.SC_FORBIDDEN); + return ResponseResult.error(ErrorCodeEnum.NO_OPERATION_PERMISSION); + }*/ + OnlineTable masterTable = datasource.getMasterTable(); + if (slaveData == null) { + onlineOperationService.saveNew(masterTable, masterData); + } else { + ResponseResult>> slaveDataListResult = + onlineOperationHelper.buildSlaveDataList(datasourceId, slaveData); + if (!slaveDataListResult.isSuccess()) { + return ResponseResult.errorFrom(slaveDataListResult); + } + onlineOperationService.saveNewWithRelation(masterTable, masterData, slaveDataListResult.getData()); + } + return null; + } + + /** + * 根据指定流程的主版本,发起一个流程实例,同时作为第一个任务节点的执行人,执行第一个用户任务。 + * 该接口无需数据权限过滤,因此用DisableDataFilter注解标注。如果当前系统没有支持数据权限过滤,该注解不会有任何影响。 + * + * @param processDefinitionKey 流程定义标识。 + * @param flowTaskCommentDto 审批意见。 + * @param taskVariableData 流程任务变量数据。 + * @param masterData 流程审批相关的主表数据。 + * @param slaveData 流程审批相关的多个从表数据。 + * @param copyData 传阅数据,格式为type和id,type的值参考FlowConstant中的常量值。 + * @return 应答结果对象。 + */ + @DisableDataFilter + @OperationLog(type = SysOperationLogType.START_FLOW) + @PostMapping("/startAndTakeUserTask/{processDefinitionKey}") + public ResponseResult startAndTakeUserTask( + @PathVariable("processDefinitionKey") String processDefinitionKey, + @MyRequestBody(required = true) FlowTaskCommentDto flowTaskCommentDto, + @MyRequestBody JSONObject taskVariableData, + @MyRequestBody(required = true) JSONObject masterData, + @MyRequestBody JSONObject slaveData, + @MyRequestBody JSONObject copyData) { + return this.startAndTake( + processDefinitionKey, flowTaskCommentDto, taskVariableData, masterData, slaveData, copyData); + } + + /** + * 启动流程并创建工单,同时将当前录入的数据存入草稿。 + * + * @param processDefinitionKey 流程定义标识。 + * @param processInstanceId 流程实例Id。第一次保存时,该值为null。 + * @param masterData 流程审批相关的主表数据。 + * @param slaveData 流程审批相关的多个从表数据。 + * @return 应答结果对象,草稿的待办任务对象。 + */ + @DisableDataFilter + @PostMapping("/startAndSaveDraft/{processDefinitionKey}") + public ResponseResult startAndSaveDraft( + @PathVariable("processDefinitionKey") String processDefinitionKey, + @MyRequestBody String processInstanceId, + @MyRequestBody JSONObject masterData, + @MyRequestBody JSONObject slaveData) { + String errorMessage; + if (MapUtil.isEmpty(masterData) && MapUtil.isEmpty(slaveData)) { + errorMessage = "数据验证失败,业务数据不能全部为空!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + ResponseResult> verifyResult = + this.verifyAndGetFlowEntryPublishAndDatasource(processDefinitionKey); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntryPublish flowEntryPublish = verifyResult.getData().getFirst(); + OnlineTable masterTable = verifyResult.getData().getSecond().getMasterTable(); + // 自动填充创建人数据。 + for (OnlineColumn column : masterTable.getColumnMap().values()) { + if (ObjectUtil.equals(column.getFieldKind(), FieldKind.CREATE_USER_ID)) { + masterData.put(column.getColumnName(), TokenData.takeFromRequest().getUserId()); + } else if (ObjectUtil.equals(column.getFieldKind(), FieldKind.CREATE_DEPT_ID)) { + masterData.put(column.getColumnName(), TokenData.takeFromRequest().getDeptId()); + } + } + FlowWorkOrder flowWorkOrder; + if (processInstanceId == null) { + flowWorkOrder = flowOnlineOperationService.saveNewDraftAndStartProcess( + flowEntryPublish.getProcessDefinitionId(), masterTable.getTableId(), masterData, slaveData); + } else { + ResponseResult flowWorkOrderResult = + flowOperationHelper.verifyAndGetFlowWorkOrderWithDraft(processDefinitionKey, processInstanceId); + if (!flowWorkOrderResult.isSuccess()) { + return ResponseResult.errorFrom(flowWorkOrderResult); + } + flowWorkOrder = flowWorkOrderResult.getData(); + flowWorkOrderService.updateDraft(flowWorkOrderResult.getData().getWorkOrderId(), + JSON.toJSONString(masterData), JSON.toJSONString(slaveData)); + } + List taskList = flowApiService.getProcessInstanceActiveTaskList(flowWorkOrder.getProcessInstanceId()); + List flowTaskVoList = flowApiService.convertToFlowTaskList(taskList); + return ResponseResult.success(flowTaskVoList.get(0)); + } + + /** + * 提交流程的用户任务。 + * 该接口无需数据权限过滤,因此用DisableDataFilter注解标注。如果当前系统没有支持数据权限过滤,该注解不会有任何影响。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @param flowTaskCommentDto 流程审批数据。 + * @param taskVariableData 流程任务变量数据。 + * @param masterData 流程审批相关的主表数据。 + * @param slaveData 流程审批相关的多个从表数据。 + * @param copyData 传阅数据,格式为type和id,type的值参考FlowConstant中的常量值。 + * @return 应答结果对象。 + */ + @DisableDataFilter + @OperationLog(type = SysOperationLogType.SUBMIT_TASK) + @PostMapping("/submitUserTask") + public ResponseResult submitUserTask( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) String taskId, + @MyRequestBody(required = true) FlowTaskCommentDto flowTaskCommentDto, + @MyRequestBody JSONObject taskVariableData, + @MyRequestBody JSONObject masterData, + @MyRequestBody JSONObject slaveData, + @MyRequestBody JSONObject copyData) { + String errorMessage; + // 验证流程任务的合法性。 + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + ResponseResult taskInfoResult = flowOperationHelper.verifyAndGetRuntimeTaskInfo(task); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + TaskInfoVo taskInfo = taskInfoResult.getData(); + // 验证在线表单及其关联数据源的合法性。 + ResponseResult datasourceResult = this.verifyAndGetOnlineDatasource(taskInfo.getFormId()); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + CallResult assigneeVerifyResult = flowApiService.verifyAssigneeOrCandidateAndClaim(task); + if (!assigneeVerifyResult.isSuccess()) { + return ResponseResult.errorFrom(assigneeVerifyResult); + } + OnlineDatasource datasource = datasourceResult.getData(); + ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId); + String dataId = instance.getBusinessKey(); + // 这里把传阅数据放到任务变量中,是为了避免给流程数据操作方法增加额外的方法调用参数。 + if (MapUtil.isNotEmpty(copyData)) { + if (taskVariableData == null) { + taskVariableData = new JSONObject(); + } + taskVariableData.put(FlowConstant.COPY_DATA_KEY, copyData); + } + FlowTaskComment flowTaskComment = BeanUtil.copyProperties(flowTaskCommentDto, FlowTaskComment.class); + if (StrUtil.isBlank(dataId)) { + return this.submitNewTask(processInstanceId, taskId, + flowTaskComment, taskVariableData, datasource, masterData, slaveData); + } + try { + if (StrUtil.equals(flowTaskComment.getApprovalType(), FlowApprovalType.TRANSFER) + && StrUtil.isBlank(flowTaskComment.getDelegateAssignee())) { + errorMessage = "数据验证失败,加签或转办任务指派人不能为空!!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + // 如果本次请求中包含从表数据,则一同插入。 + ResponseResult>> slaveDataListResult = + onlineOperationHelper.buildSlaveDataList(datasource.getDatasourceId(), slaveData); + if (!slaveDataListResult.isSuccess()) { + return ResponseResult.errorFrom(slaveDataListResult); + } +// onlineOperationService.executePlugin(onlinePluginExecuteDto, masterData, slaveData) + flowOnlineOperationService.updateAndTakeTask( + task, flowTaskComment, taskVariableData, datasource, masterData, dataId, slaveDataListResult.getData()); + } catch (FlowOperationException e) { + log.error("Failed to call [FlowOnlineOperationService.updateAndTakeTask]", e); + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, e.getMessage()); + } + return ResponseResult.success(); + } + + /** + * 查看指定流程实例的草稿数据。 + * NOTE: 白名单接口。 + * + * @param processDefinitionKey 流程定义标识。 + * @param processInstanceId 流程实例Id。 + * @return 流程实例的草稿数据。 + */ + @DisableDataFilter + @GetMapping("/viewDraftData") + public ResponseResult viewDraftData( + @RequestParam String processDefinitionKey, @RequestParam String processInstanceId) { + String errorMessage; + ResponseResult flowWorkOrderResult = + flowOperationHelper.verifyAndGetFlowWorkOrderWithDraft(processDefinitionKey, processInstanceId); + if (!flowWorkOrderResult.isSuccess()) { + return ResponseResult.errorFrom(flowWorkOrderResult); + } + FlowWorkOrder flowWorkOrder = flowWorkOrderResult.getData(); + if (flowWorkOrder.getOnlineTableId() == null) { + errorMessage = "数据验证失败,当前工单不是在线表单工单!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowWorkOrderExt flowWorkOrderExt = + flowWorkOrderService.getFlowWorkOrderExtByWorkOrderId(flowWorkOrder.getWorkOrderId()); + if (StrUtil.isBlank(flowWorkOrderExt.getDraftData())) { + return ResponseResult.success(null); + } + Long tableId = flowWorkOrder.getOnlineTableId(); + OnlineTable masterTable = onlineTableService.getOnlineTableFromCache(tableId); + JSONObject draftData = JSON.parseObject(flowWorkOrderExt.getDraftData()); + JSONObject masterData = draftData.getJSONObject(FlowConstant.MASTER_DATA_KEY); + JSONObject slaveData = draftData.getJSONObject(FlowConstant.SLAVE_DATA_KEY); + OnlineDatasource datasource = + onlineDatasourceService.getOnlineDatasourceByMasterTableId(tableId); + List slaveRelationList = null; + if (slaveData != null) { + ResponseResult> relationListResult = + onlineOperationHelper.verifyAndGetRelationList(datasource.getDatasourceId(), null); + if (!relationListResult.isSuccess()) { + return ResponseResult.errorFrom(relationListResult); + } + slaveRelationList = relationListResult.getData(); + } + datasource.setMasterTable(masterTable); + JSONObject jsonData = this.buildDraftData(datasource, masterData, slaveRelationList, slaveData); + return ResponseResult.success(jsonData); + } + + /** + * 获取当前流程实例的详情数据。包括主表数据、一对一从表数据、一对多从表数据列表等。 + * 该接口无需数据权限过滤,因此用DisableDataFilter注解标注。如果当前系统没有支持数据权限过滤,该注解不会有任何影响。 + * + * @param processInstanceId 当前运行时的流程实例Id。 + * @param taskId 流程任务Id。 + * @return 当前流程实例的详情数据。 + */ + @DisableDataFilter + @GetMapping("/viewUserTask") + public ResponseResult viewUserTask( + @RequestParam String processInstanceId, @RequestParam String taskId) { + // 验证流程任务的合法性。 + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId); + // 如果业务主数据为空,则直接返回。 + if (StrUtil.isBlank(instance.getBusinessKey())) { + return ResponseResult.success(null); + } + ResponseResult taskInfoResult = flowOperationHelper.verifyAndGetRuntimeTaskInfo(task); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + TaskInfoVo taskInfo = taskInfoResult.getData(); + // 验证在线表单及其关联数据源的合法性。 + ResponseResult datasourceResult = this.verifyAndGetOnlineDatasource(taskInfo.getFormId()); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + ResponseResult> relationListResult = + onlineOperationHelper.verifyAndGetRelationList(datasourceResult.getData().getDatasourceId(), null); + if (!relationListResult.isSuccess()) { + return ResponseResult.errorFrom(relationListResult); + } + JSONObject jsonData = this.buildUserTaskData( + instance.getBusinessKey(), datasourceResult.getData(), relationListResult.getData()); + return ResponseResult.success(jsonData); + } + + /** + * 获取已经结束的流程实例的详情数据。包括主表数据、一对一从表数据、一对多从表数据列表等。 + * 该接口无需数据权限过滤,因此用DisableDataFilter注解标注。如果当前系统没有支持数据权限过滤,该注解不会有任何影响。 + * + * @param processInstanceId 历史流程实例Id。 + * @param taskId 历史任务Id。如果该值为null,仅有发起人可以查看当前流程数据,否则只有任务的指派人才能查看。 + * @return 历史流程实例的详情数据。 + */ + @DisableDataFilter + @GetMapping("/viewHistoricProcessInstance") + public ResponseResult viewHistoricProcessInstance( + @RequestParam String processInstanceId, @RequestParam(required = false) String taskId) { + // 验证流程实例的合法性。 + ResponseResult verifyResult = + flowOperationHelper.verifyAndGetHistoricProcessInstance(processInstanceId, taskId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + HistoricProcessInstance instance = verifyResult.getData(); + if (StrUtil.isBlank(instance.getBusinessKey())) { + // 对于没有提交过任何用户任务的场景,可直接返回空数据。 + return ResponseResult.success(new JSONObject()); + } + FlowEntryPublish flowEntryPublish = + flowEntryService.getFlowEntryPublishList(CollUtil.newHashSet(instance.getProcessDefinitionId())).get(0); + TaskInfoVo taskInfoVo = JSON.parseObject(flowEntryPublish.getInitTaskInfo(), TaskInfoVo.class); + // 验证在线表单及其关联数据源的合法性。 + ResponseResult datasourceResult = this.verifyAndGetOnlineDatasource(taskInfoVo.getFormId()); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + ResponseResult> relationListResult = + onlineOperationHelper.verifyAndGetRelationList(datasourceResult.getData().getDatasourceId(), null); + if (!relationListResult.isSuccess()) { + return ResponseResult.errorFrom(relationListResult); + } + JSONObject jsonData = this.buildUserTaskData( + instance.getBusinessKey(), datasourceResult.getData(), relationListResult.getData()); + return ResponseResult.success(jsonData); + } + + /** + * 根据消息Id,获取流程Id关联的业务数据。 + * NOTE:白名单接口。 + * + * @param messageId 抄送消息Id。 + * @return 抄送消息关联的流程实例业务数据。 + */ + @DisableDataFilter + @GetMapping("/viewCopyBusinessData") + public ResponseResult viewCopyBusinessData(@RequestParam Long messageId) { + String errorMessage; + // 验证流程任务的合法性。 + FlowMessage flowMessage = flowMessageService.getById(messageId); + if (flowMessage == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + if (flowMessage.getMessageType() != FlowMessageType.COPY_TYPE) { + errorMessage = "数据验证失败,当前消息不是抄送类型消息!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (flowMessage.getOnlineFormData() == null || !flowMessage.getOnlineFormData()) { + errorMessage = "数据验证失败,当前消息为静态路由表单数据,不能通过该接口获取!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!flowMessageService.isCandidateIdentityOnMessage(messageId)) { + errorMessage = "数据验证失败,当前用户没有权限访问该消息!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + HistoricProcessInstance instance = + flowApiService.getHistoricProcessInstance(flowMessage.getProcessInstanceId()); + // 如果业务主数据为空,则直接返回。 + if (StrUtil.isBlank(instance.getBusinessKey())) { + errorMessage = "数据验证失败,当前消息为所属流程实例没有包含业务主键Id!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + Long formId = Long.valueOf(flowMessage.getBusinessDataShot()); + // 验证在线表单及其关联数据源的合法性。 + ResponseResult datasourceResult = this.verifyAndGetOnlineDatasource(formId); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + OnlineDatasource datasource = datasourceResult.getData(); + ResponseResult> relationListResult = + onlineOperationHelper.verifyAndGetRelationList(datasource.getDatasourceId(), null); + if (!relationListResult.isSuccess()) { + return ResponseResult.errorFrom(relationListResult); + } + JSONObject jsonData = this.buildUserTaskData( + instance.getBusinessKey(), datasource, relationListResult.getData()); + // 将当前消息更新为已读 + flowMessageService.readCopyTask(messageId); + return ResponseResult.success(jsonData); + } + + /** + * 修复业务数据,目前仅在线表单工作流支持的跨库业务数据主动修复接口 。 + * + * @param processInstanceId 流程实例Id。 + * @param transId 流水号Id。 + * @return 操作应答结果。 + */ + @OperationLog(type = SysOperationLogType.FIX_FLOW_BUSINESS_DATA) + @PostMapping("/fixBusinessData") + public ResponseResult fixBusinessData( + @MyRequestBody(required = true) String processInstanceId, + @MyRequestBody(required = true) Long transId) { + String errorMessage; + FlowTransProducer transProducer = flowTransProducerService.getById(transId); + if (transProducer == null) { + errorMessage = "数据操作失败,该数据修复流水号Id不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + if (!StrUtil.equals(processInstanceId, transProducer.getProcessInstanceId())) { + errorMessage = "数据操作失败,该数据修复流水号Id与流程实例Id不匹配!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + flowOnlineOperationService.fixBusinessData(transProducer); + flowTransProducerService.removeById(transProducer.getTransId()); + return ResponseResult.success(); + } + + /** + * 工作流工单列表。 + * + * @param processDefinitionKey 流程标识名。 + * @param flowWorkOrderDtoFilter 过滤对象。 + * @param pageParam 分页参数。 + * @param ignoreMaskFields 忽略脱敏的字段名集合,多个字段之间逗号分隔。 + * @return 查询结果。 + */ + @PostMapping("/listWorkOrder/{processDefinitionKey}") + public ResponseResult> listWorkOrder( + @PathVariable("processDefinitionKey") String processDefinitionKey, + @MyRequestBody FlowWorkOrderDto flowWorkOrderDtoFilter, + @MyRequestBody MyPageParam pageParam, + @MyRequestBody String ignoreMaskFields) { + if (pageParam != null) { + PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize(), pageParam.getCount()); + } + FlowWorkOrder flowWorkOrderFilter = + flowOperationHelper.makeWorkOrderFilter(flowWorkOrderDtoFilter, processDefinitionKey); + MyOrderParam orderParam = new MyOrderParam(); + orderParam.add(new MyOrderParam.OrderInfo("workOrderId", false, null)); + String orderBy = MyOrderParam.buildOrderBy(orderParam, FlowWorkOrder.class); + List flowWorkOrderList = + flowWorkOrderService.getFlowWorkOrderList(flowWorkOrderFilter, orderBy); + MyPageData resultData = + MyPageUtil.makeResponseData(flowWorkOrderList, FlowWorkOrder.INSTANCE); + flowOperationHelper.buildWorkOrderApprovalStatus(processDefinitionKey, resultData.getDataList()); + // 根据工单的提交用户名获取用户的显示名称,便于前端显示。 + // 同时这也是一个如何通过插件方法,将loginName映射到showName的示例, + flowWorkOrderService.fillUserShowNameByLoginName(resultData.getDataList()); + // 工单自身的查询中可以受到数据权限的过滤,但是工单集成业务数据时,则无需再对业务数据进行数据权限过滤了。 + GlobalThreadLocal.setDataFilter(false); + ResponseResult responseResult = this.makeWorkOrderTaskInfo(resultData.getDataList(), ignoreMaskFields); + if (!responseResult.isSuccess()) { + return ResponseResult.errorFrom(responseResult); + } + return ResponseResult.success(resultData); + } + + /** + * 流程工单打印接口。 + * 该方法并不进行实际的打印工作,而是对当前请求的工单参数数据进行合法性验证。通过验证后,会为本次调用生成 + * 唯一的打印令牌,并存入与session关联的缓存中,再将实际打印接口及生成的打印令牌返回给前端。前端收到应答后, + * 会调用返回的实际打印接口完成打印。 + * + * @param processDefinitionKey 流程标识名。 + * @param printId 打印模板Id。 + * @param printParams 打印参数列表。 + */ + @PostMapping("/printWorkOrder/{processDefinitionKey}") + public ResponseResult printWorkOrder( + @PathVariable("processDefinitionKey") String processDefinitionKey, + @MyRequestBody(required = true) Long printId, + @MyRequestBody(required = true) List printParams) { + String errorMessage; + FlowWorkOrder flowWorkOrderFilter = flowOperationHelper.makeWorkOrderFilter(null, processDefinitionKey); + Set workOrderIdSet = new HashSet<>(); + for (JSONArray printParam : printParams) { + Long workOrderId = null; + for (int i = 0; i < printParam.size(); ++i) { + JSONObject paramObject = printParam.getJSONObject(i); + String paramName = paramObject.getString("paramName"); + if (StrUtil.equals(paramName, "workOrderId")) { + workOrderId = paramObject.getLong("paramValue"); + break; + } + } + if (workOrderId == null) { + errorMessage = "数据验证失败,打印参数中必须包含工单Id参数!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + workOrderIdSet.add(workOrderId); + } + List workOrderList = flowWorkOrderService.lambdaQuery() + .setEntity(flowWorkOrderFilter).in(FlowWorkOrder::getWorkOrderId, workOrderIdSet).list(); + if (workOrderList.size() != printParams.size()) { + errorMessage = "数据验证失败,参数中的工单Id数据,存在没有数据权限访问的数据!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + // 为本次打印请求生成唯一的打印令牌。 + String token = MyCommonUtil.generateUuid(); + // 将本次请求的打印令牌和打印参数,均存入会话关联的缓存中。出于安全考虑,仅返回打印令牌。 + sessionCacheHelper.putSessionPrintTokenAndInfo(token, new MyPrintInfo(printId, printParams)); + // 将打印令牌作为url参数返回给前端。 + return ResponseResult.success(onlineProperties.getPrintUrlPath() + "?printToken=" + token); + } + + /** + * 为数据源主表字段上传文件。 + * + * @param processDefinitionKey 流程引擎流程定义标识。 + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @param datasourceId 数据源Id。 + * @param relationId 数据源关联Id。 + * @param fieldName 数据表字段名。 + * @param asImage 是否为图片文件。 + * @param uploadFile 上传文件对象。 + */ + @DisableDataFilter + @OperationLog(type = SysOperationLogType.UPLOAD, saveResponse = false) + @PostMapping("/upload") + public void upload( + @RequestParam String processDefinitionKey, + @RequestParam(required = false) String processInstanceId, + @RequestParam(required = false) String taskId, + @RequestParam Long datasourceId, + @RequestParam(required = false) Long relationId, + @RequestParam String fieldName, + @RequestParam Boolean asImage, + @RequestParam("uploadFile") MultipartFile uploadFile) throws IOException { + ResponseResult verifyResult = + this.verifyUploadOrDownload(processDefinitionKey, processInstanceId, taskId, datasourceId); + if (!verifyResult.isSuccess()) { + ResponseResult.output(HttpServletResponse.SC_FORBIDDEN, ResponseResult.errorFrom(verifyResult)); + return; + } + ResponseResult verifyTableResult = + this.verifyAndGetOnlineTable(datasourceId, relationId, null, null); + if (!verifyTableResult.isSuccess()) { + ResponseResult.output(HttpServletResponse.SC_FORBIDDEN, ResponseResult.errorFrom(verifyTableResult)); + return; + } + onlineOperationHelper.doUpload(verifyTableResult.getData(), fieldName, asImage, uploadFile); + } + + /** + * 下载文件接口。 + * 越权访问限制说明: + * taskId为空,当前用户必须为当前流程的发起人,否则必须为当前任务的指派人或候选人。 + * relationId为空,下载数据为主表字段,否则为关联的从表字段。 + * 该接口无需数据权限过滤,因此用DisableDataFilter注解标注。如果当前系统没有支持数据权限过滤,该注解不会有任何影响。 + * + * @param processDefinitionKey 流程引擎流程定义标识。 + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @param datasourceId 数据源Id。 + * @param relationId 数据源关联Id。 + * @param dataId 附件所在记录的主键Id。 + * @param fieldName 数据表字段名。 + * @param asImage 是否为图片文件。 + * @param response Http 应答对象。 + */ + @DisableDataFilter + @OperationLog(type = SysOperationLogType.DOWNLOAD, saveResponse = false) + @GetMapping("/download") + public void download( + @RequestParam String processDefinitionKey, + @RequestParam(required = false) String processInstanceId, + @RequestParam(required = false) String taskId, + @RequestParam Long datasourceId, + @RequestParam(required = false) Long relationId, + @RequestParam(required = false) String dataId, + @RequestParam String fieldName, + @RequestParam String filename, + @RequestParam Boolean asImage, + HttpServletResponse response) throws IOException { + ResponseResult verifyResult = + this.verifyUploadOrDownload(processDefinitionKey, processInstanceId, taskId, datasourceId); + if (!verifyResult.isSuccess()) { + ResponseResult.output(HttpServletResponse.SC_FORBIDDEN, ResponseResult.errorFrom(verifyResult)); + return; + } + ResponseResult verifyTableResult = + this.verifyAndGetOnlineTable(datasourceId, relationId, verifyResult.getData(), dataId); + if (!verifyTableResult.isSuccess()) { + ResponseResult.output(HttpServletResponse.SC_FORBIDDEN, ResponseResult.errorFrom(verifyTableResult)); + return; + } + onlineOperationHelper.doDownload(verifyTableResult.getData(), dataId, fieldName, filename, asImage, response); + } + + /** + * 获取所有流程对象,同时获取关联的在线表单对象列表。 + * + * @return 查询结果。 + */ + @GetMapping("/listFlowEntryForm") + public ResponseResult> listFlowEntryForm() { + List flowEntryList = flowEntryService.getFlowEntryList(null, null); + List flowEntryVoList = FlowEntry.INSTANCE.fromModelList(flowEntryList); + if (CollUtil.isNotEmpty(flowEntryVoList)) { + Set pageIdSet = flowEntryVoList.stream().map(FlowEntryVo::getPageId).collect(Collectors.toSet()); + List formList = onlineFormService.getOnlineFormListByPageIds(pageIdSet); + formList.forEach(f -> f.setWidgetJson(null)); + Map> formMap = + formList.stream().collect(Collectors.groupingBy(OnlineForm::getPageId)); + for (FlowEntryVo flowEntryVo : flowEntryVoList) { + List flowEntryFormList = formMap.get(flowEntryVo.getPageId()); + flowEntryVo.setFormList(MyModelUtil.beanToMapList(flowEntryFormList)); + } + } + return ResponseResult.success(flowEntryVoList); + } + + /** + * 获取在线表单工作流Id所关联的权限数据,包括权限字列表和权限资源列表。 + * 注:该接口仅用于微服务间调用使用,无需对前端开放。 + * + * @param onlineFlowEntryIds 在线表单工作流Id集合。 + * @return 参数中在线表单工作流Id集合所关联的权限数据。 + */ + @GetMapping("/calculatePermData") + public ResponseResult>> calculatePermData(@RequestParam Set onlineFlowEntryIds) { + return ResponseResult.success(flowOnlineOperationService.calculatePermData(onlineFlowEntryIds)); + } + + private ResponseResult startAndTake( + String processDefinitionKey, + FlowTaskCommentDto flowTaskCommentDto, + JSONObject taskVariableData, + JSONObject masterData, + JSONObject slaveData, + JSONObject copyData) { + ResponseResult> verifyResult = + this.verifyAndGetFlowEntryPublishAndDatasource(processDefinitionKey); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntryPublish flowEntryPublish = verifyResult.getData().getFirst(); + OnlineDatasource datasource = verifyResult.getData().getSecond(); + OnlineTable masterTable = datasource.getMasterTable(); + // 这里把传阅数据放到任务变量中,是为了避免给流程数据操作方法增加额外的方法调用参数。 + if (MapUtil.isNotEmpty(copyData)) { + if (taskVariableData == null) { + taskVariableData = new JSONObject(); + } + taskVariableData.put(FlowConstant.COPY_DATA_KEY, copyData); + } + FlowTaskComment flowTaskComment = BeanUtil.copyProperties(flowTaskCommentDto, FlowTaskComment.class); + // 保存在线表单提交的数据,同时启动流程和自动完成第一个用户任务。 + if (slaveData == null) { + flowOnlineOperationService.saveNewAndStartProcess( + flowEntryPublish.getProcessDefinitionId(), + flowTaskComment, + taskVariableData, + masterTable, + masterData); + } else { + // 如果本次请求中包含从表数据,则一同插入。 + ResponseResult>> slaveDataListResult = + onlineOperationHelper.buildSlaveDataList(datasource.getDatasourceId(), slaveData); + if (!slaveDataListResult.isSuccess()) { + return ResponseResult.errorFrom(slaveDataListResult); + } + flowOnlineOperationService.saveNewAndStartProcess( + flowEntryPublish.getProcessDefinitionId(), + flowTaskComment, + taskVariableData, + masterTable, + masterData, + slaveDataListResult.getData()); + } + return ResponseResult.success(); + } + + private ResponseResult verifyAndGetOnlineDatasource(Long formId) { + List formDatasourceList = onlineFormService.getFormDatasourceListFromCache(formId); + if (CollUtil.isEmpty(formDatasourceList)) { + String errorMessage = "数据验证失败,流程任务绑定的在线表单Id [" + formId + "] 不存在,请修改流程图!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return onlineOperationHelper.verifyAndGetDatasource(formDatasourceList.get(0).getDatasourceId()); + } + + private ResponseResult> verifyAndGetFlowEntryPublishAndDatasource( + String processDefinitionKey) { + String errorMessage; + // 1. 验证流程数据的合法性。 + ResponseResult flowEntryResult = flowOperationHelper.verifyAndGetFlowEntry(processDefinitionKey); + if (!flowEntryResult.isSuccess()) { + return ResponseResult.errorFrom(flowEntryResult); + } + // 2. 验证流程一个用户任务的合法性。 + FlowEntryPublish flowEntryPublish = flowEntryResult.getData().getMainFlowEntryPublish(); + if (BooleanUtil.isFalse(flowEntryPublish.getActiveStatus())) { + errorMessage = "数据验证失败,当前流程发布对象已被挂起,不能启动新流程!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + ResponseResult taskInfoResult = + flowOperationHelper.verifyAndGetInitialTaskInfo(flowEntryPublish, true); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + TaskInfoVo taskInfo = taskInfoResult.getData(); + // 3. 验证在线表单及其关联数据源的合法性。 + ResponseResult datasourceResult = this.verifyAndGetOnlineDatasource(taskInfo.getFormId()); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + return ResponseResult.success(new Tuple2<>(flowEntryPublish, datasourceResult.getData())); + } + + private ResponseResult verifyAndGetOnlineTable( + Long datasourceId, Long relationId, String businessKey, String dataId) { + ResponseResult datasourceResult = + onlineOperationHelper.verifyAndGetDatasource(datasourceId); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + OnlineTable masterTable = datasourceResult.getData().getMasterTable(); + OnlineTable table = masterTable; + ResponseResult relationResult = null; + if (relationId != null) { + relationResult = onlineOperationHelper.verifyAndGetRelation(datasourceId, relationId); + if (!relationResult.isSuccess()) { + return ResponseResult.errorFrom(relationResult); + } + table = relationResult.getData().getSlaveTable(); + } + if (StrUtil.hasBlank(businessKey, dataId)) { + return ResponseResult.success(table); + } + String errorMessage; + // 如果relationId为null,这里就是主表数据。 + if (relationId == null) { + if (!StrUtil.equals(businessKey, dataId)) { + errorMessage = "数据验证失败,参数主键Id与流程主表主键Id不匹配!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(table); + } + OnlineDatasourceRelation relation = relationResult.getData(); + OnlineTable slaveTable = relation.getSlaveTable(); + Map dataMap = + onlineOperationService.getMasterData(slaveTable, null, null, dataId); + if (dataMap == null) { + errorMessage = "数据验证失败,从表主键Id不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + OnlineColumn slaveColumn = relation.getSlaveColumn(); + Object relationSlaveDataId = dataMap.get(slaveColumn.getColumnName()); + if (relationSlaveDataId == null) { + errorMessage = "数据验证失败,当前关联的从表字段值为NULL!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + OnlineColumn masterColumn = masterTable.getColumnMap().get(relation.getMasterColumnId()); + if (BooleanUtil.isTrue(masterColumn.getPrimaryKey()) + && !StrUtil.equals(relationSlaveDataId.toString(), businessKey)) { + errorMessage = "数据验证失败,当前从表主键Id关联的主表Id当前流程的BusinessKey不一致!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + Map masterDataMap = + onlineOperationService.getMasterData(masterTable, null, null, businessKey); + if (masterDataMap == null) { + errorMessage = "数据验证失败,主表主键Id不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + Object relationMasterDataId = masterDataMap.get(masterColumn.getColumnName()); + if (relationMasterDataId == null) { + errorMessage = "数据验证失败,当前关联的主表字段值为NULL!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (!StrUtil.equals(relationMasterDataId.toString(), relationSlaveDataId.toString())) { + errorMessage = "数据验证失败,当前关联的主表字段值和从表字段值不一致!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(table); + } + + private ResponseResult verifyUploadOrDownload( + String processDefinitionKey, String processInstanceId, String taskId, Long datasourceId) { + if (!StrUtil.isAllBlank(processInstanceId, taskId)) { + ResponseResult verifyResult = + flowOperationHelper.verifyUploadOrDownloadPermission(processInstanceId, taskId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(ResponseResult.errorFrom(verifyResult)); + } + } + String errorMessage; + FlowEntry flowEntry = flowEntryService.getFlowEntryFromCache(processDefinitionKey); + if (flowEntry == null) { + errorMessage = "数据验证失败,指定流程Id不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + String businessKey = null; + if (processInstanceId != null) { + HistoricProcessInstance instance = flowApiService.getHistoricProcessInstance(processInstanceId); + if (!StrUtil.equals(flowEntry.getProcessDefinitionKey(), instance.getProcessDefinitionKey())) { + errorMessage = "数据验证失败,指定流程实例并不属于当前流程!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + businessKey = instance.getBusinessKey(); + } + List datasourceList = + onlinePageService.getOnlinePageDatasourceListByPageId(flowEntry.getPageId()); + Optional r = datasourceList.stream() + .map(OnlinePageDatasource::getDatasourceId).filter(c -> c.equals(datasourceId)).findFirst(); + if (!r.isPresent()) { + errorMessage = "数据验证失败,当前数据源Id并不属于当前流程!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(businessKey); + } + + private ResponseResult submitNewTask( + String instanceId, + String taskId, + FlowTaskComment comment, + JSONObject variableData, + OnlineDatasource datasource, + JSONObject masterData, + JSONObject slaveData) { + OnlineTable masterTable = datasource.getMasterTable(); + // 保存在线表单提交的数据,同时启动流程和自动完成第一个用户任务。 + if (slaveData == null) { + flowOnlineOperationService.saveNewAndTakeTask( + instanceId, taskId, comment, variableData, masterTable, masterData); + } else { + // 如果本次请求中包含从表数据,则一同插入。 + ResponseResult>> slaveDataListResult = + onlineOperationHelper.buildSlaveDataList(datasource.getDatasourceId(), slaveData); + if (!slaveDataListResult.isSuccess()) { + return ResponseResult.errorFrom(slaveDataListResult); + } + flowOnlineOperationService.saveNewAndTakeTask( + instanceId, taskId, comment, variableData, masterTable, masterData, slaveDataListResult.getData()); + } + return ResponseResult.success(); + } + + private JSONObject buildUserTaskData( + String businessKey, OnlineDatasource datasource, List relationList) { + OnlineTable masterTable = datasource.getMasterTable(); + JSONObject jsonData = new JSONObject(); + List oneToOneRelationList = relationList.stream() + .filter(r -> r.getRelationType().equals(RelationType.ONE_TO_ONE)).collect(Collectors.toList()); + Map result = + onlineOperationService.getMasterData(masterTable, oneToOneRelationList, relationList, businessKey); + if (MapUtil.isEmpty(result)) { + return jsonData; + } + jsonData.put(datasource.getVariableName(), result); + List oneToManyRelationList = relationList.stream() + .filter(r -> r.getRelationType().equals(RelationType.ONE_TO_MANY)).collect(Collectors.toList()); + if (CollUtil.isEmpty(oneToManyRelationList)) { + return jsonData; + } + for (OnlineDatasourceRelation relation : oneToManyRelationList) { + OnlineFilterDto filterDto = new OnlineFilterDto(); + filterDto.setTableName(relation.getSlaveTable().getTableName()); + OnlineColumn slaveColumn = relation.getSlaveTable().getColumnMap().get(relation.getSlaveColumnId()); + filterDto.setColumnName(slaveColumn.getColumnName()); + filterDto.setFilterType(FieldFilterType.EQUAL_FILTER); + OnlineColumn masterColumn = masterTable.getColumnMap().get(relation.getMasterColumnId()); + Object columnValue = result.get(masterColumn.getColumnName()); + filterDto.setColumnValue(columnValue); + MyPageData> pageData = onlineOperationService.getSlaveDataList( + relation, CollUtil.newLinkedList(filterDto), null, null); + if (CollUtil.isNotEmpty(pageData.getDataList())) { + result.put(relation.getVariableName() + ONE_TO_MANY_VAR_SUFFIX, pageData.getDataList()); + } + } + return jsonData; + } + + private JSONObject buildDraftData( + OnlineDatasource datasource, + JSONObject masterData, + List relationList, + JSONObject slaveData) { + OnlineTable masterTable = datasource.getMasterTable(); + JSONObject jsonData = new JSONObject(); + JSONObject normalizedMasterData = new JSONObject(); + Map columnNameAndColumnMap = masterTable.getColumnMap() + .values().stream().collect(Collectors.toMap(OnlineColumn::getColumnName, c -> c)); + if (masterData != null) { + for (Map.Entry entry : masterData.entrySet()) { + OnlineColumn column = columnNameAndColumnMap.get(entry.getKey()); + Object v = onlineOperationHelper.convertToTypeValue(column, entry.getValue().toString()); + normalizedMasterData.put(entry.getKey(), v); + } + } + if (slaveData != null && relationList != null) { + Map relationMap = + relationList.stream().collect(Collectors.toMap(OnlineDatasourceRelation::getRelationId, c -> c)); + for (Map.Entry entry : slaveData.entrySet()) { + OnlineDatasourceRelation relation = relationMap.get(Long.valueOf(entry.getKey())); + if (relation != null) { + this.buildRelationDraftData(relation, entry.getValue(), normalizedMasterData); + } + } + } + jsonData.put(datasource.getVariableName(), normalizedMasterData); + return jsonData; + } + + private void buildRelationDraftData(OnlineDatasourceRelation relation, Object value, JSONObject masterData) { + if (relation.getRelationType().equals(RelationType.ONE_TO_ONE)) { + Map slaveColumnNameAndColumnMap = + relation.getSlaveTable().getColumnMap().values() + .stream().collect(Collectors.toMap(OnlineColumn::getColumnName, c -> c)); + JSONObject slaveObject = (JSONObject) value; + JSONObject normalizedSlaveObject = new JSONObject(); + for (Map.Entry entry2 : slaveObject.entrySet()) { + OnlineColumn column = slaveColumnNameAndColumnMap.get(entry2.getKey()); + Object v = onlineOperationHelper.convertToTypeValue(column, entry2.getValue().toString()); + normalizedSlaveObject.put(entry2.getKey(), v); + } + masterData.put(relation.getVariableName(), normalizedSlaveObject); + } else if (relation.getRelationType().equals(RelationType.ONE_TO_MANY)) { + JSONArray slaveArray = (JSONArray) value; + JSONArray normalizedSlaveArray = new JSONArray(); + for (int i = 0; i <= slaveArray.size() - 1; i++) { + JSONObject slaveObject = slaveArray.getJSONObject(i); + JSONObject normalizedSlaveObject = new JSONObject(); + for (Map.Entry entry2 : slaveObject.entrySet()) { + normalizedSlaveObject.put(entry2.getKey(), entry2.getValue()); + } + normalizedSlaveArray.add(normalizedSlaveObject); + } + masterData.put(relation.getVariableName(), normalizedSlaveArray); + } + } + + private ResponseResult makeWorkOrderTaskInfo(List flowWorkOrderVoList, String ignoreMaskFields) { + if (CollUtil.isEmpty(flowWorkOrderVoList)) { + return ResponseResult.success(); + } + Set definitionIdSet = + flowWorkOrderVoList.stream().map(FlowWorkOrderVo::getProcessDefinitionId).collect(Collectors.toSet()); + List flowEntryPublishList = flowEntryService.getFlowEntryPublishList(definitionIdSet); + Map flowEntryPublishMap = + flowEntryPublishList.stream().collect(Collectors.toMap(FlowEntryPublish::getProcessDefinitionId, c -> c)); + for (FlowWorkOrderVo flowWorkOrderVo : flowWorkOrderVoList) { + FlowEntryPublish flowEntryPublish = flowEntryPublishMap.get(flowWorkOrderVo.getProcessDefinitionId()); + flowWorkOrderVo.setInitTaskInfo(flowEntryPublish.getInitTaskInfo()); + } + Long tableId = flowWorkOrderVoList.get(0).getOnlineTableId(); + OnlineTable masterTable = onlineTableService.getOnlineTableFromCache(tableId); + ResponseResult responseResult = + this.buildWorkOrderMasterData(flowWorkOrderVoList, masterTable, ignoreMaskFields); + if (!responseResult.isSuccess()) { + return ResponseResult.errorFrom(responseResult); + } + responseResult = this.buildWorkOrderDraftData(flowWorkOrderVoList, masterTable); + if (!responseResult.isSuccess()) { + return ResponseResult.errorFrom(responseResult); + } + List unfinishedProcessInstanceIds = flowWorkOrderVoList.stream() + .filter(c -> !c.getFlowStatus().equals(FlowTaskStatus.FINISHED)) + .map(FlowWorkOrderVo::getProcessInstanceId) + .collect(Collectors.toList()); + if (CollUtil.isEmpty(unfinishedProcessInstanceIds)) { + return ResponseResult.success(); + } + Map> taskMap = + flowApiService.getTaskListByProcessInstanceIds(unfinishedProcessInstanceIds) + .stream().collect(Collectors.groupingBy(Task::getProcessInstanceId)); + for (FlowWorkOrderVo flowWorkOrderVo : flowWorkOrderVoList) { + List instanceTaskList = taskMap.get(flowWorkOrderVo.getProcessInstanceId()); + if (instanceTaskList != null) { + JSONArray taskArray = new JSONArray(); + for (Task task : instanceTaskList) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("taskId", task.getId()); + jsonObject.put("taskName", task.getName()); + jsonObject.put("taskKey", task.getTaskDefinitionKey()); + jsonObject.put("assignee", task.getAssignee()); + taskArray.add(jsonObject); + } + flowWorkOrderVo.setRuntimeTaskInfoList(taskArray); + } + } + return ResponseResult.success(); + } + + private ResponseResult buildWorkOrderDraftData( + List flowWorkOrderVoList, OnlineTable masterTable) { + List draftWorkOrderList = flowWorkOrderVoList.stream() + .filter(c -> c.getFlowStatus().equals(FlowTaskStatus.DRAFT)).collect(Collectors.toList()); + if (CollUtil.isEmpty(draftWorkOrderList)) { + return ResponseResult.success(); + } + Set workOrderIdSet = draftWorkOrderList.stream() + .map(FlowWorkOrderVo::getWorkOrderId).collect(Collectors.toSet()); + List workOrderExtList = + flowWorkOrderService.getFlowWorkOrderExtByWorkOrderIds(workOrderIdSet); + Map workOrderExtMap = workOrderExtList.stream() + .collect(Collectors.toMap(FlowWorkOrderExt::getWorkOrderId, c -> c)); + for (FlowWorkOrderVo workOrder : draftWorkOrderList) { + FlowWorkOrderExt workOrderExt = workOrderExtMap.get(workOrder.getWorkOrderId()); + if (workOrderExt == null) { + continue; + } + JSONObject draftData = JSON.parseObject(workOrderExt.getDraftData()); + JSONObject masterData = draftData.getJSONObject(FlowConstant.MASTER_DATA_KEY); + JSONObject slaveData = draftData.getJSONObject(FlowConstant.SLAVE_DATA_KEY); + OnlineDatasource datasource = + onlineDatasourceService.getOnlineDatasourceByMasterTableId(masterTable.getTableId()); + List slaveRelationList = null; + if (slaveData != null) { + ResponseResult> relationListResult = + onlineOperationHelper.verifyAndGetRelationList(datasource.getDatasourceId(), RelationType.ONE_TO_ONE); + if (!relationListResult.isSuccess()) { + return ResponseResult.errorFrom(relationListResult); + } + slaveRelationList = relationListResult.getData(); + } + datasource.setMasterTable(masterTable); + JSONObject jsonData = this.buildDraftData(datasource, masterData, slaveRelationList, slaveData); + JSONObject masterAndOneToOneData = jsonData.getJSONObject(datasource.getVariableName()); + if (MapUtil.isNotEmpty(masterAndOneToOneData)) { + List> dataList = new LinkedList<>(); + dataList.add(masterAndOneToOneData); + onlineOperationService.buildDataListWithDict(masterTable, slaveRelationList, dataList); + } + workOrder.setMasterData(masterAndOneToOneData); + } + return ResponseResult.success(); + } + + private ResponseResult buildWorkOrderMasterData( + List flowWorkOrderVoList, OnlineTable masterTable, String ignoreMaskFields) { + Set businessKeySet = flowWorkOrderVoList.stream() + .filter(c -> c.getBusinessKey() != null) + .map(FlowWorkOrderVo::getBusinessKey).collect(Collectors.toSet()); + if (CollUtil.isEmpty(businessKeySet)) { + return ResponseResult.success(); + } + Set convertedBusinessKeySet = + onlineOperationHelper.convertToTypeValue(masterTable.getPrimaryKeyColumn(), businessKeySet); + List filterList = new LinkedList<>(); + OnlineFilterDto filterDto = new OnlineFilterDto(); + filterDto.setTableName(masterTable.getTableName()); + filterDto.setColumnName(masterTable.getPrimaryKeyColumn().getColumnName()); + filterDto.setFilterType(FieldFilterType.IN_LIST_FILTER); + filterDto.setColumnValueList(new HashSet<>(convertedBusinessKeySet)); + filterList.add(filterDto); + TaskInfoVo taskInfoVo = JSON.parseObject(flowWorkOrderVoList.get(0).getInitTaskInfo(), TaskInfoVo.class); + // 验证在线表单及其关联数据源的合法性。 + ResponseResult datasourceResult = this.verifyAndGetOnlineDatasource(taskInfoVo.getFormId()); + if (!datasourceResult.isSuccess()) { + return ResponseResult.errorFrom(datasourceResult); + } + OnlineDatasource datasource = datasourceResult.getData(); + ResponseResult> relationListResult = + onlineOperationHelper.verifyAndGetRelationList(datasource.getDatasourceId(), RelationType.ONE_TO_ONE); + if (!relationListResult.isSuccess()) { + return ResponseResult.errorFrom(relationListResult); + } + MyPageData> pageData = onlineOperationService.getMasterDataList( + masterTable, relationListResult.getData(), null, filterList, null, null,null); + List> dataList = pageData.getDataList(); + onlineOperationHelper.maskFieldDataList(dataList, masterTable, relationListResult.getData(), ignoreMaskFields); + Map> dataMap = dataList.stream() + .collect(Collectors.toMap(c -> c.get(masterTable.getPrimaryKeyColumn().getColumnName()).toString(), c -> c)); + for (FlowWorkOrderVo flowWorkOrderVo : flowWorkOrderVoList) { + if (StrUtil.isNotBlank(flowWorkOrderVo.getBusinessKey())) { + Object dataId = onlineOperationHelper.convertToTypeValue( + masterTable.getPrimaryKeyColumn(), flowWorkOrderVo.getBusinessKey()); + Map data = dataMap.get(dataId.toString()); + if (data != null) { + flowWorkOrderVo.setMasterData(data); + } + } + } + return ResponseResult.success(); + } + + + /** + * 发布工作流。 + * + * @param entryId 流程主键Id。 + * @return 应答结果对象。 + */ + @OperationLog(type = SysOperationLogType.PUBLISH) + @PostMapping("/newPublish") + public ResponseResult publish(@MyRequestBody(required = true) Long entryId) throws XMLStreamException { + String errorMessage; + ResponseResult verifyResult = this.doVerifyAndGet(entryId); + if (!verifyResult.isSuccess()) { + return ResponseResult.errorFrom(verifyResult); + } + FlowEntry flowEntry = verifyResult.getData(); + if (StrUtil.isBlank(flowEntry.getBpmnXml())) { + errorMessage = "数据验证失败,该流程没有流程图不能被发布!"; + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST, errorMessage); + } + ResponseResult taskInfoResult = this.verifyAndGetInitialTaskInfo(flowEntry); + if (!taskInfoResult.isSuccess()) { + return ResponseResult.errorFrom(taskInfoResult); + } + String taskInfo = taskInfoResult.getData() == null ? null : JSON.toJSONString(taskInfoResult.getData()); +// flowEntryService.publish(flowEntry, taskInfo); + flowEntryOnlineService.publish(flowEntry, taskInfo); + return ResponseResult.success(); + } + + private ResponseResult doVerifyAndGet(Long entryId) { + String errorMessage; + if (MyCommonUtil.existBlankArgument(entryId)) { + return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST); + } + FlowEntry flowEntry = flowEntryService.getById(entryId); + if (flowEntry == null) { + return ResponseResult.error(ErrorCodeEnum.DATA_NOT_EXIST); + } + TokenData tokenData = TokenData.takeFromRequest(); + if (!StrUtil.equals(flowEntry.getAppCode(), tokenData.getAppCode())) { + errorMessage = "数据验证失败,当前应用并不存在该流程定义!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + if (ObjectUtil.notEqual(flowEntry.getTenantId(), tokenData.getTenantId())) { + errorMessage = "数据验证失败,当前租户并不存在该流程定义!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + return ResponseResult.success(flowEntry); + } + + private ResponseResult verifyAndGetInitialTaskInfo(FlowEntry flowEntry) throws XMLStreamException { + String errorMessage; + BpmnModel bpmnModel = flowApiService.convertToBpmnModel(flowEntry.getBpmnXml()); + Process process = bpmnModel.getMainProcess(); + if (process == null) { + errorMessage = "数据验证失败,当前流程标识 [" + flowEntry.getProcessDefinitionKey() + "] 关联的流程模型并不存在!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + Collection elementList = process.getFlowElements(); + FlowElement startEvent = null; + // 这里我们只定位流程模型中的第二个节点。 + for (FlowElement flowElement : elementList) { + if (flowElement instanceof StartEvent) { + startEvent = flowElement; + break; + } + } + if (startEvent == null) { + errorMessage = "数据验证失败,当前流程图没有包含 [开始事件] 节点,请修改流程图!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + FlowElement firstTask = this.findFirstTask(elementList, startEvent); + if (firstTask == null) { + errorMessage = "数据验证失败,当前流程图没有包含 [开始事件] 节点没有任何连线,请修改流程图!"; + return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage); + } + TaskInfoVo taskInfoVo; + if (firstTask instanceof UserTask) { + UserTask userTask = (UserTask) firstTask; + String formKey = userTask.getFormKey(); + if (StrUtil.isNotBlank(formKey)) { + taskInfoVo = JSON.parseObject(formKey, TaskInfoVo.class); + } else { + taskInfoVo = new TaskInfoVo(); + } + taskInfoVo.setAssignee(userTask.getAssignee()); + taskInfoVo.setTaskKey(userTask.getId()); + taskInfoVo.setTaskType(FlowTaskType.USER_TYPE); + Map> extensionMap = userTask.getExtensionElements(); + if (MapUtil.isNotEmpty(extensionMap)) { + taskInfoVo.setOperationList(flowTaskExtService.buildOperationListExtensionElement(extensionMap)); + taskInfoVo.setVariableList(flowTaskExtService.buildVariableListExtensionElement(extensionMap)); + } + } else { + taskInfoVo = new TaskInfoVo(); + taskInfoVo.setTaskType(FlowTaskType.OTHER_TYPE); + } + return ResponseResult.success(taskInfoVo); + } + + private FlowElement findFirstTask(Collection elementList, FlowElement startEvent) { + for (FlowElement flowElement : elementList) { + if (flowElement instanceof SequenceFlow) { + SequenceFlow sequenceFlow = (SequenceFlow) flowElement; + if (sequenceFlow.getSourceFlowElement().equals(startEvent)) { + return sequenceFlow.getTargetFlowElement(); + } + } + } + return null; + } +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/listener/ApprovalSettingsByCreateListener.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/listener/ApprovalSettingsByCreateListener.java new file mode 100644 index 0000000..02fe000 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/listener/ApprovalSettingsByCreateListener.java @@ -0,0 +1,94 @@ +package apelet.common.flow.online.listener; + +import apelet.common.core.util.ApplicationContextHolder; +import apelet.common.flow.model.FlowEntry; +import apelet.common.flow.model.FlowNodeTasks; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.flow.service.FlowEntryService; +import apelet.common.flow.service.FlowNodeTasksService; +import apelet.common.flow.service.FlowWorkOrderService; +import apelet.common.online.dto.OnlinePluginExecuteDto; +import apelet.common.online.model.OnlineForm; +import apelet.common.online.service.OnlineFormService; +import apelet.common.online.service.OnlineOperationService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.service.delegate.DelegateTask; + +import java.util.ArrayList; +import java.util.List; + +/** + * 审批设置插件by创建事件 + */ +public class ApprovalSettingsByCreateListener implements TaskListener { + + private final transient FlowNodeTasksService flowNodeTasksService = + ApplicationContextHolder.getBean(FlowNodeTasksService.class); + private final transient FlowEntryService flowEntryService = + ApplicationContextHolder.getBean(FlowEntryService.class); + private final transient OnlineFormService onlineFormService = + ApplicationContextHolder.getBean(OnlineFormService.class); + private final transient FlowWorkOrderService flowWorkOrderService = + ApplicationContextHolder.getBean(FlowWorkOrderService.class); + private final transient OnlineOperationService onlineOperationService = + ApplicationContextHolder.getBean(OnlineOperationService.class); + + private final transient RuntimeService runtimeService = + ApplicationContextHolder.getBean(RuntimeService.class); + @Override + public void notify(DelegateTask delegateTask) { + + String eventName = delegateTask.getEventName(); + List setList = new ArrayList<>(); + if (eventName.equals("create")) { + setList.add(1); + } else if (eventName.equals("assignment")) { + setList.add(2); + } else if (eventName.equals("complete")) { + String operationType = (String)delegateTask.getVariable("operationType"); + if(operationType != null){ + if(operationType.equals("agree")){ + setList.add(5); + }else if(operationType.equals("reject")) { + setList.add(6); + }else if(operationType.equals("refuse")){ + } + } + setList.add(3); + } else if (eventName.equals("delete")) { + setList.add(4); + } + String taskKey = delegateTask.getTaskDefinitionKey(); + String processDefinitionId = delegateTask.getProcessDefinitionId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); +// wrapper.eq(FlowNodeTasks::getFlowId, processDefinitionId); + wrapper.eq(FlowNodeTasks::getTaskKey, taskKey); + wrapper.in(FlowNodeTasks::getExecutionTiming, setList); + List list = flowNodeTasksService.list(wrapper); + if (list != null && list.size() > 0) { + for (FlowNodeTasks flowNodeTasks : list) { + LambdaQueryWrapper wrapperFlowEntry = new LambdaQueryWrapper<>(); + wrapperFlowEntry.eq(FlowEntry::getEntryId, flowNodeTasks.getFlowId()); + FlowEntry flowEntry = flowEntryService.getById(flowNodeTasks.getFlowId()); + OnlineForm form = onlineFormService.getOnlineFormFromCache(flowEntry.getDefaultFormId()); + Long formId = form.getFormId(); + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(delegateTask.getProcessInstanceId()) + .singleResult(); + String billId = processInstance.getBusinessKey(); + if (billId != null && formId > 0) { + OnlinePluginExecuteDto onlinePluginExecuteDto = new OnlinePluginExecuteDto(); + onlinePluginExecuteDto.setBillId(String.valueOf(billId)); + onlinePluginExecuteDto.setFormId(formId); + onlinePluginExecuteDto.setButtonName(flowNodeTasks.getOperation()); + onlineOperationService.executePlugin(onlinePluginExecuteDto, null, null); + } + } + + } + + } +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/object/TransactionalFlowBusinessData.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/object/TransactionalFlowBusinessData.java new file mode 100644 index 0000000..c71ae46 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/object/TransactionalFlowBusinessData.java @@ -0,0 +1,101 @@ +package apelet.common.flow.online.object; + +import apelet.common.core.util.ContextUtil; +import apelet.common.online.object.TransactionalBusinessData; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.servlet.http.HttpServletRequest; + +/** + * 跨库存储工作流的业务数据的事物性事件数据。 + * + * @author guifc + * @date 2023-08-04 + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class TransactionalFlowBusinessData extends TransactionalBusinessData { + + /** + * 流程实例Id。 + */ + private String processInstanceId; + /** + * 流程任务Id。 + */ + private String taskId; + /** + * 流程任务定义标识。 + */ + private String taskKey; + /** + * 流程任务定义名称。 + */ + private String taskName; + /** + * 审批注释。 + */ + private String taskComment; + + private static final ThreadLocal FLOW_BUSINESS_DATA_THREAD_LOCAL = new ThreadLocal<>(); + + public TransactionalFlowBusinessData() { + super(); + } + + public TransactionalFlowBusinessData(HttpServletRequest request) { + super(request); + } + + /** + * 获取HttpServletRequest属性中的事物性时间对象。如果没有情趣对象,则从线程本地存储中获取。 + * + * @return 返回设置在当前请求属性中的事务性时间对象。如果不存在则返回NULL。 + */ + public static TransactionalFlowBusinessData getFromRequestAttribute() { + HttpServletRequest request = ContextUtil.getHttpRequest(); + if (request != null) { + return (TransactionalFlowBusinessData) request.getAttribute(BUSINESS_DATA_ATTR_KEY); + } + return FLOW_BUSINESS_DATA_THREAD_LOCAL.get(); + } + + /** + * 获取HttpServletRequest属性中的事物性时间对象。如果存在直接返回,否则创建新对象,并设置到当前请求的指定属性中。 + * 如果没有情趣对象,则从线程本地存储中创建。 + * + * @return 返回设置在当前请求属性中的事务性时间对象。 + */ + public static TransactionalFlowBusinessData getOrCreateFromRequestAttribute() { + HttpServletRequest request = ContextUtil.getHttpRequest(); + TransactionalFlowBusinessData data; + if (request != null) { + data = (TransactionalFlowBusinessData) request.getAttribute(BUSINESS_DATA_ATTR_KEY); + if (data != null) { + return data; + } + data = new TransactionalFlowBusinessData(request); + request.setAttribute(BUSINESS_DATA_ATTR_KEY, data); + } else { + data = FLOW_BUSINESS_DATA_THREAD_LOCAL.get(); + if (data != null) { + return data; + } + data = new TransactionalFlowBusinessData(); + FLOW_BUSINESS_DATA_THREAD_LOCAL.set(data); + } + return data; + } + + /** + * 移除该对象。 + */ + public static void removeFromRequestAttribute() { + if (ContextUtil.hasRequestContext()) { + ContextUtil.getHttpRequest().removeAttribute(BUSINESS_DATA_ATTR_KEY); + } else { + FLOW_BUSINESS_DATA_THREAD_LOCAL.remove(); + } + } +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowEntryOnlineService.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowEntryOnlineService.java new file mode 100644 index 0000000..5369bb6 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowEntryOnlineService.java @@ -0,0 +1,13 @@ +package apelet.common.flow.online.service; + +import apelet.common.flow.model.FlowEntry; + +import javax.xml.stream.XMLStreamException; + +public interface FlowEntryOnlineService { + + void publish(FlowEntry flowEntry, String initTaskInfo) throws XMLStreamException; + + + void processFlowTaskExtList(FlowEntry flowEntry, String taskInfo) throws XMLStreamException; +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowOnlineOperationService.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowOnlineOperationService.java new file mode 100644 index 0000000..76f1ce0 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/FlowOnlineOperationService.java @@ -0,0 +1,163 @@ +package apelet.common.flow.online.service; + +import apelet.common.flow.model.FlowTaskComment; +import apelet.common.flow.model.FlowTransProducer; +import apelet.common.flow.model.FlowWorkOrder; +import apelet.common.online.model.OnlineDatasource; +import apelet.common.online.model.OnlineDatasourceRelation; +import apelet.common.online.model.OnlineTable; +import com.alibaba.fastjson.JSONObject; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 流程操作服务接口。 + * + * @author guifc + * @date 2023-08-04 + */ +public interface FlowOnlineOperationService { + + /** + * 启动流程实例,并将业务主键Id传给新启动的流程实例。 + * + * @param processDefinitionId 流程实例Id。 + * @param dataId 业务主键Id。 + */ + ProcessInstance startWithBusinessKey(String processDefinitionId, String dataId); + + + /** + * 启动流程实例,并将业务主键Id传给新启动的流程实例。 + * + * @param processDefinitionId 流程实例Id。 + * @param dataId 业务主键Id。 + */ + void startWithBusinessKeyExt(String processDefinitionId, String dataId,Long formId); + + + /** + * 保存在线表单的数据,同时启动流程。如果当前用户是第一个用户任务的Assignee, + * 或者第一个用户任务的Assignee是流程发起人变量,该方法还会自动Take第一个任务。 + * + * @param processDefinitionId 流程定义Id。 + * @param flowTaskComment 流程审批批注对象。 + * @param taskVariableData 流程任务的变量数据。 + * @param table 表对象。 + * @param data 表数据。 + */ + void saveNewAndStartProcess( + String processDefinitionId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable table, + JSONObject data); + + /** + * 保存在线表单的数据,同时启动流程。如果当前用户是第一个用户任务的Assignee, + * 或者第一个用户任务的Assignee是流程发起人变量,该方法还会自动Take第一个任务。 + * + * @param processDefinitionId 流程定义Id。 + * @param flowTaskComment 流程审批批注对象。 + * @param taskVariableData 流程任务的变量数据。 + * @param masterTable 主表对象。 + * @param masterData 主表数据。 + * @param slaveDataListMap 关联从表数据Map。 + */ + void saveNewAndStartProcess( + String processDefinitionId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable masterTable, + JSONObject masterData, + Map> slaveDataListMap); + + /** + * 保存在线表单的草稿数据,同时启动一个流程实例。 + * + * @param processDefinitionId 流程定义Id。 + * @param tableId 在线表单主表Id。 + * @param masterData 主表数据。 + * @param slaveData 所有关联从表数据。 + * @return 流程工单对象。 + */ + FlowWorkOrder saveNewDraftAndStartProcess( + String processDefinitionId, Long tableId, JSONObject masterData, JSONObject slaveData); + + /** + * 保存在线表单的数据,同时Take用户任务。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @param flowTaskComment 流程审批批注对象。 + * @param taskVariableData 流程任务的变量数据。 + * @param table 表对象。 + * @param data 表数据。 + */ + void saveNewAndTakeTask( + String processInstanceId, + String taskId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable table, + JSONObject data); + + /** + * 保存在线表单的数据,同时Take用户任务。 + * + * @param processInstanceId 流程实例Id。 + * @param taskId 流程任务Id。 + * @param flowTaskComment 流程审批批注对象。 + * @param taskVariableData 流程任务的变量数据。 + * @param masterTable 主表对象。 + * @param masterData 主表数据。 + * @param slaveDataListMap 关联从表数据Map。 + */ + void saveNewAndTakeTask( + String processInstanceId, + String taskId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable masterTable, + JSONObject masterData, + Map> slaveDataListMap); + + /** + * 保存业务表数据,同时接收流程任务。 + * + * @param task 流程任务。 + * @param flowTaskComment 流程审批批注对象。 + * @param taskVariableData 流程任务的变量数据。 + * @param datasource 主表所在数据源。 + * @param masterData 主表数据。 + * @param masterDataId 主表数据主键。 + * @param slaveDataListMap 从表数据。 + */ + void updateAndTakeTask( + Task task, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineDatasource datasource, + JSONObject masterData, + String masterDataId, + Map> slaveDataListMap); + + /** + * 修复业务数据。 + * + * @param flowTransProducer 流程操作流水生产者对象。 + */ + void fixBusinessData(FlowTransProducer flowTransProducer); + + /** + * 获取在线表单工作流Id所关联的权限数据,包括权限字列表和权限资源列表。 + * + * @param onlineFormEntryIds 在线表单工作流Id集合。 + * @return 参数中在线表单工作流Id集合所关联的权限数据。 + */ + List> calculatePermData(Set onlineFormEntryIds); +} 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 new file mode 100644 index 0000000..57ca761 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowEntryOnlineServiceImpl.java @@ -0,0 +1,319 @@ +package apelet.common.flow.online.service.impl; + +import apelet.common.core.object.TokenData; +import apelet.common.core.util.MyModelUtil; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.dao.FlowEntryMapper; +import apelet.common.flow.dao.FlowEntryPublishMapper; +import apelet.common.flow.dao.FlowEntryPublishVariableMapper; +import apelet.common.flow.listener.*; +import apelet.common.flow.model.*; +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.util.BaseFlowIdentityExtHelper; +import apelet.common.flow.util.FlowCustomExtFactory; +import apelet.common.flow.util.FlowRedisKeyUtil; +import apelet.common.redis.util.CommonRedisUtil; +import apelet.common.sequence.wrapper.IdGeneratorWrapper; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.Cleanup; +import org.flowable.bpmn.converter.BpmnXMLConverter; +import org.flowable.bpmn.model.*; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +@Service("flowEntryOnlineService") +public class FlowEntryOnlineServiceImpl implements FlowEntryOnlineService { + + @Autowired + private FlowEntryMapper flowEntryMapper; + @Autowired + private FlowEntryPublishMapper flowEntryPublishMapper; + @Autowired + private FlowEntryPublishVariableMapper flowEntryPublishVariableMapper; + @Autowired + private FlowEntryVariableService flowEntryVariableService; + @Autowired + private FlowCategoryService flowCategoryService; + @Autowired + private FlowTaskExtService flowTaskExtService; + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private RepositoryService repositoryService; + @Autowired + private IdGeneratorWrapper idGenerator; + @Autowired + private CommonRedisUtil commonRedisUtil; + + @Transactional(rollbackFor = Exception.class) + @Override + public void publish(FlowEntry flowEntry, String initTaskInfo) throws XMLStreamException { + commonRedisUtil.evictFormCache( + FlowRedisKeyUtil.makeFlowEntryKey(flowEntry.getProcessDefinitionKey())); + FlowCategory flowCategory = flowCategoryService.getById(flowEntry.getCategoryId()); + InputStream xmlStream = new ByteArrayInputStream( + flowEntry.getBpmnXml().getBytes(StandardCharsets.UTF_8)); + @Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(xmlStream); + BpmnXMLConverter converter = new BpmnXMLConverter(); + BpmnModel bpmnModel = converter.convertToBpmnModel(reader); + bpmnModel.getMainProcess().setName(flowEntry.getProcessDefinitionName()); + bpmnModel.getMainProcess().setId(flowEntry.getProcessDefinitionKey()); + flowApiService.addProcessInstanceEndListener(bpmnModel, FlowFinishedListener.class); + List flowTaskExtList = flowTaskExtService.buildTaskExtList(bpmnModel); + if (StrUtil.isNotBlank(flowEntry.getExtensionData())) { + FlowEntryExtensionData flowEntryExtensionData = + JSON.parseObject(flowEntry.getExtensionData(), FlowEntryExtensionData.class); + this.mergeTaskNotifyData(flowEntryExtensionData, flowTaskExtList); + this.processFlowTaskRevive(flowEntryExtensionData, bpmnModel); + } + this.processFlowTaskExtList(flowTaskExtList, bpmnModel); + TokenData tokenData = TokenData.takeFromRequest(); + Deployment deploy = repositoryService.createDeployment() + .addBpmnModel(flowEntry.getProcessDefinitionKey() + ".bpmn", bpmnModel) + .tenantId(tokenData.getTenantId() != null ? tokenData.getTenantId().toString() : tokenData.getAppCode()) + .name(flowEntry.getProcessDefinitionName()) + .key(flowEntry.getProcessDefinitionKey()) + .category(flowCategory.getCode()) + .deploy(); + ProcessDefinition processDefinition = flowApiService.getProcessDefinitionByDeployId(deploy.getId()); + FlowEntryPublish flowEntryPublish = new FlowEntryPublish(); + flowEntryPublish.setEntryPublishId(idGenerator.nextLongId()); + flowEntryPublish.setEntryId(flowEntry.getEntryId()); + flowEntryPublish.setProcessDefinitionId(processDefinition.getId()); + flowEntryPublish.setDeployId(processDefinition.getDeploymentId()); + flowEntryPublish.setPublishVersion(processDefinition.getVersion()); + flowEntryPublish.setActiveStatus(true); + flowEntryPublish.setMainVersion(flowEntry.getStatus().equals(FlowEntryStatus.UNPUBLISHED)); + flowEntryPublish.setCreateUserId(TokenData.takeFromRequest().getUserId()); + flowEntryPublish.setPublishTime(new Date()); + flowEntryPublish.setInitTaskInfo(initTaskInfo); + flowEntryPublish.setExtensionData(flowEntry.getExtensionData()); + flowEntryPublish.setStartConditions(flowEntry.getStartConditions()); + AnalyzedNode rootNode = flowApiService.analyzeBpmnRoads(processDefinition.getId()); + flowEntryPublish.setAnalyzedNodeJson(JSON.toJSONString(rootNode)); + flowEntryPublishMapper.insert(flowEntryPublish); + FlowEntry updatedFlowEntry = new FlowEntry(); + updatedFlowEntry.setEntryId(flowEntry.getEntryId()); + updatedFlowEntry.setStatus(FlowEntryStatus.PUBLISHED); + updatedFlowEntry.setLatestPublishTime(new Date()); + // 对于从未发布过的工作,第一次发布的时候会将本地发布置位主版本。 + if (flowEntry.getStatus().equals(FlowEntryStatus.UNPUBLISHED)) { + updatedFlowEntry.setMainEntryPublishId(flowEntryPublish.getEntryPublishId()); + } + flowEntryMapper.updateById(updatedFlowEntry); + FlowEntryVariable flowEntryVariableFilter = new FlowEntryVariable(); + flowEntryVariableFilter.setEntryId(flowEntry.getEntryId()); + List flowEntryVariableList = + flowEntryVariableService.getFlowEntryVariableList(flowEntryVariableFilter, null); + if (CollUtil.isNotEmpty(flowTaskExtList)) { + flowTaskExtList.forEach(t -> t.setProcessDefinitionId(processDefinition.getId())); + flowTaskExtService.saveBatch(flowTaskExtList); + } + this.insertEntryPublishVariables(flowEntryVariableList, flowEntryPublish.getEntryPublishId()); + } + + + @Override + public void processFlowTaskExtList(FlowEntry flowEntry,String taskInfo) throws XMLStreamException { + + InputStream xmlStream = new ByteArrayInputStream( + flowEntry.getBpmnXml().getBytes(StandardCharsets.UTF_8)); + @Cleanup XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(xmlStream); + BpmnXMLConverter converter = new BpmnXMLConverter(); + BpmnModel bpmnModel = converter.convertToBpmnModel(reader); + bpmnModel.getMainProcess().setName(flowEntry.getProcessDefinitionName()); + bpmnModel.getMainProcess().setId(flowEntry.getProcessDefinitionKey()); + + List flowTaskExtList = flowTaskExtService.buildTaskExtList(bpmnModel); + + List elementList = new LinkedList<>(); + this.calculateAllElementList(bpmnModel.getMainProcess().getFlowElements(), elementList); + + Map elementMap = elementList.stream() + .filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, c -> c)); + for (FlowTaskExt t : flowTaskExtList) { + UserTask userTask = (UserTask) elementMap.get(t.getTaskId()); + // create 创建 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "create"); + // assignment 分配/指派 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "assignment"); + // complete 完成 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "complete"); + // delete 删除 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "delete"); + } + } + + private void calculateAllElementList(Collection elements, List resultList) { + resultList.addAll(elements); + for (FlowElement element : elements) { + if (element instanceof SubProcess) { + this.calculateAllElementList(((SubProcess) element).getFlowElements(), resultList); + } + } + } + + private void mergeTaskNotifyData(FlowEntryExtensionData flowEntryExtensionData, List flowTaskExtList) { + if (CollUtil.isEmpty(flowEntryExtensionData.getNotifyTypes())) { + return; + } + List flowTaskNotifyTypes = + flowEntryExtensionData.getNotifyTypes().stream().filter(StrUtil::isNotBlank).collect(Collectors.toList()); + if (CollUtil.isEmpty(flowTaskNotifyTypes)) { + return; + } + for (FlowTaskExt flowTaskExt : flowTaskExtList) { + if (flowTaskExt.getExtraDataJson() == null) { + JSONObject o = new JSONObject(); + o.put(FlowConstant.USER_TASK_NOTIFY_TYPES_KEY, flowTaskNotifyTypes); + flowTaskExt.setExtraDataJson(o.toJSONString()); + } else { + FlowUserTaskExtData taskExtData = + JSON.parseObject(flowTaskExt.getExtraDataJson(), FlowUserTaskExtData.class); + if (CollUtil.isEmpty(taskExtData.getFlowNotifyTypeList())) { + taskExtData.setFlowNotifyTypeList(flowTaskNotifyTypes); + } else { + Set notifyTypesSet = taskExtData.getFlowNotifyTypeList() + .stream().filter(StrUtil::isNotBlank).collect(Collectors.toSet()); + notifyTypesSet.addAll(flowTaskNotifyTypes); + taskExtData.setFlowNotifyTypeList(new LinkedList<>(notifyTypesSet)); + } + flowTaskExt.setExtraDataJson(JSON.toJSONString(taskExtData)); + } + } + } + + private void processFlowTaskRevive(FlowEntryExtensionData flowEntryExtensionData, BpmnModel bpmnModel) { + if (BooleanUtil.isTrue(flowEntryExtensionData.getSupportRevive())) { + List mainProcessUserTasks = bpmnModel.getMainProcess().getFlowElements() + .stream().filter(UserTask.class::isInstance).map(t -> (UserTask) t).collect(Collectors.toList()); + mainProcessUserTasks.forEach(t -> flowApiService.addTaskCreateListener(t, FlowTaskReviveListener.class)); + } + } + + private void processFlowTaskExtList(List flowTaskExtList, BpmnModel bpmnModel) { + List elementList = new LinkedList<>(); + this.calculateAllElementList(bpmnModel.getMainProcess().getFlowElements(), elementList); + this.doAddLatestApprovalStatusListener(elementList); + Map elementMap = elementList.stream() + .filter(UserTask.class::isInstance).collect(Collectors.toMap(FlowElement::getId, c -> c)); + BaseFlowIdentityExtHelper flowIdentityExtHelper = flowCustomExtFactory.getFlowIdentityExtHelper(); + for (FlowTaskExt t : flowTaskExtList) { + UserTask userTask = (UserTask) elementMap.get(t.getTaskId()); + flowApiService.addTaskCreateListener(userTask, FlowUserTaskListener.class); + // create 创建 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "create"); + // assignment 分配/指派 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "assignment"); + // complete 完成 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "complete"); + // delete 删除 + flowApiService.addTaskListenerByEvent(userTask, ApprovalSettingsByCreateListener.class, "delete"); + + Map> attributes = userTask.getAttributes(); + if (CollUtil.isNotEmpty(attributes.get(FlowConstant.USER_TASK_AUTO_SKIP_KEY))) { + flowApiService.addTaskCreateListener(userTask, AutoSkipTaskListener.class); + } + // 如果流程图中包含部门领导审批和上级部门领导审批的选项,就需要注册 FlowCustomExtFactory 工厂中的 + // BaseFlowIdentityExtHelper 对象,该注册操作需要业务模块中实现。 + if (StrUtil.equals(t.getGroupType(), FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER)) { + userTask.setCandidateGroups( + CollUtil.newArrayList("${" + FlowConstant.GROUP_TYPE_UP_DEPT_POST_LEADER_VAR + "}")); + Assert.notNull(flowIdentityExtHelper); + flowApiService.addTaskCreateListener(userTask, flowIdentityExtHelper.getUpDeptPostLeaderListener()); + } else if (StrUtil.equals(t.getGroupType(), FlowConstant.GROUP_TYPE_DEPT_POST_LEADER)) { + userTask.setCandidateGroups( + CollUtil.newArrayList("${" + FlowConstant.GROUP_TYPE_DEPT_POST_LEADER_VAR + "}")); + Assert.notNull(flowIdentityExtHelper); + flowApiService.addTaskCreateListener(userTask, flowIdentityExtHelper.getDeptPostLeaderListener()); + } else if (StrUtil.equals(t.getGroupType(), FlowConstant.GROUP_TYPE_POST)) { + Assert.notNull(t.getDeptPostListJson()); + List groupDataList = + JSON.parseArray(t.getDeptPostListJson(), FlowTaskPostCandidateGroup.class); + List candidateGroupList = + FlowTaskPostCandidateGroup.buildCandidateGroupList(groupDataList); + userTask.setCandidateGroups(candidateGroupList); + } + this.processFlowTaskExtListener(userTask, t); + } + } + + private void insertEntryPublishVariables(List entryVariableList, Long entryPublishId) { + if (CollUtil.isEmpty(entryVariableList)) { + return; + } + List entryPublishVariableList = + MyModelUtil.copyCollectionTo(entryVariableList, FlowEntryPublishVariable.class); + for (FlowEntryPublishVariable variable : entryPublishVariableList) { + variable.setVariableId(idGenerator.nextLongId()); + variable.setEntryPublishId(entryPublishId); + } + flowEntryPublishVariableMapper.insertList(entryPublishVariableList); + } + + private void doAddLatestApprovalStatusListener(Collection elementList) { + List sequenceFlowList = + elementList.stream().filter(SequenceFlow.class::isInstance).collect(Collectors.toList()); + for (FlowElement sequenceFlow : sequenceFlowList) { + FlowElementExtProperty extProperty = flowTaskExtService.buildFlowElementExt(sequenceFlow); + if (extProperty != null && extProperty.getLatestApprovalStatus() != null) { + List fieldExtensions = new LinkedList<>(); + FieldExtension fieldExtension = new FieldExtension(); + fieldExtension.setFieldName(FlowConstant.LATEST_APPROVAL_STATUS_KEY); + fieldExtension.setStringValue(extProperty.getLatestApprovalStatus().toString()); + fieldExtensions.add(fieldExtension); + flowApiService.addExecutionListener( + sequenceFlow, UpdateLatestApprovalStatusListener.class, "start", fieldExtensions); + } + } + List subProcesseList = elementList.stream() + .filter(SubProcess.class::isInstance).map(SubProcess.class::cast).collect(Collectors.toList()); + for (SubProcess subProcess : subProcesseList) { + this.doAddLatestApprovalStatusListener(subProcess.getFlowElements()); + } + } + + private void processFlowTaskExtListener(UserTask userTask, FlowTaskExt taskExt) { + if (StrUtil.isBlank(taskExt.getExtraDataJson())) { + return; + } + FlowUserTaskExtData userTaskExtData = + JSON.parseObject(taskExt.getExtraDataJson(), FlowUserTaskExtData.class); + if (CollUtil.isNotEmpty(userTaskExtData.getFlowNotifyTypeList())) { + flowApiService.addTaskCreateListener(userTask, FlowTaskNotifyListener.class); + } + if (userTaskExtData.getRejectType().equals(FlowUserTaskExtData.REJECT_TYPE_BACK_TO_SOURCE)) { + flowApiService.addTaskCreateListener(userTask, FlowTaskRejectBackListener.class); + } + if (StrUtil.isNotBlank(userTaskExtData.getTimeoutHandleWay())) { + flowApiService.addTaskCreateListener(userTask, FlowTaskTimeoutListener.class); + } + if (StrUtil.isNotBlank(userTaskExtData.getEmptyUserHandleWay())) { + flowApiService.addTaskCreateListener(userTask, FlowEmptyUserHandleListener.class); + } + } +} 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 new file mode 100644 index 0000000..00ac7ea --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineBusinessServiceImpl.java @@ -0,0 +1,102 @@ +package apelet.common.flow.online.service.impl; + +import apelet.common.core.annotation.MyDataSource; +import apelet.common.core.constant.ApplicationConstant; +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.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 lombok.extern.slf4j.Slf4j; +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; + +/** + * 在线表单和流程监听器进行数据对接时的服务实现类。 + * + * @author guifc + * @date 2023-08-04 + */ +@Slf4j +@MyDataSource(ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowOnlineBusinessService") +public class FlowOnlineBusinessServiceImpl implements BaseFlowOnlineService { + + @Autowired + private FlowCustomExtFactory flowCustomExtFactory; + @Autowired + private OnlineTableService onlineTableService; + @Autowired + private OnlineDatasourceService onlineDatasourceService; + @Autowired + private OnlineDatasourceRelationService onlineDatasourceRelationService; + @Autowired + private OnlineOperationService onlineOperationService; + + @PostConstruct + public void doRegister() { + flowCustomExtFactory.getOnlineBusinessDataExtHelper().setOnlineBusinessService(this); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void updateFlowStatus(FlowWorkOrder workOrder) { + OnlineTable onlineTable = onlineTableService.getOnlineTableFromCache(workOrder.getOnlineTableId()); + if (onlineTable == null) { + log.error("OnlineTableId [{}] doesn't exist while calling FlowOnlineBusinessServiceImpl.updateFlowStatus", + workOrder.getOnlineTableId()); + return; + } + String dataId = workOrder.getBusinessKey(); + this.handleTransactionalFlowBusinessData(workOrder, "FlowOnlineBusinessServiceImpl.updateFlowStatus"); + for (OnlineColumn column : onlineTable.getColumnMap().values()) { + if (ObjectUtil.equals(column.getFieldKind(), FieldKind.FLOW_FINISHED_STATUS)) { + onlineOperationService.updateColumn(onlineTable, dataId, column, workOrder.getFlowStatus()); + } + if (ObjectUtil.equals(column.getFieldKind(), FieldKind.FLOW_APPROVAL_STATUS)) { + onlineOperationService.updateColumn(onlineTable, dataId, column, workOrder.getLatestApprovalStatus()); + } + } + } + + @Override + public void deleteBusinessData(FlowWorkOrder workOrder) { + OnlineTable onlineTable = onlineTableService.getOnlineTableFromCache(workOrder.getOnlineTableId()); + if (onlineTable == null) { + log.error("OnlineTableId [{}] doesn't exist while calling FlowOnlineBusinessServiceImpl.deleteBusinessData", + workOrder.getOnlineTableId()); + return; + } + OnlineDatasource datasource = + onlineDatasourceService.getOnlineDatasourceByMasterTableId(onlineTable.getTableId()); + List relationList = + onlineDatasourceRelationService.getOnlineDatasourceRelationListFromCache(CollUtil.newHashSet(datasource.getDatasourceId())); + String dataId = workOrder.getBusinessKey(); + for (OnlineDatasourceRelation relation : relationList) { + OnlineTable slaveTable = onlineTableService.getOnlineTableFromCache(relation.getSlaveTableId()); + if (slaveTable == null) { + throw new OnlineRuntimeException("数据验证失败,数据源关联 [" + relation.getRelationName() + "] 的从表Id不存在!"); + } + relation.setSlaveTable(slaveTable); + } + this.handleTransactionalFlowBusinessData(workOrder, "FlowOnlineBusinessServiceImpl.deleteBusinessData"); + onlineOperationService.delete(onlineTable, relationList, dataId); + } + + private void handleTransactionalFlowBusinessData(FlowWorkOrder workOrder, String desc) { + TransactionalFlowBusinessData eventData = TransactionalFlowBusinessData.getFromRequestAttribute(); + if (eventData != null) { + eventData.setProcessInstanceId(workOrder.getProcessInstanceId()); + eventData.setOperationDesc(desc); + } + } +} diff --git a/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineOperationServiceImpl.java b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineOperationServiceImpl.java new file mode 100644 index 0000000..3e667f6 --- /dev/null +++ b/common/common-flow-online/src/main/java/apelet/common/flow/online/service/impl/FlowOnlineOperationServiceImpl.java @@ -0,0 +1,434 @@ +package apelet.common.flow.online.service.impl; + +import apelet.common.core.annotation.MultiDatabaseWriteMethod; +import apelet.common.core.annotation.MyDataSource; +import apelet.common.core.config.CoreProperties; +import apelet.common.core.constant.ApplicationConstant; +import apelet.common.core.object.CallResult; +import apelet.common.core.object.ObjectValue; +import apelet.common.dbutil.provider.DataSourceProvider; +import apelet.common.dbutil.provider.PostgreSqlProvider; +import apelet.common.flow.config.FlowProperties; +import apelet.common.flow.constant.FlowApprovalType; +import apelet.common.flow.constant.FlowConstant; +import apelet.common.flow.constant.FlowTaskStatus; +import apelet.common.flow.dao.FlowTransProducerMapper; +import apelet.common.flow.exception.FlowOperationException; +import apelet.common.flow.model.*; +import apelet.common.flow.online.object.TransactionalFlowBusinessData; +import apelet.common.flow.online.service.FlowOnlineOperationService; +import apelet.common.flow.service.FlowApiService; +import apelet.common.flow.service.FlowEntryService; +import apelet.common.flow.service.FlowWorkOrderService; +import apelet.common.generator.utils.OrmGenDataSourceUtil; +import apelet.common.online.config.OnlineProperties; +import apelet.common.online.exception.OnlineRuntimeException; +import apelet.common.online.model.*; +import apelet.common.online.object.TransactionalBusinessData; +import apelet.common.online.service.*; +import apelet.common.online.util.OnlineDataSourceUtil; +import apelet.common.orm.impl.Selector; +import apelet.common.orm.impl.SelectorItem; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +import java.sql.Connection; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@MyDataSource(ApplicationConstant.COMMON_FLOW_AND_ONLINE_DATASOURCE_TYPE) +@Service("flowOnlineOperationService") +public class FlowOnlineOperationServiceImpl implements FlowOnlineOperationService { + + @Autowired + private FlowApiService flowApiService; + @Autowired + private FlowWorkOrderService flowWorkOrderService; + @Autowired + private FlowEntryService flowEntryService; + @Autowired + private OnlineOperationService onlineOperationService; + @Autowired + private OnlineDatasourceService onlineDatasourceService; + @Autowired + private OnlineProperties onlineProperties; + @Autowired + private FlowProperties flowProperties; + @Autowired + private CoreProperties coreProperties; + @Autowired + private OnlineDataSourceUtil dataSourceUtil; + @Autowired + private FlowTransProducerMapper flowTransProducerMapper; + @Autowired + private OnlineFormService onlineFormService; + @Autowired + private OnlineTableService onlineTableService; + + @Autowired + private OrmGenDataSourceUtil ormGenDataSourceUtil; + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public ProcessInstance startWithBusinessKey(String processDefinitionId, String dataId) { + ProcessInstance instance = flowApiService.start(processDefinitionId, dataId); + flowWorkOrderService.saveNew(instance, dataId, null, null); + return instance; + } + + @Override + public void startWithBusinessKeyExt(String processDefinitionId, String dataId, Long formId) { + OnlineForm OnlineForm = onlineFormService.getOnlineFormFromCache(formId); + OnlineTable onlineTable = onlineTableService.getOnlineTableFromCache(OnlineForm.getMasterTableId()); + Selector selector = new Selector(); + selector.getList().add(new SelectorItem("billstatus")); + ObjectValue objectValue = ormGenDataSourceUtil.queryOne(onlineTable.getTableName(),Long.valueOf(dataId),selector); + if(objectValue == null){ + String errorMessage = "无效数据!"; + throw new RuntimeException(errorMessage); + } + if(!objectValue.get("billstatus").equals("A")){ + throw new RuntimeException("只有保存状态才能提交"); + } + objectValue.put("billstatus","B"); + try { + ormGenDataSourceUtil.update(onlineTable.getTableName(),objectValue,selector); + }catch (Exception e){ + e.printStackTrace(); + String errorMessage = "更新失败!错误信息:"+e.getMessage(); + throw new RuntimeException(errorMessage); + } + if(processDefinitionId != null){ + JSONObject taskVariableData = new JSONObject(); + ProcessInstance instance = this.startWithBusinessKey(processDefinitionId,dataId); + FlowTaskComment flowTaskComment = new FlowTaskComment(); + flowTaskComment.setApprovalType("agree"); + flowTaskComment.setTaskComment("发起人"); + flowApiService.takeFirstTask(instance.getProcessInstanceId(), flowTaskComment, taskVariableData); + } + + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNewAndStartProcess( + String processDefinitionId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable table, + JSONObject data) { + this.saveNewAndStartProcess(processDefinitionId, flowTaskComment, taskVariableData, table, data, null); + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNewAndStartProcess( + String processDefinitionId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable masterTable, + JSONObject masterData, + Map> slaveDataListMap) { + Object dataId = onlineOperationService.saveNewWithRelation(masterTable, masterData, slaveDataListMap); + Assert.notNull(dataId); + if (taskVariableData == null) { + taskVariableData = new JSONObject(); + } + taskVariableData.put(FlowConstant.MASTER_DATA_KEY, masterData); + taskVariableData.put(FlowConstant.SLAVE_DATA_KEY, this.normailizeSlaveDataListMap(slaveDataListMap)); + taskVariableData.put(FlowConstant.MASTER_TABLE_KEY, masterTable); + ProcessInstance instance = flowApiService.start(processDefinitionId, dataId); + flowWorkOrderService.saveNew(instance, dataId, masterTable.getTableId(), null); + flowApiService.takeFirstTask(instance.getProcessInstanceId(), flowTaskComment, taskVariableData); + // 这里需要在创建工单后再次更新一下工单状态,在flowApiService.completeTask中的更新, + // 因为当时没有创建工单对象,更新会不起任何作用,所以这里要补偿一下。 + Integer approvalStatus = MapUtil.getInt(taskVariableData, FlowConstant.LATEST_APPROVAL_STATUS_KEY); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(instance.getId(), approvalStatus); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public FlowWorkOrder saveNewDraftAndStartProcess( + String processDefinitionId, Long tableId, JSONObject masterData, JSONObject slaveData) { + ProcessInstance instance = flowApiService.start(processDefinitionId, null); + return flowWorkOrderService.saveNewWithDraft( + instance, tableId, null, JSON.toJSONString(masterData), JSON.toJSONString(slaveData)); + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNewAndTakeTask( + String processInstanceId, + String taskId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable table, + JSONObject data) { + this.saveNewAndTakeTask( + processInstanceId, taskId, flowTaskComment, taskVariableData, table, data, null); + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public void saveNewAndTakeTask( + String processInstanceId, + String taskId, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineTable masterTable, + JSONObject masterData, + Map> slaveDataListMap) { + Object dataId = onlineOperationService.saveNewWithRelation(masterTable, masterData, slaveDataListMap); + Assert.notNull(dataId); + Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId); + flowApiService.setBusinessKeyForProcessInstance(processInstanceId, dataId); + Map variables = + flowApiService.initAndGetProcessInstanceVariables(task.getProcessDefinitionId()); + if (taskVariableData == null) { + taskVariableData = new JSONObject(); + } + taskVariableData.putAll(variables); + taskVariableData.put(FlowConstant.MASTER_DATA_KEY, masterData); + taskVariableData.put(FlowConstant.SLAVE_DATA_KEY, this.normailizeSlaveDataListMap(slaveDataListMap)); + taskVariableData.put(FlowConstant.MASTER_TABLE_KEY, masterTable); + flowApiService.completeTask(task, flowTaskComment, taskVariableData, null); + ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId); + FlowWorkOrder flowWorkOrder = + flowWorkOrderService.getFlowWorkOrderByProcessInstanceId(instance.getProcessInstanceId()); + if (flowWorkOrder == null) { + flowWorkOrderService.saveNew(instance, dataId, masterTable.getTableId(), null); + } else { + flowWorkOrder.setBusinessKey(dataId.toString()); + flowWorkOrder.setUpdateTime(new Date()); + flowWorkOrder.setFlowStatus(FlowTaskStatus.SUBMITTED); + flowWorkOrderService.updateById(flowWorkOrder); + } + } + + @MultiDatabaseWriteMethod + @Transactional(rollbackFor = Exception.class) + @Override + public void updateAndTakeTask( + Task task, + FlowTaskComment flowTaskComment, + JSONObject taskVariableData, + OnlineDatasource datasource, + JSONObject masterData, + String masterDataId, + Map> slaveDataListMap) { + int flowStatus = FlowTaskStatus.APPROVING; + if (flowTaskComment.getApprovalType().equals(FlowApprovalType.REFUSE)) { + flowStatus = FlowTaskStatus.REFUSED; + } else if (flowTaskComment.getApprovalType().equals(FlowApprovalType.STOP)) { + flowStatus = FlowTaskStatus.FINISHED; + } + OnlineTable masterTable = datasource.getMasterTable(); + Long datasourceId = datasource.getDatasourceId(); + flowWorkOrderService.updateFlowStatusByProcessInstanceId(task.getProcessInstanceId(), flowStatus); + this.updateMasterData(masterTable, masterData, masterDataId); + if (slaveDataListMap != null) { + for (Map.Entry> relationEntry : slaveDataListMap.entrySet()) { + Long relationId = relationEntry.getKey().getRelationId(); + onlineOperationService.updateRelationData( + masterTable, masterData, masterDataId, datasourceId, relationId, relationEntry.getValue()); + } + } + if (flowTaskComment.getApprovalType().equals(FlowApprovalType.STOP)) { + Integer s = MapUtil.getInt(taskVariableData, FlowConstant.LATEST_APPROVAL_STATUS_KEY); + flowWorkOrderService.updateLatestApprovalStatusByProcessInstanceId(task.getProcessInstanceId(), s); + CallResult stopResult = flowApiService.stopProcessInstance( + task.getProcessInstanceId(), flowTaskComment.getTaskComment(), flowStatus); + if (!stopResult.isSuccess()) { + throw new FlowOperationException(stopResult.getErrorMessage()); + } + } else { + if (taskVariableData == null) { + taskVariableData = new JSONObject(); + } + taskVariableData.put(FlowConstant.MASTER_DATA_KEY, masterData); + taskVariableData.put(FlowConstant.SLAVE_DATA_KEY, this.normailizeSlaveDataListMap(slaveDataListMap)); + taskVariableData.put(FlowConstant.MASTER_TABLE_KEY, masterTable); + flowApiService.completeTask(task, flowTaskComment, taskVariableData, null); + } + } + + @Override + public void fixBusinessData(FlowTransProducer flowTransProducer) { + try { + String sql = "SELECT * FROM zz_flow_trans_consumer WHERE trans_id = " + flowTransProducer.getTransId(); + List> dataList = dataSourceUtil.query(flowTransProducer.getDblinkId(), sql); + if (CollUtil.isNotEmpty(dataList)) { + return; + } + } catch (Exception e) { + log.error("Failed to call dataSourceUtil.query", e); + throw new OnlineRuntimeException(e.getMessage()); + } + TransactionalFlowBusinessData businessData = new TransactionalFlowBusinessData(); + businessData.setTransId(flowTransProducer.getTransId()); + businessData.setDblinkId(flowTransProducer.getDblinkId()); + List sqlDataList = + JSON.parseArray(flowTransProducer.getSqlData(), TransactionalBusinessData.BusinessSqlData.class); + businessData.setSqlDataList(sqlDataList); + try { + this.doHandle(businessData); + } catch (Exception e) { + log.error(e.getMessage(), e); + throw new OnlineRuntimeException(e.getMessage()); + } + } + + @Override + public List> calculatePermData(Set onlineFormEntryIds) { + if (CollUtil.isEmpty(onlineFormEntryIds)) { + return new LinkedList<>(); + } + List> permDataList = new LinkedList<>(); + List flowEntries = flowEntryService.getInList(onlineFormEntryIds); + Set pageIds = flowEntries.stream().map(FlowEntry::getPageId).collect(Collectors.toSet()); + Map pageAndVariableNameMap = + onlineDatasourceService.getPageIdAndVariableNameMapByPageIds(pageIds); + for (FlowEntry flowEntry : flowEntries) { + JSONObject permData = new JSONObject(); + permData.put("entryId", flowEntry.getEntryId()); + String key = StrUtil.upperFirst(flowEntry.getProcessDefinitionKey()); + List permCodeList = new LinkedList<>(); + String formPermCode = "form" + key; + permCodeList.add(formPermCode); + permCodeList.add(formPermCode + ":fragment" + key); + permData.put("permCodeList", permCodeList); + String flowUrlPrefix = flowProperties.getUrlPrefix(); + String onlineUrlPrefix = onlineProperties.getUrlPrefix(); + List permList = CollUtil.newLinkedList( + onlineUrlPrefix + "/onlineForm/view", + onlineUrlPrefix + "/onlineForm/render", + onlineUrlPrefix + "/onlineOperation/listByOneToManyRelationId/" + pageAndVariableNameMap.get(flowEntry.getPageId()), + flowUrlPrefix + "/flowOperation/viewInitialHistoricTaskInfo", + flowUrlPrefix + "/flowOperation/startOnly", + flowUrlPrefix + "/flowOperation/viewInitialTaskInfo", + flowUrlPrefix + "/flowOperation/viewRuntimeTaskInfo", + flowUrlPrefix + "/flowOperation/viewProcessBpmn", + flowUrlPrefix + "/flowOperation/viewHighlightFlowData", + flowUrlPrefix + "/flowOperation/listFlowTaskComment", + flowUrlPrefix + "/flowOperation/cancelWorkOrder", + flowUrlPrefix + "/flowOperation/listRuntimeTask", + flowUrlPrefix + "/flowOperation/listHistoricProcessInstance", + flowUrlPrefix + "/flowOperation/listHistoricTask", + flowUrlPrefix + "/flowOperation/freeJumpTo", + flowUrlPrefix + "/flowOnlineOperation/startPreview", + flowUrlPrefix + "/flowOnlineOperation/viewUserTask", + flowUrlPrefix + "/flowOnlineOperation/viewHistoricProcessInstance", + flowUrlPrefix + "/flowOnlineOperation/submitUserTask", + flowUrlPrefix + "/flowOnlineOperation/upload", + flowUrlPrefix + "/flowOnlineOperation/download", + flowUrlPrefix + "/flowOperation/submitConsign", + flowUrlPrefix + "/flowOnlineOperation/startAndTakeUserTask/" + flowEntry.getProcessDefinitionKey(), + flowUrlPrefix + "/flowOnlineOperation/startAndSaveDraft/" + flowEntry.getProcessDefinitionKey(), + flowUrlPrefix + "/flowOnlineOperation/listWorkOrder/" + flowEntry.getProcessDefinitionKey(), + flowUrlPrefix + "/flowOnlineOperation/printWorkOrder/" + flowEntry.getProcessDefinitionKey() + ); + permData.put("permList", permList); + permDataList.add(permData); + } + return permDataList; + } + +// @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) +// public void bulkHandleBusinessData(TransactionalFlowBusinessData businessData) { +// try { +// this.doHandle(businessData); +// // 这里会切换回原有的在线表单表所在的数据库上线文。 +// flowTransProducerMapper.deleteById(businessData.getTransId()); +// } catch (Exception e) { +// log.error("Failed to commit online business data [** " + JSON.toJSONString(businessData) + " **]", e); +// FlowTransProducer transProducer = flowTransProducerMapper.selectById(businessData.getTransId()); +// transProducer.setErrorReason(e.getMessage()); +// flowTransProducerMapper.updateById(transProducer); +// businessData.setErrorReason(e.getMessage()); +// throw new OnlineRuntimeException(e.getMessage()); +// } +// } + + private void doHandle(TransactionalFlowBusinessData businessData) throws Exception { + Connection conn = null; + try { + conn = dataSourceUtil.getConnection(businessData.getDblinkId()); + conn.setAutoCommit(false); + for (TransactionalBusinessData.BusinessSqlData s : businessData.getSqlDataList()) { + List paramList = null; + if (CollUtil.isNotEmpty(s.getColumnValueList())) { + paramList = s.getColumnValueList().stream().map(Object.class::cast).collect(Collectors.toList()); + } + dataSourceUtil.execute(conn, s.getSql(), paramList); + } + this.insertFlowConsumerTrans(conn, businessData); + conn.commit(); + } catch (Exception e) { + if (conn != null) { + conn.rollback(); + } + log.error(e.getMessage(), e); + throw new OnlineRuntimeException(e.getMessage()); + } finally { + if (conn != null) { + conn.close(); + } + } + } + + private void insertFlowConsumerTrans(Connection conn, TransactionalFlowBusinessData businessData) { + DataSourceProvider provider = dataSourceUtil.getProvider(businessData.getDblinkId()); + Long transId = businessData.getTransId(); + String sql = "INSERT INTO zz_flow_trans_consumer VALUES(?,?)"; + if (provider instanceof PostgreSqlProvider) { + dataSourceUtil.execute(conn, sql, CollUtil.newArrayList(transId, new java.sql.Date(System.currentTimeMillis()))); + } else { + dataSourceUtil.execute(conn, sql, CollUtil.newArrayList(transId, new Date())); + } + } + + private void updateMasterData(OnlineTable masterTable, JSONObject masterData, String dataId) { + if (masterData == null) { + return; + } + // 如果存在主表数据,就执行主表数据的更新。 + Map originalMasterData = + onlineOperationService.getMasterData(masterTable, null, null, dataId); + for (Map.Entry entry : originalMasterData.entrySet()) { + masterData.putIfAbsent(entry.getKey(), entry.getValue()); + } + if (!onlineOperationService.update(masterTable, masterData)) { + throw new FlowOperationException("主表数据不存在!"); + } + } + + private Map> normailizeSlaveDataListMap( + Map> slaveDataListMap) { + if (slaveDataListMap == null || slaveDataListMap.size() == 0) { + return null; + } + Map> resultMap = new HashMap<>(slaveDataListMap.size()); + for (Map.Entry> entry : slaveDataListMap.entrySet()) { + resultMap.put(entry.getKey().getSlaveTable().getTableName(), entry.getValue()); + } + return resultMap; + } +} diff --git a/common/common-flow-online/src/main/resources/META-INF/spring.factories b/common/common-flow-online/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..ed4c0ac --- /dev/null +++ b/common/common-flow-online/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +apelet.common.flow.online.config.FlowOnlineAutoConfig \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml index 1f2d3c0..0036e23 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -13,6 +13,7 @@ common-flow + common-flow-online common-online common-tenant