Merge branch 'feature/REQ-5865'

This commit is contained in:
wangli 2025-12-10 11:21:04 +08:00
commit 6d597ca377
11 changed files with 101 additions and 46 deletions

View File

@ -120,7 +120,7 @@ public interface ProcessTaskApi {
*/
@Operation(summary = "系统操作回退任务到指定节点")
@PostMapping("/api/process/task/system/back")
@Manageable
@InvokeMode(ASYNC)
CommonResponse<Boolean> systemBackTask(@Validated @RequestBody BpmnNodeBackSystemOperateDTO dto);
/**

View File

@ -47,6 +47,13 @@ public class BpmnProcessInstanceLogQueryDTO {
@Builder.Default
private Boolean hasButton = false;
/**
* 是否包含未来的节点默认包含
*/
@ApiModelProperty(value = "是否包含未来的节点,默认包含")
@Builder.Default
private Boolean includeFutureTasks = true;
/**
* 是否需要加密同一个实例的日志在不同端[cms/oms]审批人的信息需要按一定规则进行隐藏控制
*/

View File

@ -5,16 +5,17 @@ import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO;
import cn.axzo.workflow.core.engine.job.AsyncActivityTriggerJobHandler;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.job.service.JobService;
import org.flowable.job.service.impl.persistence.entity.JobEntity;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.util.Objects;
@ -47,10 +48,12 @@ public class CustomActivityTriggerAsyncCmd extends AbstractCommand<String> imple
public String execute(CommandContext commandContext) {
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
TaskEntity task = (TaskEntity) processEngineConfiguration.getTaskService().createTaskQuery()
.executionId(dto.getTriggerId())
.taskDefinitionKey(StringUtils.isBlank(dto.getActivityId()) ? null : dto.getActivityId())
.singleResult();
TaskQuery taskQuery = processEngineConfiguration.getTaskService().createTaskQuery()
.executionId(dto.getTriggerId());
if (StringUtils.hasText(dto.getActivityId())) {
taskQuery.taskDefinitionKey(dto.getActivityId());
}
TaskEntity task = (TaskEntity) taskQuery.singleResult();
if (Objects.isNull(task)) {
throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, dto.getTriggerId());
}
@ -59,7 +62,6 @@ public class CustomActivityTriggerAsyncCmd extends AbstractCommand<String> imple
log.info("业务节点唤醒时发现节点已经修改配置无法继续唤醒processInstanceId:{}, taskDefinitionKey={}", task.getProcessInstanceId(), task.getTaskDefinitionKey());
return "";
}
return startAsync(commandContext);
}
@ -69,7 +71,7 @@ public class CustomActivityTriggerAsyncCmd extends AbstractCommand<String> imple
TaskService taskService = processEngineConfiguration.getTaskService();
TaskEntity task = (TaskEntity) taskService.createTaskQuery()
.executionId(dto.getTriggerId())
.taskDefinitionKey(StringUtils.isBlank(dto.getActivityId()) ? null : dto.getActivityId())
.taskDefinitionKey(StringUtils.hasText(dto.getActivityId()) ? dto.getActivityId() : null)
.singleResult();
JobService jobService = processEngineConfiguration.getJobServiceConfiguration().getJobService();

View File

