feat(REQ-2924) - 测试新版日志,并提供外部查询日志的聚合接口

This commit is contained in:
wangli 2024-09-08 14:57:12 +08:00
parent f30d891f82
commit 89e33fb766
15 changed files with 379 additions and 119 deletions

View File

@ -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;

View File

@ -249,7 +249,7 @@ public interface ProcessInstanceApi {
* @return
*/
@Operation(summary = "获取指定流程的日志")
@PostMapping("/api/process/instance/log")
@PostMapping("/api/process/instance/logs")
@InvokeMode(SYNC)
CommonResponse<BpmnProcessInstanceLogVO> getProcessInstanceLog(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto);
CommonResponse<BpmnProcessInstanceLogVO> getProcessInstanceLogs(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto);
}

View File

@ -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<BpmnTaskInstanceLogVO> tasks;
private List<BpmnTaskInstanceLogVO> taskDetails;
/**
* 当前实例对应模型的全局兜底按钮配置
@ -117,6 +118,12 @@ public class BpmnProcessInstanceLogVO {
@ApiModelProperty(value = "当前实例对应模型的全局兜底按钮配置")
private BpmnButtonConf defaultButtonConf;
/**
* 指定人访问实例日志时计算其流程应该有权限操作的按钮
*/
@ApiModelProperty(value = "指定人访问实例日志时,计算其流程应该有权限操作的按钮", notes = "流程有权限,不代表待办消息中一定能看到按钮")
private List<BpmnButtonMetaInfo> currentUserButtons;
/**
* 是否支持批量审批
*/
@ -140,4 +147,7 @@ public class BpmnProcessInstanceLogVO {
*/
@ApiModelProperty(value = "工作台类型")
private WorkspaceType workspaceType;
@ApiModelProperty(value = "程序计算按钮使用,非对外使用", hidden = true)
private transient BpmnButtonConf calculatingButtonConf;
}

View File

@ -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<BpmnTaskDelegateAssigner> forecastAssignees;
@ApiModelProperty(value = "程序计算按钮使用,非对外使用", hidden = true)
private transient BpmnButtonConf buttonConf;
public boolean isVirtual() {
return StringUtils.isBlank(this.taskId);
}

View File

@ -101,7 +101,7 @@ public class CustomApproveTaskCmd extends AbstractCommand<Void> implements Seria
if (Objects.nonNull(operationDesc)) {
this.operationDesc = operationDesc;
} else {
this.operationDesc = "已通过";
this.operationDesc = "已通过";
}
}

View File

@ -69,7 +69,7 @@ public class CustomRejectionTaskCmd extends AbstractCommand<Void> implements Ser
if (Objects.nonNull(operationDesc)) {
this.operationDesc = operationDesc;
} else {
this.operationDesc = "已驳回";
this.operationDesc = "已驳回";
}
this.attachmentList = dto.getAttachmentList();
this.approver = dto.getApprover();

View File

@ -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 对象不一致

View File

