fix: 防止定时任务引用失效工作流

- 保存任务时校验工作流权限与必填参数

- 删除工作流前检查并重新确认定时任务引用
This commit is contained in:
2026-08-03 14:50:28 +08:00
parent 93db17b384
commit 8c334be65d
12 changed files with 817 additions and 43 deletions

View File

@@ -2,18 +2,29 @@ package tech.easyflow.admin.controller.job;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import com.easyagents.flow.core.chain.Parameter;
import org.quartz.CronExpression;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.easyagentsflow.service.WorkflowRunningParameterResolver;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.common.constant.enums.EnumJobType;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.job.entity.SysJob;
import tech.easyflow.job.job.JobConstant;
import tech.easyflow.job.service.SysJobService;
import tech.easyflow.job.support.SysJobWorkflowReferenceSupport;
import tech.easyflow.log.annotation.LogRecord;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.service.ResourceAccessService;
import java.io.Serializable;
import java.math.BigInteger;
@@ -21,6 +32,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 系统任务表 控制层。
@@ -31,8 +43,32 @@ import java.util.List;
@RestController
@RequestMapping("/api/v1/sysJob")
public class SysJobController extends BaseCurdController<SysJobService, SysJob> {
public SysJobController(SysJobService service) {
/** 工作流服务。 */
private final WorkflowService workflowService;
/** 资源访问控制服务。 */
private final ResourceAccessService resourceAccessService;
/** 工作流运行参数解析器。 */
private final WorkflowRunningParameterResolver workflowRunningParameterResolver;
/**
* 创建定时任务控制器。
*
* @param service 定时任务服务
* @param workflowService 工作流服务
* @param resourceAccessService 资源访问控制服务
* @param workflowRunningParameterResolver 工作流运行参数解析器
*/
public SysJobController(SysJobService service,
WorkflowService workflowService,
ResourceAccessService resourceAccessService,
WorkflowRunningParameterResolver workflowRunningParameterResolver) {
super(service);
this.workflowService = workflowService;
this.resourceAccessService = resourceAccessService;
this.workflowRunningParameterResolver = workflowRunningParameterResolver;
}
@GetMapping("/start")
@@ -73,9 +109,94 @@ public class SysJobController extends BaseCurdController<SysJobService, SysJob>
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
validateWorkflowReference(entity);
return super.onSaveOrUpdateBefore(entity, isSave);
}
/**
* 校验工作流类型任务引用的工作流可被当前用户运行。
*
* @param entity 待保存的定时任务
* @throws BusinessException 工作流不存在、参数非法或无运行权限时抛出
*/
private void validateWorkflowReference(SysJob entity) {
if (entity == null
|| !Integer.valueOf(EnumJobType.TINY_FLOW.getCode()).equals(entity.getJobType())) {
return;
}
BigInteger workflowId = SysJobWorkflowReferenceSupport.requireWorkflowId(entity);
Workflow workflow = workflowService.getById(workflowId);
if (workflow == null) {
throw new BusinessException("工作流不存在,请重新选择");
}
resourceAccessService.assertAccess(
CategoryResourceType.WORKFLOW,
workflow,
ResourceAction.USE,
"无权限运行所选工作流"
);
validateRequiredWorkflowParams(entity, workflow);
}
/**
* 校验定时任务已填写工作流的全部必填运行参数。
*
* @param entity 待保存定时任务
* @param workflow 关联工作流
* @throws BusinessException 工作流参数配置无效或必填值缺失时抛出
*/
private void validateRequiredWorkflowParams(SysJob entity, Workflow workflow) {
List<Parameter> parameters =
workflowRunningParameterResolver.resolveStartParameters(workflow.getContent());
if (parameters == null) {
throw new BusinessException("工作流参数配置无效,请检查工作流后重试");
}
Map<String, Object> jobParams = entity.getJobParams();
Object rawWorkflowParams = jobParams == null
? null
: jobParams.get(JobConstant.WORKFLOW_PARAMS_KEY);
Map<?, ?> workflowParams = rawWorkflowParams instanceof Map<?, ?> map
? map
: Map.of();
for (Parameter parameter : parameters) {
if (parameter == null || !parameter.isRequired()) {
continue;
}
String name = parameter.getName();
if (!StringUtils.hasText(name)) {
throw new BusinessException("工作流存在无效必填参数配置,请检查工作流后重试");
}
if (!hasRequiredValue(workflowParams.get(name))) {
String label = StringUtils.hasText(parameter.getFormLabel())
? parameter.getFormLabel()
: name;
throw new BusinessException("工作流必填参数“" + label + "”不能为空");
}
}
}
/**
* 判断必填参数值是否有效。
*
* @param value 参数值
* @return 非空时为 true
*/
private boolean hasRequiredValue(Object value) {
if (value == null) {
return false;
}
if (value instanceof CharSequence sequence) {
return StringUtils.hasText(sequence);
}
if (value instanceof Collection<?> collection) {
return !collection.isEmpty();
}
if (value instanceof Map<?, ?> map) {
return !map.isEmpty();
}
return true;
}
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
service.deleteJob(ids);

View File

@@ -0,0 +1,79 @@
package tech.easyflow.admin.controller.job;
import com.easyagents.flow.core.chain.Parameter;
import org.mockito.MockedStatic;
import org.testng.Assert;
import org.testng.annotations.Test;
import tech.easyflow.ai.easyagentsflow.service.WorkflowRunningParameterResolver;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.common.constant.enums.EnumJobType;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.job.entity.SysJob;
import tech.easyflow.job.job.JobConstant;
import tech.easyflow.job.service.SysJobService;
import tech.easyflow.system.service.ResourceAccessService;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
/**
* {@link SysJobController} 工作流任务参数校验测试。
*/
public class SysJobControllerTest {
/**
* 验证缺少工作流必填参数时拒绝保存定时任务。
*/
@Test
public void shouldRejectWorkflowJobWhenRequiredParameterIsMissing() {
BigInteger workflowId = BigInteger.valueOf(101);
SysJobService jobService = mock(SysJobService.class);
WorkflowService workflowService = mock(WorkflowService.class);
ResourceAccessService resourceAccessService = mock(ResourceAccessService.class);
WorkflowRunningParameterResolver parameterResolver =
mock(WorkflowRunningParameterResolver.class);
Workflow workflow = new Workflow();
workflow.setId(workflowId);
workflow.setContent("{}");
when(workflowService.getById(workflowId)).thenReturn(workflow);
Parameter requiredParameter = mock(Parameter.class);
when(requiredParameter.isRequired()).thenReturn(true);
when(requiredParameter.getName()).thenReturn("user_input");
when(requiredParameter.getFormLabel()).thenReturn("用户问题");
when(parameterResolver.resolveStartParameters(workflow.getContent()))
.thenReturn(List.of(requiredParameter));
SysJobController controller = new SysJobController(
jobService,
workflowService,
resourceAccessService,
parameterResolver
);
SysJob job = new SysJob();
job.setJobType(EnumJobType.TINY_FLOW.getCode());
job.setJobParams(Map.of(
JobConstant.WORKFLOW_KEY, workflowId.toString(),
JobConstant.WORKFLOW_PARAMS_KEY, Map.of()
));
LoginAccount account = new LoginAccount();
account.setId(BigInteger.ONE);
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
BusinessException exception = Assert.expectThrows(
BusinessException.class,
() -> controller.onSaveOrUpdateBefore(job, true)
);
Assert.assertTrue(exception.getMessage().contains("用户问题"));
}
}
}