@ -5,7 +5,6 @@ import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.common.engine.impl.interceptor.CommandContext;
@ -13,9 +12,11 @@ import org.flowable.engine.RuntimeService;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.service.impl.persistence.entity.TaskEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.io.Serializable;
import java.util.Objects;
@ -47,13 +48,16 @@ public class CustomActivityTriggerCmd extends AbstractCommand<Void> implements S
@Override
public Void execute(CommandContext commandContext) {
log.info("CustomActivityTriggerCmd execute start, dto: {}", JSON.toJSONString(dto));
ProcessEngineConfigurationImpl processEngineConfiguration =
CommandContextUtil.getProcessEngineConfiguration(commandContext);
TaskEntity task = (TaskEntity) processEngineConfiguration.getTaskService().createTaskQuery()
.executionId(dto.getTriggerId())
.taskDefinitionKey(StringUtils.isBlank(dto.getActivityId()) ? null : dto.getActivityId())
.singleResult();
TaskQuery taskQuery = processEngineConfiguration.getTaskService().createTaskQuery()
.executionId(dto.getTriggerId());
if (StringUtils.hasText(dto.getActivityId())) {
taskQuery.taskDefinitionKey(dto.getActivityId());
}
TaskEntity task = (TaskEntity) taskQuery.singleResult();
if (Objects.isNull(task)) {
throw new WorkflowEngineException(ACTIVITY_TRIGGER_NOT_EXISTS, dto.getTriggerId());
}
@ -63,9 +67,10 @@ public class CustomActivityTriggerCmd extends AbstractCommand<Void> implements S
return null;
}
addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, "已同意");
addComment(commandContext, task, COMMENT_TYPE_OPERATION_DESC, "已同意", true);
RuntimeService runtimeService = processEngineConfiguration.getRuntimeService();
runtimeService.trigger(dto.getTriggerId());
log.info("CustomActivityTriggerCmd triggerAsync");
runtimeService.triggerAsync(dto.getTriggerId());
return null;
}

View File

@ -40,9 +40,9 @@ public class CustomCommandContext extends CommandContext {
} else if (exception instanceof FlowableException && ((FlowableException) exception).isReduceLogLevel()) {
// reduce log level, because this may have been caused because of job deletion due to cancelActiviti="true"
LOGGER.info("Error while closing command context", exception);
LOGGER.info("Error while closing command context {}", exception.getMessage(), exception);
} else if (exception instanceof WorkflowEngineException) {
LOGGER.warn("Workflow error while closing command context", exception);
LOGGER.warn("Workflow error while closing command context {}", exception.getMessage(), exception);
} else if (exception instanceof PersistenceException) {
Throwable rootCause = getRootCause(exception);
if (rootCause instanceof MySQLTransactionRollbackException) {
@ -53,7 +53,7 @@ public class CustomCommandContext extends CommandContext {
LOGGER.warn("persistence error while closing command context:{}", exception.getMessage(), exception);
}
} else {
LOGGER.error("Error while closing command context", exception);
LOGGER.error("Error while closing command context: {}", exception.getMessage(), exception);
}
}

View File

