diff --git a/mateclaw-server/src/main/java/vip/mate/tool/guard/controller/SecurityController.java b/mateclaw-server/src/main/java/vip/mate/tool/guard/controller/SecurityController.java index 156e5935..7e279fa9 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/guard/controller/SecurityController.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/guard/controller/SecurityController.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DuplicateKeyException; import org.springframework.web.bind.annotation.*; import vip.mate.approval.ApprovalWorkflowService; import vip.mate.common.result.R; @@ -97,8 +98,12 @@ public class SecurityController { public R createRule(@RequestBody ToolGuardRuleEntity rule) { try { return R.ok(ruleService.createRule(rule)); - } catch (Exception e) { + } catch (IllegalArgumentException e) { return R.fail(e.getMessage()); + } catch (DuplicateKeyException e) { + // Race fallback: pre-check passed but a concurrent insert took the slot. + String ruleId = rule != null && rule.getRuleId() != null ? rule.getRuleId().trim() : ""; + return R.fail("Rule ID already exists: " + ruleId); } } diff --git a/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleService.java b/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleService.java index a53cbff4..66ab98b4 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleService.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/guard/service/ToolGuardRuleService.java @@ -74,6 +74,12 @@ public class ToolGuardRuleService { rule.setName(rule.getName().trim()); rule.setPattern(rule.getPattern().trim()); rule.setBuiltin(false); + // Pre-check uniqueness so the API returns a friendly message instead of + // surfacing the raw JDBC UNIQUE-constraint violation through the global + // exception handler. The DB constraint still guards against races. + if (getByRuleId(rule.getRuleId()) != null) { + throw new IllegalArgumentException("Rule ID already exists: " + rule.getRuleId()); + } ruleMapper.insert(rule); ruleRegistry.reload(); return rule; diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 6418bae8..7ddcaa4b 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -1368,6 +1368,9 @@ export default { }, messages: { ruleIdRequired: 'Rule ID is required', + nameRequired: 'Name is required', + patternRequired: 'Regex pattern is required', + ruleIdDuplicate: 'Rule ID already exists', saveFailed: 'Failed to save rule', }, }, diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index 49ba1b74..106a97a4 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -1266,6 +1266,9 @@ export default { }, messages: { ruleIdRequired: '规则 ID 不能为空', + nameRequired: '名称不能为空', + patternRequired: '正则模式不能为空', + ruleIdDuplicate: '规则 ID 已存在', saveFailed: '保存规则失败', }, }, diff --git a/mateclaw-ui/src/views/Security/ToolGuard/index.vue b/mateclaw-ui/src/views/Security/ToolGuard/index.vue index d64d7996..0f553437 100644 --- a/mateclaw-ui/src/views/Security/ToolGuard/index.vue +++ b/mateclaw-ui/src/views/Security/ToolGuard/index.vue @@ -155,12 +155,12 @@ />
- - + +
- - + +
@@ -360,6 +360,14 @@ async function saveRule() { ElMessage.error(t('security.toolGuard.messages.ruleIdRequired')) return } + if (!ruleForm.name.trim()) { + ElMessage.error(t('security.toolGuard.messages.nameRequired')) + return + } + if (!ruleForm.pattern.trim()) { + ElMessage.error(t('security.toolGuard.messages.patternRequired')) + return + } try { if (editingRule.value) { @@ -370,7 +378,12 @@ async function saveRule() { showRuleModal.value = false loadRules() } catch (e: any) { - ElMessage.error(e?.msg || e?.message || t('security.toolGuard.messages.saveFailed')) + const raw = e?.msg || e?.message || '' + if (typeof raw === 'string' && raw.toLowerCase().includes('already exists')) { + ElMessage.error(t('security.toolGuard.messages.ruleIdDuplicate')) + } else { + ElMessage.error(raw || t('security.toolGuard.messages.saveFailed')) + } } }