fix(tool-guard): friendly duplicate rule-id rejection and client-side name/pattern validation (#88)

This commit is contained in:
matevip 2026-05-13 09:55:30 +08:00
parent aed559d1cf
commit 2bca55f5b4
5 changed files with 36 additions and 6 deletions

View File

@ -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<ToolGuardRuleEntity> 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);
}
}

View File

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

View File

@ -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',
},
},

View File

@ -1266,6 +1266,9 @@ export default {
},
messages: {
ruleIdRequired: '规则 ID 不能为空',
nameRequired: '名称不能为空',
patternRequired: '正则模式不能为空',
ruleIdDuplicate: '规则 ID 已存在',
saveFailed: '保存规则失败',
},
},

View File

@ -155,12 +155,12 @@
/>
</div>
<div class="form-group">
<label>{{ t('security.toolGuard.fields.name') }}</label>
<input v-model="ruleForm.name" class="form-input" />
<label>{{ t('security.toolGuard.fields.name') }} <span class="required">*</span></label>
<input v-model="ruleForm.name" class="form-input" required />
</div>
<div class="form-group">
<label>{{ t('security.toolGuard.fields.pattern') }}</label>
<input v-model="ruleForm.pattern" class="form-input mono" placeholder="regex pattern" />
<label>{{ t('security.toolGuard.fields.pattern') }} <span class="required">*</span></label>
<input v-model="ruleForm.pattern" class="form-input mono" placeholder="regex pattern" required />
</div>
<div class="form-group">
<label>{{ t('security.toolGuard.fields.severity') }}</label>
@ -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'))
}
}
}