@ -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<BpmnTaskInstanceLogVO> genericTaskLogVos(String processInstanceId, List<ExtAxProcessLog> logs, List<ProcessNodeDetailVO> forecasting) {
private void calcAuthorizedButtons(BpmnProcessInstanceLogVO logVO, BpmnTaskDelegateAssigner visitor) {
List<BpmnButtonMetaInfo> 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<BpmnTaskDelegateAssigner> 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<BpmnButtonMetaInfo> chooseButtons(BpmnProcessInstanceLogVO logVO, String buttonConfigName) {
List<BpmnButtonMetaInfo> 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<BpmnTaskInstanceLogVO> genericTaskLogVos(String processInstanceId,
List<ExtAxProcessLog> logs,
List<ProcessNodeDetailVO> forecasting,
Boolean encrypt) {
List<BpmnTaskInstanceLogVO> tasks = new ArrayList<>();
Map<String, List<Attachment>> 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<BpmnTaskInstanceLogVO> 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<ProcessNodeDetailVO> forecasting, List<BpmnTaskInstanceLogVO> 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<ExtAxProcessLog> logs, List<BpmnTaskInstanceLogVO> tasks, Map<String, List<Attachment>> attachmentByTaskMap) {
ListUtils.emptyIfNull(logs).forEach(e -> {
Optional<BpmnTaskInstanceLogVO> 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<AttachmentDTO> getAttachmentByType(Map<String, List<Attachment>> attachmentByTaskMap, String taskId, AttachmentTypeEnum type) {
@ -1144,15 +1300,4 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
.collect(Collectors.toList());
}
// List<AttachmentDTO> attachments = task.getAttachments();
// // 图片
// List<AttachmentDTO> imageList = attachments.stream().filter(attachment -> Objects.equals(AttachmentTypeEnum.image, attachment.getType())).collect(Collectors.toList());
// detail.setImageList(imageList);
// // 附件
// List<AttachmentDTO> 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()));
}

View File

@ -21,10 +21,6 @@
<redisson.version>3.25.0</redisson.version>
</properties>
<dependencies>
<!--<dependency>
<groupId>cn.axzo.workflow</groupId>
<artifactId>workflow-engine-spring-boot-starter</artifactId>
</dependency>-->
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-web-spring-boot-starter</artifactId>
@ -75,7 +71,6 @@
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.framework</groupId>
<artifactId>axzo-processor-spring-boot-starter</artifactId>
@ -100,14 +95,14 @@
<groupId>cn.axzo.maokai</groupId>
<artifactId>maokai-api</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>cn.axzo.tyr</groupId>-->
<!-- <artifactId>tyr-api</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.axzo.karma</groupId>
<artifactId>karma-api</artifactId>
</dependency>
<dependency>
<groupId>cn.axzo.oss</groupId>
<artifactId>oss-http-api</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibaba-dingtalk-service-sdk</artifactId>

View File

@ -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

View File

@ -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> T rpcProcessor(Supplier<CommonResponse<T>> supplier, String operationType, Object... param) {
return rpcProcessorMayThrow(supplier, operationType, commonResponse -> {
throw new ServiceException(commonResponse.getMsg());
}, param);
}
public static <T> T rpcApolloProcessor(Supplier<Result<T>> supplier, String operationType, Object... param) {
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
Result<T> 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> T rpcApiResultProcessor(Supplier<ApiResult<T>> supplier, String operationType, Object... param) {
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
ApiResult<T> 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 <T> List<T> rpcApiListResultProcessor(Supplier<ApiListResult<T>> supplier, String operationType, Object... param) {
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
ApiListResult<T> 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> T rpcProcessorMayThrow(Supplier<CommonResponse<T>> supplier, String operationType, Consumer<CommonResponse<T>> throwConsumer, Object... param) {
AssertUtil.notNull(throwConsumer, "自定义的异常处理不可为空");
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
CommonResponse<T> 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> R printLatency(Supplier<R> 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> T rpcProfileProcessor(Supplier<CommonResponse<T>> supplier, String operationType, Object... param) {
log.info(operationType + "-Param: " + JSONUtil.toJsonStr(param));
StopWatch stopWatch = new StopWatch(operationType);
stopWatch.start(operationType);
CommonResponse<T> 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();
}
}

View File

@ -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> T parseApiResult(Supplier<ApiResult<T>> supplier, String operatorDesc,
String extInfo, Object... param) {
StopWatch stopWatch = new StopWatch(operatorDesc);
log.info("{}-Param: {}", operatorDesc, JSONUtil.toJsonStr(param));
stopWatch.start();
ApiResult<T> 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> T parseApiResult(Supplier<ApiResult<T>> supplier, String operatorDesc,
String extInfo, Consumer<ParseConsumer<T>> 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<T> {
private String extInfo;
private ApiResult<T> result;
private Object[] params;
public ParseConsumer(String extInfo, ApiResult<T> 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<T> getResult() {
return result;
}
public void setResult(ApiResult<T> result) {
this.result = result;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
}
}

View File

@ -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<String> createProcessInstance(@Validated @RequestBody BpmnProcessInstanceCreateDTO dto) {
log.info("发起审核createProcessInstance===>>>参数:{}", JSONUtil.toJsonStr(dto));
long personId = Long.parseLong(Optional.ofNullable(dto.getInitiator().getPersonId()).orElse("-1"));
Map<Long, String> personMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personId(personId).build()),
Map<Long, String> 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<BpmnProcessInstanceLogVO> getProcessInstanceLog(@Validated @RequestBody BpmnProcessInstanceLogQueryDTO dto) {
public CommonResponse<BpmnProcessInstanceLogVO> 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<String> 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<String, String> 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<ApiSignUrlDownloadResponse> getSignPrivateUrl(List<String> signUrls) {
ApiSignUrlDownloadRequest request = new ApiSignUrlDownloadRequest();
request.setFileKeys(signUrls);
return RpcExternalUtil.rpcProcessor(() -> serverFileServiceApi.signUrlFetchDownload(request), "批量获取手写签私有访问地址", request);
}
}

View File

@ -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<Long, String> personMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personId(personId).build()),
Map<Long, String> 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<Long, String> personMap = parseApiResult(() -> flowSupportApi.listPersons(PersonProfileQueryReq.builder().personIds(personIds).build()),
Map<Long, String> 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 -> {

View File

@ -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<String, Object> 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);
/**
* 同意
*