diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java index 1f319c9c4..166b5ea57 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessActivityApi.java @@ -1,10 +1,8 @@ package cn.axzo.workflow.client.feign.bpmn; -import cn.axzo.workflow.client.config.CommonFeignConfiguration; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; import cn.azxo.framework.common.model.CommonResponse; import io.swagger.v3.oas.annotations.Operation; -import org.springframework.cloud.openfeign.FeignClient; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; diff --git a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java index 465d79c4f..aa14705d7 100644 --- a/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java +++ b/workflow-engine-api/src/main/java/cn/axzo/workflow/client/feign/bpmn/ProcessInstanceApi.java @@ -249,7 +249,7 @@ public interface ProcessInstanceApi { * @return */ @Operation(summary = "获取指定流程的日志") - @PostMapping("/api/process/instance/log") + @PostMapping("/api/process/instance/logs") @InvokeMode(SYNC) - CommonResponse getProcessInstanceLog(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto); + CommonResponse getProcessInstanceLogs(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto); } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java index 4d2ad7731..aa580364d 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/process/BpmnProcessInstanceLogVO.java @@ -3,6 +3,7 @@ package cn.axzo.workflow.common.model.response.bpmn.process; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.enums.WorkspaceType; import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceLogVO; import io.swagger.annotations.ApiModel; @@ -109,7 +110,7 @@ public class BpmnProcessInstanceLogVO { * 任务信息集合 */ @ApiModelProperty("任务信息集合") - private List tasks; + private List taskDetails; /** * 当前实例对应模型的全局兜底按钮配置 @@ -117,6 +118,12 @@ public class BpmnProcessInstanceLogVO { @ApiModelProperty(value = "当前实例对应模型的全局兜底按钮配置") private BpmnButtonConf defaultButtonConf; + /** + * 指定人访问实例日志时,计算其流程应该有权限操作的按钮 + */ + @ApiModelProperty(value = "指定人访问实例日志时,计算其流程应该有权限操作的按钮", notes = "流程有权限,不代表待办消息中一定能看到按钮") + private List currentUserButtons; + /** * 是否支持批量审批 */ @@ -140,4 +147,7 @@ public class BpmnProcessInstanceLogVO { */ @ApiModelProperty(value = "工作台类型") private WorkspaceType workspaceType; + + @ApiModelProperty(value = "程序计算按钮使用,非对外使用", hidden = true) + private transient BpmnButtonConf calculatingButtonConf; } diff --git a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java index 32172360a..2c191b8d4 100644 --- a/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java +++ b/workflow-engine-common/src/main/java/cn/axzo/workflow/common/model/response/bpmn/task/BpmnTaskInstanceLogVO.java @@ -4,6 +4,7 @@ import cn.axzo.workflow.common.enums.ApprovalMethodEnum; import cn.axzo.workflow.common.enums.BpmnFlowNodeMode; import cn.axzo.workflow.common.enums.BpmnFlowNodeType; import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; import cn.axzo.workflow.common.model.request.bpmn.task.AttachmentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner; import io.swagger.annotations.ApiModel; @@ -117,6 +118,9 @@ public class BpmnTaskInstanceLogVO { @ApiModelProperty(value = "未完成节点多实例模式的审批人信息") private List forecastAssignees; + @ApiModelProperty(value = "程序计算按钮使用,非对外使用", hidden = true) + private transient BpmnButtonConf buttonConf; + public boolean isVirtual() { return StringUtils.isBlank(this.taskId); } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java index 4e4720113..fe3aca1e0 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomApproveTaskCmd.java @@ -101,7 +101,7 @@ public class CustomApproveTaskCmd extends AbstractCommand implements Seria if (Objects.nonNull(operationDesc)) { this.operationDesc = operationDesc; } else { - this.operationDesc = "已通过"; + this.operationDesc = "(已通过)"; } } diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java index 2ee944c96..9dc1225a3 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/cmd/CustomRejectionTaskCmd.java @@ -69,7 +69,7 @@ public class CustomRejectionTaskCmd extends AbstractCommand implements Ser if (Objects.nonNull(operationDesc)) { this.operationDesc = operationDesc; } else { - this.operationDesc = "已驳回"; + this.operationDesc = "(已驳回)"; } this.attachmentList = dto.getAttachmentList(); this.approver = dto.getApprover(); diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java index 886585c98..f6f7b4259 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/engine/tx/listener/AutoPassTransactionListener.java @@ -59,7 +59,7 @@ public class AutoPassTransactionListener implements TransactionListener { pass.setTaskId(delegateTask.getId()); pass.setAdvice(advice); pass.setApprover(assigner); - pass.setOperationDesc("自动通过"); + pass.setOperationDesc("(自动通过)"); String jobId = commandExecutor.execute(commandConfig, new CustomApproveTaskAsyncCmd(pass)); // 重置任务,因为上面的 cmd 和这个 cmd 的 lock 对象不一致 diff --git a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java index 46fa043d0..98be35bf1 100644 --- a/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java +++ b/workflow-engine-core/src/main/java/cn/axzo/workflow/core/service/impl/BpmnProcessInstanceServiceImpl.java @@ -9,6 +9,7 @@ import cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum; import cn.axzo.workflow.common.enums.WorkspaceType; import cn.axzo.workflow.common.model.request.BpmnApproveConf; import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf; +import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; @@ -123,6 +124,10 @@ import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_S import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION; import static cn.axzo.workflow.common.constant.BpmnConstants.BIZ_ORG_RELATION; import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CURRENT; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY; +import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR; import static cn.axzo.workflow.common.constant.BpmnConstants.CREATE_INSTANCE_PARAMS; import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121; import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR; @@ -139,8 +144,14 @@ import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.AND; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.EXCEPTIONAL; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.GENERAL; import static cn.axzo.workflow.common.enums.BpmnFlowNodeMode.OR; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_CARBON_COPY; import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER; +import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_TASK; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.APPROVED; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.DELETED; import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.PROCESSING; +import static cn.axzo.workflow.common.enums.BpmnProcessInstanceResultEnum.REJECTED; import static cn.axzo.workflow.common.enums.WorkspaceType.GOVERNMENT; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_ID_NOT_EXISTS; import static cn.axzo.workflow.core.common.code.BpmnInstanceRespCode.PROCESS_INSTANCE_NOT_EXISTS; @@ -1050,7 +1061,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic .orElse(variables.getOrDefault(OLD_INTERNAL_INITIATOR, null)))) .tenantId(historicProcessInstance.getTenantId()) .agented((Boolean) Optional.ofNullable(variables.get(INTERNAL_PROCESS_AGENT)).orElse(false)) - .tasks(genericTaskLogVos(historicProcessInstance.getId(), logs, forecasting)) + .taskDetails(genericTaskLogVos(historicProcessInstance.getId(), logs, forecasting, dto.getEncrypt())) .defaultButtonConf(getButtonConfig(bpmnModel.getMainProcess()).orElse(new BpmnButtonConf())) .supportBatchOperation(getProcessApproveConf(bpmnModel.getMainProcess()).orElse(new BpmnApproveConf()).getSupportBatchOperation()) .userAgreeSignature(getProcessApproveConf(bpmnModel.getMainProcess()).orElse(new BpmnApproveConf()).getUserAgreeSignature()) @@ -1062,15 +1073,183 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic logVO.setWorkspaceType(WorkspaceType.getType(Integer.valueOf(category.getWorkspaceTypeCode()))); logVO.setCategory(category.getValue()); }); + + // 根据传入的访问人计算有权限的按钮 + calcAuthorizedButtons(logVO, dto.getVisitor()); return logVO; } - private List genericTaskLogVos(String processInstanceId, List logs, List forecasting) { + private void calcAuthorizedButtons(BpmnProcessInstanceLogVO logVO, BpmnTaskDelegateAssigner visitor) { + List authorizedButtons = new ArrayList<>(); + if (Objects.nonNull(logVO.getDefaultButtonConf()) + && CollectionUtils.isEmpty(logVO.getDefaultButtonConf().getCarbonCopy())) { + authorizedButtons.addAll(logVO.getDefaultButtonConf().getCarbonCopy()); + } + + if (Objects.equals(PROCESSING, logVO.getResult())) { + String ge130Assignee = getGe130Assignee(visitor); + String le130Assignee = getLe130Assignee(visitor); + + // 运行到的当前节点的按钮配置 + logVO.getTaskDetails().stream() + .filter(i -> Objects.equals(PROCESSING, i.getResult())) + .findFirst() + .ifPresent(i -> logVO.setCalculatingButtonConf(i.getButtonConf())); + + + // 比对发起人 + if (Objects.nonNull(logVO.getInitiator()) && + (Objects.equals(logVO.getInitiator().buildAssigneeId_1_2_1(), le130Assignee) + || logVO.getInitiator().buildAssigneeId().contains(ge130Assignee))) { + authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_INITIATOR)); + } + + + // 比对当前审批人 + logVO.getTaskDetails().stream().filter(i -> Objects.equals(PROCESSING, i.getResult()) + || (Objects.equals(DELETED, i.getResult()) && Objects.isNull(i.getEndTime()))) + .findFirst() + .map(i -> { + List list = new ArrayList<>(); + if (Objects.nonNull(i.getAssigneeSnapshot())) { + list.add(i.getAssigneeSnapshot()); + } + if (!CollectionUtils.isEmpty(i.getForecastAssignees())) { + list.addAll(i.getForecastAssignees()); + } + return list; + }) + .orElse(Collections.emptyList()) + .stream() + .filter(Objects::nonNull) + .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee)) + .findAny() + .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_CURRENT))); + + + // 比对历史审批人 + logVO.getTaskDetails().stream() + .filter(i -> Objects.equals(i.getNodeType(), NODE_TASK) || Objects.equals(i.getNodeType(), NODE_BUSINESS)) + .filter(i -> !Objects.equals(PROCESSING, i.getResult())) + .map(BpmnTaskInstanceLogVO::getAssigneeSnapshot) + .filter(Objects::nonNull) + .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee)) + .findAny() + .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_HISTORY))); + + // 比对抄送人 + logVO.getTaskDetails().stream() + .filter(i -> Objects.equals(i.getNodeType(), NODE_CARBON_COPY)) + .flatMap(i -> i.getForecastAssignees().stream()) + .filter(i -> i.buildAssigneeId().contains(ge130Assignee) || Objects.equals(i.buildAssigneeId_1_2_1(), le130Assignee)) + .findAny() + .ifPresent(i -> authorizedButtons.addAll(chooseButtons(logVO, CONFIG_BUTTON_TYPE_CARBON_COPY))); + } + logVO.setCurrentUserButtons(authorizedButtons); + } + + /** + * 按钮的通用处理, 有限使用节点的按钮配置,如果没有则按兜底按钮配置 + * + * @param logVO 该对象中的 calcButtonConf 字段为当前节点的按钮配置 + * @param buttonConfigName String CONFIG_BUTTON_TYPE_INITIATOR = "initiator"; + * String CONFIG_BUTTON_TYPE_CURRENT = "current"; + * String CONFIG_BUTTON_TYPE_HISTORY = "history"; + * String CONFIG_BUTTON_TYPE_CARBON_COPY = "carbonCopy"; + * @return + */ + private List chooseButtons(BpmnProcessInstanceLogVO logVO, String buttonConfigName) { + List mergeButtons = new ArrayList<>(); + if (Objects.isNull(logVO.getCalculatingButtonConf())) { + BpmnButtonConf defaultButtonConf = logVO.getDefaultButtonConf(); + if (Objects.isNull(defaultButtonConf)) { + return mergeButtons; + } + logVO.setCalculatingButtonConf(defaultButtonConf); + } + switch (buttonConfigName) { + case CONFIG_BUTTON_TYPE_INITIATOR: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getInitiator()); + break; + case CONFIG_BUTTON_TYPE_CURRENT: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getCurrent()); + break; + case CONFIG_BUTTON_TYPE_HISTORY: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getHistory()); + break; + case CONFIG_BUTTON_TYPE_CARBON_COPY: + mergeButtons.addAll(logVO.getCalculatingButtonConf().getCarbonCopy()); + break; + default: + break; + } + return mergeButtons; + } + + public static String getLe130Assignee(BpmnTaskDelegateAssigner visitor) { + return visitor.getTenantId() + "|" + visitor.getAssignee() + "|" + visitor.getAssigneeType(); + } + + public static String getGe130Assignee(BpmnTaskDelegateAssigner visitor) { + // String ge130Assignee = contextInfo.getOuId() + "|" + contextInfo.getUserInfo().getPersonId(); + // 130版本以上,产品要求仅校验 personId + return "|" + visitor.getPersonId(); + } + + private List genericTaskLogVos(String processInstanceId, + List logs, + List forecasting, + Boolean encrypt) { List tasks = new ArrayList<>(); Map> attachmentByTaskMap = taskService.getProcessInstanceAttachments(processInstanceId).stream() .collect(Collectors.groupingBy(Attachment::getTaskId)); // 已完成的和进行中的 + getHistoricTasks(logs, tasks, attachmentByTaskMap); + // 未来节点 + getFutureTasks(forecasting, tasks); + // 处理是否加密 + handleEncrypt(encrypt, tasks); + return tasks; + } + + private static void handleEncrypt(Boolean encrypt, List tasks) { + if (!encrypt) { + return; + } + tasks.forEach(i -> { + if (Objects.equals(NODE_STARTER.getType(), i.getTaskDefinitionKey())) { + i.setOperationDesc(i.getAssigneeSnapshot().getAssignerName()); + } else if (Objects.equals(i.getResult(), APPROVED)) { + i.setOperationDesc(APPROVED.getDesc()); + } else if (Objects.equals(i.getResult(), REJECTED)) { + i.setOperationDesc(REJECTED.getDesc()); + } else if (Objects.equals(i.getResult(), PROCESSING) || Objects.isNull(i.getTaskId())) { + i.setOperationDesc("待处理"); + } else { + i.setOperationDesc("已处理"); + } + // 统一将多人节点数据全部置空 + i.setForecastAssignees(Collections.emptyList()); + // 统一将签名数据置空 + i.setSignatureUrl(null); + }); + } + + private static void getFutureTasks(List forecasting, List tasks) { + ListUtils.emptyIfNull(forecasting).forEach(e -> { + tasks.add(BpmnTaskInstanceLogVO.builder() + .taskDefinitionKey(e.getId()) + .name(e.getName()) + .approvalMethod(e.getApprovalMethod()) + .nodeType(e.getNodeType()) + .nodeMode(e.getNodeMode()) + .forecastAssignees(e.getForecastAssigners()) + .build()); + }); + } + + private void getHistoricTasks(List logs, List tasks, Map> attachmentByTaskMap) { ListUtils.emptyIfNull(logs).forEach(e -> { Optional processingTask = tasks.stream().filter(i -> Objects.equals(PROCESSING, i.getResult())) .filter(i -> Objects.equals(i.getTaskDefinitionKey(), e.getActivityId())).findAny(); @@ -1104,31 +1283,8 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic .forecastAssignees(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? e.getAssigneeFull() : Collections.emptyList()) .build()); } + }); - }); - // 未来节点 - ListUtils.emptyIfNull(forecasting).forEach(e -> { - tasks.add(BpmnTaskInstanceLogVO.builder() -// .taskId(e.getTaskId()) - .taskDefinitionKey(e.getId()) - .name(e.getName()) -// .createTime(e.getStartTime()) -// .endTime(e.getEndTime()) - .approvalMethod(e.getApprovalMethod()) - .nodeType(e.getNodeType()) - .nodeMode(e.getNodeMode()) -// .result(BpmnProcessInstanceResultEnum.valueOfStatus(e.getStatus())) -// .operationDesc(e.getOperationDesc()) -// .advice(e.getAdvice()) -// .commentExt("") -// .imageList(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.image)) -// .fileList(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.file)) -// .signatureUrl(getAttachmentByType(attachmentByTaskMap, e.getTaskId(), AttachmentTypeEnum.signature).stream().findFirst().orElse(new AttachmentDTO()).getUrl()) -// .assigneeSnapshot(Objects.equals(e.getNodeType(), BpmnFlowNodeType.NODE_CARBON_COPY.getType()) ? null : BpmnTaskDelegateAssigner.toObjectCompatible(e.getAssigneeFull().get(0))) - .forecastAssignees(e.getForecastAssigners()) - .build()); - }); - return tasks; } public List getAttachmentByType(Map> attachmentByTaskMap, String taskId, AttachmentTypeEnum type) { @@ -1144,15 +1300,4 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic .collect(Collectors.toList()); } -// List attachments = task.getAttachments(); -// // 图片 -// List imageList = attachments.stream().filter(attachment -> Objects.equals(AttachmentTypeEnum.image, attachment.getType())).collect(Collectors.toList()); -// detail.setImageList(imageList); -// // 附件 -// List fileList = attachments.stream().filter(attachment -> Objects.equals(AttachmentTypeEnum.file, attachment.getType())).collect(Collectors.toList()); -// detail.setFileList(fileList); -// // 手写签名 -// attachments.stream().filter(attachment -> Objects.equals(AttachmentTypeEnum.signature, attachment.getType())).findAny().ifPresent(i -> detail.setSignatureUrl(i.getUrl())); - - } diff --git a/workflow-engine-server/pom.xml b/workflow-engine-server/pom.xml index afb7ae83a..8b5c81251 100644 --- a/workflow-engine-server/pom.xml +++ b/workflow-engine-server/pom.xml @@ -21,10 +21,6 @@ 3.25.0 - cn.axzo.framework axzo-web-spring-boot-starter @@ -75,7 +71,6 @@ mysql mysql-connector-java - cn.axzo.framework axzo-processor-spring-boot-starter @@ -100,14 +95,14 @@ cn.axzo.maokai maokai-api - - - - cn.axzo.karma karma-api + + cn.axzo.oss + oss-http-api + com.aliyun alibaba-dingtalk-service-sdk diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java index 697702527..1e1d7729a 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/WorkflowEnginApplication.java @@ -1,17 +1,18 @@ package cn.axzo.workflow.server; -import liquibase.pro.packaged.E; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; import org.springframework.transaction.annotation.EnableTransactionManagement; @MapperScan({"cn.axzo.workflow.core.**.mapper"}) -@ComponentScan({"cn.axzo.workflow", "cn.axzo.maokai"}) +@ComponentScan({"cn.axzo.workflow"}) +@EnableFeignClients("cn.axzo.oss") @SpringBootApplication(exclude = RabbitAutoConfiguration.class) @EnableTransactionManagement @EnableCaching diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RpcExternalUtil.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RpcExternalUtil.java new file mode 100644 index 000000000..36a99bede --- /dev/null +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/common/util/RpcExternalUtil.java @@ -0,0 +1,114 @@ +package cn.axzo.workflow.server.common.util; + +import cn.axzo.apollo.core.web.Result; +import cn.axzo.basics.common.util.AssertUtil; +import cn.axzo.framework.domain.ServiceException; +import cn.axzo.framework.domain.web.result.ApiListResult; +import cn.axzo.framework.domain.web.result.ApiResult; +import cn.azxo.framework.common.model.CommonResponse; +import cn.hutool.core.date.StopWatch; +import cn.hutool.core.lang.Assert; +import cn.hutool.http.HttpStatus; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * 外部rpc接口 返回 CommonResponse + * + * @author tanjie@axzo.cn + * @date 2022/5/23 11:08 + */ +@Slf4j +public class RpcExternalUtil { + + /** + * 常用的RPC请求返回值解析,如果 被请求方 返回非200会抛出异常 + */ + public static T rpcProcessor(Supplier> supplier, String operationType, Object... param) { + + return rpcProcessorMayThrow(supplier, operationType, commonResponse -> { + throw new ServiceException(commonResponse.getMsg()); + }, param); + } + + public static T rpcApolloProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + Result result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + Assert.notNull(result, "服务调用异常"); + Assert.isTrue(result.getCode() == 200, "服务调用异常:" + result.getMsg()); + return result.getData(); + } + + public static T rpcApiResultProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + ApiResult result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + Assert.notNull(result, "服务调用异常"); + Assert.isTrue(result.getCode() == 200, "服务调用异常:" + result.getMsg()); + return result.getData(); + } + + /** + * 常用的RPC请求返回值解析,如果 被请求方 返回非200会抛出异常 + * + * @param supplier ApiListResult类型的RPC接口 + * @param operationType 接口方法描述 + * @param param 接口入参 + * @return 接口返回值 + */ + public static List rpcApiListResultProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + ApiListResult result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + Assert.notNull(result, "服务调用异常"); + Assert.isTrue(result.isSuccess(), "服务调用异常:" + result.getMsg()); + return result.getData(); + } + + public static T rpcProcessorMayThrow(Supplier> supplier, String operationType, Consumer> throwConsumer, Object... param) { + AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空"); + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + CommonResponse result = printLatency(supplier, operationType); + log.info(operationType + "-Result: " + JSONUtil.toJsonStr(result)); + + Assert.notNull(result, "服务调用异常"); + // 200自定义处理 + if (HttpStatus.HTTP_OK != result.getCode()) { + throwConsumer.accept(result); + } + return result.getData(); + } + + public static R printLatency(Supplier function, String optType) { + StopWatch stopWatch = new StopWatch(optType); + stopWatch.start(optType); + R r = function.get(); + stopWatch.stop(); + log.info(stopWatch.shortSummary(TimeUnit.MILLISECONDS)); + return r; + } + + + public static T rpcProfileProcessor(Supplier> supplier, String operationType, Object... param) { + log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param)); + StopWatch stopWatch = new StopWatch(operationType); + stopWatch.start(operationType); + CommonResponse result = supplier.get(); + stopWatch.stop(); + log.info("-Result: " + JSONUtil.toJsonStr(result)); + log.info(stopWatch.shortSummary(TimeUnit.MILLISECONDS)); + Assert.notNull(result, "服务调用异常"); + // 200自定义处理 + if (HttpStatus.HTTP_OK != result.getCode() && 404 != result.getCode()) { + throw new ServiceException(result.getMsg()); + } + + return result.getData(); + } +} diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java index 38833c963..af15e04bc 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/delegate/AbstractBpmnTaskAssigneeSelector.java @@ -38,7 +38,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -116,27 +115,9 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign return Collections.emptyList(); } - public static T parseApiResult(Supplier> supplier, String operatorDesc, - String extInfo, Object... param) { - StopWatch stopWatch = new StopWatch(operatorDesc); - log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param)); - stopWatch.start(); - ApiResult result = supplier.get(); - stopWatch.stop(); - log.info("{}-Cost:{}, Result: {}", operatorDesc, - "API StopWatch '" + stopWatch.getId() + "': running time = " + stopWatch.getTotalTimeSeconds() + " 's", - JSONUtil.toJsonStr(result)); - Assert.notNull(result, "服务调用异常"); - // 200自定义处理 - if (HttpStatus.HTTP_OK != result.getCode()) { - throw new WorkflowEngineException(CALC_TASK_ASSIGNEE_ERROR, "[API:" + extInfo + "]" + result.getMsg()); - } - return result.getData(); - } - protected final T parseApiResult(Supplier> supplier, String operatorDesc, - String extInfo, Consumer> consumer, Object... param) { + String extInfo, Object... param) { StopWatch stopWatch = new StopWatch(operatorDesc); log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param)); stopWatch.start(); @@ -229,39 +210,4 @@ public abstract class AbstractBpmnTaskAssigneeSelector implements BpmnTaskAssign executionStartListener = applicationContext.getBean(EngineExecutionStartListener.class); } - static class ParseConsumer { - private String extInfo; - private ApiResult result; - private Object[] params; - - public ParseConsumer(String extInfo, ApiResult result, Object[] params) { - this.extInfo = extInfo; - this.result = result; - this.params = params; - } - - public String getExtInfo() { - return extInfo; - } - - public void setExtInfo(String extInfo) { - this.extInfo = extInfo; - } - - public ApiResult getResult() { - return result; - } - - public void setResult(ApiResult result) { - this.result = result; - } - - public Object[] getParams() { - return params; - } - - public void setParams(Object[] params) { - this.params = params; - } - } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java index 2da69ca63..6f50246cb 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessInstanceController.java @@ -3,6 +3,9 @@ package cn.axzo.workflow.server.controller.web.bpmn; import cn.axzo.karma.client.feign.FlowSupportApi; import cn.axzo.karma.client.model.request.PersonProfileQueryReq; import cn.axzo.karma.client.model.response.PersonProfileResp; +import cn.axzo.oss.http.api.ServerFileServiceApi; +import cn.axzo.oss.http.model.ApiSignUrlDownloadRequest; +import cn.axzo.oss.http.model.ApiSignUrlDownloadResponse; import cn.axzo.workflow.client.feign.bpmn.ProcessInstanceApi; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAdminPageReqVO; @@ -23,15 +26,20 @@ import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstancePa import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.HistoricProcessInstanceVO; import cn.axzo.workflow.common.model.response.bpmn.process.ProcessNodeDetailVO; +import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskInstanceLogVO; import cn.axzo.workflow.core.service.BpmnProcessInstanceService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; +import cn.axzo.workflow.server.common.util.RpcExternalUtil; import cn.azxo.framework.common.model.CommonResponse; import cn.hutool.json.JSONUtil; import com.fasterxml.jackson.databind.node.ObjectNode; import io.swagger.v3.oas.annotations.Operation; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.ListUtils; import org.flowable.engine.history.HistoricProcessInstance; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -48,10 +56,10 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import static cn.axzo.workflow.server.controller.delegate.AbstractBpmnTaskAssigneeSelector.parseApiResult; import static cn.azxo.framework.common.model.CommonResponse.success; /** @@ -68,6 +76,8 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { private BpmnProcessInstanceService bpmnProcessInstanceService; @Resource private FlowSupportApi flowSupportApi; + @Resource + private ServerFileServiceApi serverFileServiceApi; /** * 超管查询所有流程实例 @@ -104,7 +114,7 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { public CommonResponse createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateDTO dto) { log.info("发起审核createProcessInstance===>>>参数:{}", JSONUtil.toJsonStr(dto)); long personId = Long.parseLong(Optional.ofNullable(dto.getInitiator().getPersonId()).orElse("-1")); - Map personMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personId(personId).build()), + Map personMap = RpcExternalUtil.rpcApiResultProcessor(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personId(personId).build()), "查询档案信息", "cn.axzo.karma.client.feign.FlowSupportApi.listPersons", personId).stream() .collect(Collectors.toMap(PersonProfileResp::getId, PersonProfileResp::getAvatarUrl)); if (personMap.containsKey(personId)) { @@ -335,10 +345,34 @@ public class BpmnProcessInstanceController implements ProcessInstanceApi { * @return */ @Operation(summary = "获取指定流程实例的日志") - @PostMapping("/log") + @PostMapping("/logs") @Override - public CommonResponse getProcessInstanceLog(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto) { + public CommonResponse getProcessInstanceLogs(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto) { log.info("获取指定流程实例的日志 getProcessInstanceLog===>>>参数:{}", JSONUtil.toJsonStr(dto)); - return success(bpmnProcessInstanceService.getProcessInstanceLog(dto)); + BpmnProcessInstanceLogVO log = bpmnProcessInstanceService.getProcessInstanceLog(dto); + parseSignatureUrl(log); + return success(log); + } + + private void parseSignatureUrl(BpmnProcessInstanceLogVO log) { + List signUrls = log.getTaskDetails().stream() + .filter(i -> StringUtils.hasText(i.getTaskId())) + .map(BpmnTaskInstanceLogVO::getSignatureUrl) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(signUrls)) { + Map ossUrlMap = ListUtils.emptyIfNull(getSignPrivateUrl(signUrls)).stream() + .collect(Collectors.toMap(ApiSignUrlDownloadResponse::getFileKey, ApiSignUrlDownloadResponse::getSignUrl, (s, t) -> s)); + log.getTaskDetails().stream() + .filter(i -> StringUtils.hasText(i.getTaskId())) + .forEach(i -> i.setSignatureUrl(ossUrlMap.getOrDefault(i.getSignatureUrl(), null))); + } + } + + private List getSignPrivateUrl(List signUrls) { + ApiSignUrlDownloadRequest request = new ApiSignUrlDownloadRequest(); + request.setFileKeys(signUrls); + return RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "批量获取手写签私有访问地址", request); } } diff --git a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java index d42ac86f1..257aaeb53 100644 --- a/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java +++ b/workflow-engine-server/src/main/java/cn/axzo/workflow/server/controller/web/bpmn/BpmnProcessTaskController.java @@ -28,6 +28,7 @@ import cn.axzo.workflow.common.model.response.bpmn.task.BpmnTaskTodoPageItemVO; import cn.axzo.workflow.core.service.BpmnProcessTaskService; import cn.axzo.workflow.server.common.annotation.ErrorReporter; import cn.axzo.workflow.server.common.annotation.RepeatSubmit; +import cn.axzo.workflow.server.common.util.RpcExternalUtil; import cn.azxo.framework.common.model.CommonResponse; import com.alibaba.fastjson.JSON; import io.swagger.v3.oas.annotations.Operation; @@ -53,7 +54,6 @@ import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; -import static cn.axzo.workflow.server.controller.delegate.AbstractBpmnTaskAssigneeSelector.parseApiResult; import static cn.azxo.framework.common.model.CommonResponse.success; /** @@ -191,7 +191,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { log.info("转交任务 transferTask===>>>参数:{}", JSON.toJSONString(dto)); long personId = Long.parseLong(Optional.ofNullable(dto.getTargetAssigner().getPersonId()).orElse("-1")); - Map personMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personId(personId).build()), + Map personMap = RpcExternalUtil.rpcApiResultProcessor(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personId(personId).build()), "查询档案信息", "cn.axzo.karma.client.feign.FlowSupportApi.listPersons", personId).stream() .collect(Collectors.toMap(PersonProfileResp::getId, PersonProfileResp::getAvatarUrl)); if (personMap.containsKey(personId)) { @@ -239,7 +239,7 @@ public class BpmnProcessTaskController implements ProcessTaskApi { .map(BpmnTaskDelegateAssigner::getPersonId) .map(Long::parseLong) .distinct().collect(Collectors.toList()); - Map personMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personIds(personIds).build()), + Map personMap = RpcExternalUtil.rpcApiResultProcessor(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personIds(personIds).build()), "查询档案信息", "cn.axzo.karma.client.feign.FlowSupportApi.listPersons", personIds).stream() .collect(Collectors.toMap(PersonProfileResp::getId, PersonProfileResp::getAvatarUrl)); countersignDTO.getTargetAssignerList().forEach(e -> { diff --git a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java index b9074162b..b82850ce8 100644 --- a/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java +++ b/workflow-engine-spring-boot-starter/src/main/java/cn/axzo/workflow/starter/api/WorkflowCoreService.java @@ -5,6 +5,7 @@ import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbo import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCancelDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCarbonCopyDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceCreateDTO; +import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceLogQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceQueryDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnOptionalNodeDTO; @@ -16,6 +17,7 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCommentDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskCountersignDTO; import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskTransferDTO; import cn.axzo.workflow.common.model.response.bpmn.BatchOperationResultVO; +import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceLogVO; import cn.axzo.workflow.common.model.response.bpmn.process.BpmnProcessInstanceVO; import cn.axzo.workflow.common.util.ThreadUtil; import cn.axzo.workflow.starter.feign.ext.WorkflowEngineStarterFeignConfiguration; @@ -150,6 +152,17 @@ public interface WorkflowCoreService { @InvokeMode(SYNC) Map getProcessVariables(@NotBlank(message = "流程实例 ID 不能为空") @RequestParam String processInstanceId, @Nullable @RequestParam(required = false) String tenantId); + /** + * 获取指定流程的日志 + * + * @param dto + * @return + */ + @Operation(summary = "获取指定流程的日志") + @PostMapping("/api/process/instance/logs") + @InvokeMode(SYNC) + BpmnProcessInstanceLogVO getProcessInstanceLogs(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto); + /** * 同意 *