@ -283,6 +283,10 @@ public class CustomTaskHelper {
}
public static void addComment(CommandContext commandContext, TaskEntity task, String type, String message) {
addComment(commandContext, task, type, message, false);
}
public static void addComment(CommandContext commandContext, TaskEntity task, String type, String message, boolean withVariableLocal) {
if (!StringUtils.hasText(type) || !StringUtils.hasText(message)) {
return;
}
@ -307,7 +311,11 @@ public class CustomTaskHelper {
comment.setFullMessage(message);
processEngineConfiguration.getCommentEntityManager().insert(comment);
task.setTransientVariableLocal(type, message);
if (withVariableLocal) {
task.setVariableLocal(type, message);
} else {
task.setTransientVariableLocal(type, message);
}
}
public static Attachment addAttachment(CommandContext commandContext, Task task, AttachmentDTO attachmentDto) {

View File

@ -1,11 +1,11 @@
package cn.axzo.workflow.core.service.impl;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO;
import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO;
import cn.axzo.workflow.common.model.request.bpmn.process.BpmnProcessInstanceAbortDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.engine.cmd.CustomAbortProcessInstanceCmd;
import cn.axzo.workflow.core.engine.cmd.CustomActivityTriggerAsyncCmd;
import cn.axzo.workflow.core.engine.cmd.CustomActivityTriggerCmd;
@ -47,6 +47,7 @@ public class BpmnProcessActivityServiceImpl implements BpmnProcessActivityServic
@Override
@Transactional(rollbackFor = Exception.class)
public void trigger(BpmnActivityTriggerDTO dto) {
log.info("processActivityService trigger called");
CommandExecutor commandExecutor = processEngineConfiguration.getCommandExecutor();
if (Boolean.TRUE.equals(dto.getAsync())) {
commandExecutor.execute(new CustomActivityTriggerAsyncCmd(dto));

View File

@ -1245,7 +1245,7 @@ public class BpmnProcessInstanceServiceImpl implements BpmnProcessInstanceServic
List<ProcessNodeDetailVO> forecasting = new ArrayList<>();
// 只有还在运行中的实例才需要推测后续节点
if (Objects.equals(historicProcessInstance.getBusinessStatus(), PROCESSING.getStatus())) {
if (Objects.equals(historicProcessInstance.getBusinessStatus(), PROCESSING.getStatus()) && Objects.equals(Boolean.TRUE, dto.getIncludeFutureTasks())) {
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
.processInstanceId(dto.getProcessInstanceId())
.includeProcessVariables()

View File

@ -1,12 +1,12 @@
package cn.axzo.workflow.server.controller.web.bpmn;
import cn.axzo.workflow.client.feign.bpmn.ProcessActivityApi;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutCallbackDTO;
import cn.axzo.workflow.common.model.request.bpmn.activity.BpmnActivityTimeoutTriggerDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivitySetAssigneeDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnActivityTriggerDTO;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.service.BpmnProcessActivityService;
import cn.axzo.workflow.server.common.annotation.ErrorReporter;
import cn.axzo.workflow.server.common.annotation.RepeatSubmit;
@ -24,10 +24,12 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotBlank;
import java.util.ArrayList;
import java.util.List;
import static cn.axzo.workflow.client.config.WorkflowRequestInterceptor.HEADER_SERVER_NAME;
import static cn.axzo.workflow.common.code.BpmnTaskRespCode.ACTIVITY_BIZ_SET_ASSIGNEE_HAS_REPEAT;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDuplicateByPersonId;
import static cn.azxo.framework.common.model.CommonResponse.success;
@ -44,7 +46,8 @@ import static cn.azxo.framework.common.model.CommonResponse.success;
@ErrorReporter
@Validated
public class BpmnProcessActivityController extends BasicPopulateAvatarController implements ProcessActivityApi {
@Resource
private HttpServletRequest request;
@Resource
private BpmnProcessActivityService bpmnProcessActivityService;
@ -61,7 +64,9 @@ public class BpmnProcessActivityController extends BasicPopulateAvatarController
@RepeatSubmit
@Deprecated
public CommonResponse<Boolean> trigger(@NotBlank(message = "触发 ID 不能为空") @RequestParam String triggerId) {
log.info("业务节点唤醒 trigger2 ===>>>参数:{}", triggerId);
String header = request.getHeader(HEADER_SERVER_NAME);
String remoteAddr = request.getRemoteAddr();
log.info("业务节点唤醒 trigger2 ===>>>参数:{}, 请求来自微服务:{}, ip: {}", triggerId, header, remoteAddr);
return trigger(new BpmnActivityTriggerDTO(triggerId, true, null));
}
@ -76,7 +81,10 @@ public class BpmnProcessActivityController extends BasicPopulateAvatarController
@Override
@RepeatSubmit
public CommonResponse<Boolean> trigger(@Validated @RequestBody BpmnActivityTriggerDTO dto) {
log.info("业务节点唤醒 trigger ===>>>参数:{}", JSON.toJSONString(dto));
log.info("businessNode trigger ===>>>参数:{}", JSON.toJSONString(dto));
String header = request.getHeader(HEADER_SERVER_NAME);
String remoteAddr = request.getRemoteAddr();
log.info("业务节点唤醒 trigger ===>>>参数:{}, 请求来自微服务: {}, ip: {}", JSON.toJSONString(dto), header, remoteAddr);
bpmnProcessActivityService.trigger(dto);
return success(true);
}

View File

@ -45,6 +45,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import static cn.axzo.workflow.common.constant.BpmnConstants.AND_SIGN_EXPRESSION;
@ -97,32 +98,35 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
@Override
public void onCreate(TaskEntity taskEntity) {
log.info("TaskEntityEventHandle#onCreate processInstanceId: {}, taskEntityId: {}", taskEntity.getProcessInstanceId(), taskEntity.getTaskDefinitionKey());
// 记录发起人
boolean isNodeStarter = Objects.equals(taskEntity.getTaskDefinitionKey(), NODE_STARTER.getType());
BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(taskEntity.getProcessDefinitionId());
FlowElement flowElement = bpmnModel.getFlowElement(taskEntity.getTaskDefinitionKey());
ExtAxProcessLog log = new ExtAxProcessLog();
log.setProcessInstanceId(taskEntity.getProcessInstanceId());
log.setTenantId(taskEntity.getTenantId());
log.setActivityId(taskEntity.getTaskDefinitionKey());
log.setActivityName(taskEntity.getName());
log.setApprovalMethod((isNodeStarter ? nobody : getApprovalMethod(flowElement).orElse(nobody)).getType());
log.setNodeType((getNodeType(flowElement).orElse(BpmnFlowNodeType.NODE_EMPTY)).getType());
log.setNodeMode((isNodeStarter ? BpmnFlowNodeMode.GENERAL : getNodeMode(flowElement)).getType());
log.setTaskId(taskEntity.getId());
ExtAxProcessLog processLog = new ExtAxProcessLog();
processLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
processLog.setTenantId(taskEntity.getTenantId());
processLog.setActivityId(taskEntity.getTaskDefinitionKey());
processLog.setActivityName(taskEntity.getName());
processLog.setApprovalMethod((isNodeStarter ? nobody : getApprovalMethod(flowElement).orElse(nobody)).getType());
processLog.setNodeType((getNodeType(flowElement).orElse(BpmnFlowNodeType.NODE_EMPTY)).getType());
processLog.setNodeMode((isNodeStarter ? BpmnFlowNodeMode.GENERAL : getNodeMode(flowElement)).getType());
processLog.setTaskId(taskEntity.getId());
String operationDesc = taskEntity.getVariable(COMMENT_TYPE_OPERATION_DESC, String.class);
log.setOperationDesc(StringUtils.hasText(operationDesc) ? operationDesc : HANDLING.getDesc());
log.setStartTime(taskEntity.getCreateTime());
log.setStatus(PROCESSING.getStatus());
log.setSignature(getActivitySignature(flowElement));
processLog.setOperationDesc(StringUtils.hasText(operationDesc) ? operationDesc : HANDLING.getDesc());
processLog.setStartTime(taskEntity.getCreateTime());
processLog.setStatus(PROCESSING.getStatus());
processLog.setSignature(getActivitySignature(flowElement));
processLogService.insert(log);
log.info("TaskEntityEventHandle#onCreate insert:{}", JSON.toJSONString(processLog));
processLogService.insert(processLog);
}
@Override
public void onInitialized(TaskEntity taskEntity) {
log.info("TaskEntityEventHandle#onInitialized processInstanceId: {}, taskEntityId: {}", taskEntity.getProcessInstanceId(), taskEntity.getTaskDefinitionKey());
Process process = ProcessDefinitionUtil.getProcess(taskEntity.getProcessDefinitionId());
ExtAxProcessLog queryLog = new ExtAxProcessLog();
queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
@ -132,15 +136,18 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
.ifPresent(updateLog::setButtonConf);
BpmnMetaParserHelper.getFormFieldPermissionConf(process.getFlowElement(taskEntity.getTaskDefinitionKey()))
.ifPresent(updateLog::setFormFieldPermissionConf);
log.info("TaskEntityEventHandle#onInitialized update, queryLog: {}, updateLog: {}", JSON.toJSONString(queryLog), JSON.toJSONString(updateLog));
processLogService.update(queryLog, updateLog);
}
@Override
public void onUpdated(TaskEntity taskEntity) {
log.info("TaskEntityEventHandle#onUpdated processInstanceId: {}, taskEntityId: {}", taskEntity.getProcessInstanceId(), taskEntity.getTaskDefinitionKey());
if (Objects.equals(HIDDEN_ASSIGNEE_ID, taskEntity.getAssignee())) {
ExtAxProcessLog queryLog = new ExtAxProcessLog();
queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
queryLog.setTaskId(taskEntity.getId());
log.info("TaskEntityEventHandle#onUpdated delete: {}", JSON.toJSONString(queryLog));
processLogService.delete(queryLog);
} else {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
@ -157,6 +164,7 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
// 快照审批人的组织架构信息
OrgStructureSnapshotInfo snapshotInfo = buildApproverOrgStructureInfo(assignee, taskEntity);
log.info("TaskEntityEventHandle#onUpdated updateAssigneeAndSnapshot, queryLog: {}, assignee: {}, snapshotInfo: {}", JSON.toJSONString(queryLog), JSON.toJSONString(assignee), JSON.toJSONString(snapshotInfo));
processLogService.updateAssigneeAndSnapshot(queryLog, assignee, snapshotInfo);
});
}
@ -230,6 +238,7 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
@Override
public void onDeleted(TaskEntity taskEntity) {
log.info("TaskEntityEventHandle#onDeleted processInstanceId: {}, taskEntityId: {}", taskEntity.getProcessInstanceId(), taskEntity.getTaskDefinitionKey());
ExtAxProcessLog queryLog = new ExtAxProcessLog();
queryLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
queryLog.setTaskId(taskEntity.getId());
@ -257,7 +266,8 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
if (Objects.nonNull(advice) && StringUtils.hasText(advice.toString())) {
update.setAdvice(advice.toString());
}
Object operationDesc = taskEntity.getTransientVariableLocal(COMMENT_TYPE_OPERATION_DESC);
Object operationDesc = Optional.ofNullable(taskEntity.getTransientVariableLocal(COMMENT_TYPE_OPERATION_DESC)).orElse(taskEntity.getVariableLocal(COMMENT_TYPE_OPERATION_DESC));
log.info("TaskEntityEventHandle#onDeleted processInstanceId: {}, operationDesc: {}", taskEntity.getProcessInstanceId(), operationDesc);
if (Objects.nonNull(operationDesc) && StringUtils.hasText(operationDesc.toString())) {
update.setOperationDesc(Objects.nonNull(assignee) ?
(StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "") + operationDesc
@ -267,6 +277,7 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
(StringUtils.hasText(assignee.getAssignerName()) ? assignee.getAssignerName() : "")
: "");
}
taskEntity.removeVariableLocal(COMMENT_TYPE_OPERATION_DESC);
String completionType = taskEntity.getVariable(TASK_COMPLETE_OPERATION_TYPE + taskEntity.getId(), String.class);
@ -289,6 +300,7 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
update.setOperationDesc("抄送" + carbonCopies.size() + "");
}
log.info("TaskEntityEventHandle#onDeleted update, queryLog: {}, update: {}", JSON.toJSONString(queryLog), JSON.toJSONString(update));
processLogService.update(queryLog, update);
if (needDelete) {
@ -296,6 +308,7 @@ public class TaskEntityEventHandle implements EntityEventHandle<TaskEntity> {
ExtAxProcessLog deleteLog = new ExtAxProcessLog();
deleteLog.setProcessInstanceId(taskEntity.getProcessInstanceId());
deleteLog.setTaskId(taskEntity.getId());
log.info("TaskEntityEventHandle#onDeleted delete, deleteLog: {}", JSON.toJSONString(deleteLog));
processLogService.delete(deleteLog);
}
}

View File

@ -81,19 +81,30 @@ public class ImplementationReadyChecker implements ApplicationListener<Applicati
}
/**
* 自适应转换value 15 时返回 4 位数组否则返回最小所需位数数组
*/
public static boolean[] toBooleansAdaptive(int value) {
if (value < 0) throw new IllegalArgumentException("adaptive mode only for non-negative");
if (value == 0) return new boolean[]{false};
int bits = 32 - Integer.numberOfLeadingZeros(value);
if (value < 0) {
throw new IllegalArgumentException("adaptive mode only for non-negative");
}
// 核心修改value 15 强制用 4 否则计算最小所需位数
int bits = (value <= 15) ? 4 : (32 - Integer.numberOfLeadingZeros(value));
return toBooleans(value, bits);
}
// 高位在前index=0 是最高位
/**
* value 转换为指定位数的 boolean 数组高位在前
*/
public static boolean[] toBooleans(int value, int bits) {
if (bits <= 0 || bits > 32) throw new IllegalArgumentException("bits must be 1~32");
if (bits <= 0 || bits > 32) {
throw new IllegalArgumentException("bits must be 1~32");
}
boolean[] arr = new boolean[bits];
for (int i = 0; i < bits; i++) {
int shift = bits - 1 - i;
int shift = bits - 1 - i; // 从高位到低位遍历
arr[i] = ((value >>> shift) & 1) == 1;
}
return arr;