mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 11:58:34 +08:00
fix(workflow): pre-check unique name on create/rename, return 409 instead of 500 (Fixes Gitee #IJPYWA)
This commit is contained in:
parent
b64a312994
commit
ac090afde3
@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import vip.mate.exception.MateClawException;
|
||||||
import vip.mate.workflow.compiler.PublishContext;
|
import vip.mate.workflow.compiler.PublishContext;
|
||||||
import vip.mate.workflow.compiler.WorkflowAclPort;
|
import vip.mate.workflow.compiler.WorkflowAclPort;
|
||||||
import vip.mate.workflow.compiler.WorkflowCompiler;
|
import vip.mate.workflow.compiler.WorkflowCompiler;
|
||||||
@ -112,6 +113,10 @@ public class WorkflowService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public WorkflowEntity create(WorkflowEntity workflow) {
|
public WorkflowEntity create(WorkflowEntity workflow) {
|
||||||
if (workflow.getEnabled() == null) workflow.setEnabled(true);
|
if (workflow.getEnabled() == null) workflow.setEnabled(true);
|
||||||
|
// Pre-check name uniqueness so the duplicate path surfaces as a friendly
|
||||||
|
// 409 instead of an opaque 500 from the uk_workflow_workspace_name
|
||||||
|
// unique index hitting the catch-all handler.
|
||||||
|
requireUniqueName(workflow.getWorkspaceId(), workflow.getName(), null);
|
||||||
workflowMapper.insert(workflow);
|
workflowMapper.insert(workflow);
|
||||||
return workflow;
|
return workflow;
|
||||||
}
|
}
|
||||||
@ -128,7 +133,10 @@ public class WorkflowService {
|
|||||||
public WorkflowEntity updateMetadata(long id, long workspaceId, String name,
|
public WorkflowEntity updateMetadata(long id, long workspaceId, String name,
|
||||||
String description, Boolean enabled) {
|
String description, Boolean enabled) {
|
||||||
WorkflowEntity existing = getOrThrow(id, workspaceId);
|
WorkflowEntity existing = getOrThrow(id, workspaceId);
|
||||||
if (name != null) existing.setName(name);
|
if (name != null && !name.equals(existing.getName())) {
|
||||||
|
requireUniqueName(workspaceId, name, id);
|
||||||
|
existing.setName(name);
|
||||||
|
}
|
||||||
if (description != null) existing.setDescription(description);
|
if (description != null) existing.setDescription(description);
|
||||||
if (enabled != null) existing.setEnabled(enabled);
|
if (enabled != null) existing.setEnabled(enabled);
|
||||||
// draftJson / latest_revision_id / workspace_id are intentionally
|
// draftJson / latest_revision_id / workspace_id are intentionally
|
||||||
@ -216,6 +224,35 @@ public class WorkflowService {
|
|||||||
return new PublishOutcome(workflow, revision);
|
return new PublishOutcome(workflow, revision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject a create / rename whose name already exists in the workspace. The
|
||||||
|
* underlying table carries a unique index on (workspace_id, name, deleted),
|
||||||
|
* so without this pre-check the duplicate insert would bubble up as a
|
||||||
|
* generic 500 with a database-level "duplicate entry" message. Pass a
|
||||||
|
* non-null {@code excludeId} on rename so the row doesn't see itself as a
|
||||||
|
* conflict.
|
||||||
|
*/
|
||||||
|
private void requireUniqueName(Long workspaceId, String name, Long excludeId) {
|
||||||
|
if (name == null || name.isBlank()) {
|
||||||
|
throw new MateClawException("err.workflow.name_required", 400,
|
||||||
|
"工作流名称不能为空");
|
||||||
|
}
|
||||||
|
if (workspaceId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LambdaQueryWrapper<WorkflowEntity> q = new LambdaQueryWrapper<WorkflowEntity>()
|
||||||
|
.eq(WorkflowEntity::getWorkspaceId, workspaceId)
|
||||||
|
.eq(WorkflowEntity::getName, name);
|
||||||
|
if (excludeId != null) {
|
||||||
|
q.ne(WorkflowEntity::getId, excludeId);
|
||||||
|
}
|
||||||
|
Long count = workflowMapper.selectCount(q);
|
||||||
|
if (count != null && count > 0) {
|
||||||
|
throw new MateClawException("err.workflow.duplicate_name", 409,
|
||||||
|
"工作区内已存在同名工作流: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int nextRevisionNumber(long workflowId) {
|
private int nextRevisionNumber(long workflowId) {
|
||||||
WorkflowRevisionEntity max = revisionMapper.selectOne(new LambdaQueryWrapper<WorkflowRevisionEntity>()
|
WorkflowRevisionEntity max = revisionMapper.selectOne(new LambdaQueryWrapper<WorkflowRevisionEntity>()
|
||||||
.eq(WorkflowRevisionEntity::getWorkflowId, workflowId)
|
.eq(WorkflowRevisionEntity::getWorkflowId, workflowId)
|
||||||
|
|||||||
@ -171,6 +171,8 @@ err.agent.not_found=Agent\u4e0d\u5b58\u5728
|
|||||||
err.agent.disabled=Agent \u5df2\u7981\u7528
|
err.agent.disabled=Agent \u5df2\u7981\u7528
|
||||||
err.agent.name_required=Agent \u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a
|
err.agent.name_required=Agent \u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a
|
||||||
err.agent.duplicate_name=\u5f53\u524d\u5de5\u4f5c\u533a\u5df2\u5b58\u5728\u540c\u540d\u5458\u5de5\uff0c\u8bf7\u6362\u4e2a\u540d\u5b57\u518d\u8bd5
|
err.agent.duplicate_name=\u5f53\u524d\u5de5\u4f5c\u533a\u5df2\u5b58\u5728\u540c\u540d\u5458\u5de5\uff0c\u8bf7\u6362\u4e2a\u540d\u5b57\u518d\u8bd5
|
||||||
|
err.workflow.name_required=\u5de5\u4f5c\u6d41\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a
|
||||||
|
err.workflow.duplicate_name=\u5f53\u524d\u5de5\u4f5c\u533a\u5df2\u5b58\u5728\u540c\u540d\u5de5\u4f5c\u6d41\uff0c\u8bf7\u6362\u4e2a\u540d\u5b57\u518d\u8bd5
|
||||||
err.workspace.not_found=\u5de5\u4f5c\u533a\u4e0d\u5b58\u5728
|
err.workspace.not_found=\u5de5\u4f5c\u533a\u4e0d\u5b58\u5728
|
||||||
err.workspace.slug_exists=\u5de5\u4f5c\u533a\u6807\u8bc6\u5df2\u5b58\u5728
|
err.workspace.slug_exists=\u5de5\u4f5c\u533a\u6807\u8bc6\u5df2\u5b58\u5728
|
||||||
err.workspace.cannot_modify_default=\u4e0d\u80fd\u4fee\u6539\u9ed8\u8ba4\u5de5\u4f5c\u533a\u7684\u6807\u8bc6
|
err.workspace.cannot_modify_default=\u4e0d\u80fd\u4fee\u6539\u9ed8\u8ba4\u5de5\u4f5c\u533a\u7684\u6807\u8bc6
|
||||||
|
|||||||
@ -177,6 +177,8 @@ err.agent.not_found=Agent not found
|
|||||||
err.agent.disabled=Agent is disabled
|
err.agent.disabled=Agent is disabled
|
||||||
err.agent.name_required=Agent name is required
|
err.agent.name_required=Agent name is required
|
||||||
err.agent.duplicate_name=An employee with this name already exists in this workspace — try a different name
|
err.agent.duplicate_name=An employee with this name already exists in this workspace — try a different name
|
||||||
|
err.workflow.name_required=Workflow name is required
|
||||||
|
err.workflow.duplicate_name=A workflow with this name already exists in this workspace — try a different name
|
||||||
# workspace
|
# workspace
|
||||||
err.workspace.not_found=Workspace not found
|
err.workspace.not_found=Workspace not found
|
||||||
err.workspace.slug_exists=Workspace slug already exists
|
err.workspace.slug_exists=Workspace slug already exists
|
||||||
|
|||||||
@ -13,12 +13,14 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
import vip.mate.MateClawApplication;
|
import vip.mate.MateClawApplication;
|
||||||
import vip.mate.common.result.R;
|
import vip.mate.common.result.R;
|
||||||
|
import vip.mate.exception.MateClawException;
|
||||||
import vip.mate.workflow.compiler.WorkflowAclPort;
|
import vip.mate.workflow.compiler.WorkflowAclPort;
|
||||||
import vip.mate.workflow.model.WorkflowEntity;
|
import vip.mate.workflow.model.WorkflowEntity;
|
||||||
import vip.mate.workflow.repository.WorkflowMapper;
|
import vip.mate.workflow.repository.WorkflowMapper;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,6 +99,21 @@ class WorkflowControllerTest {
|
|||||||
assertInstanceOf(CompileErrorResponse.class, body.getData());
|
assertInstanceOf(CompileErrorResponse.class, body.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("create() rejects a second workflow with the same name as a 409 instead of a 500 from the unique index.")
|
||||||
|
void createRejectsDuplicateNameAs409() {
|
||||||
|
WorkflowEntity first = new WorkflowEntity();
|
||||||
|
first.setName("dup");
|
||||||
|
controller.create(first, 99L);
|
||||||
|
|
||||||
|
WorkflowEntity second = new WorkflowEntity();
|
||||||
|
second.setName("dup");
|
||||||
|
MateClawException ex = assertThrows(MateClawException.class,
|
||||||
|
() -> controller.create(second, 99L));
|
||||||
|
assertEquals(409, ex.getCode());
|
||||||
|
assertEquals("err.workflow.duplicate_name", ex.getMsgKey());
|
||||||
|
}
|
||||||
|
|
||||||
private Long createWorkflow(String name) {
|
private Long createWorkflow(String name) {
|
||||||
WorkflowEntity wf = new WorkflowEntity();
|
WorkflowEntity wf = new WorkflowEntity();
|
||||||
wf.setWorkspaceId(99L);
|
wf.setWorkspaceId(99L);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user