mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-14 19:45:08 +08:00
fix(tool-guard): friendly duplicate rule-id rejection and client-side name/pattern validation (#88)
This commit is contained in:
parent
aed559d1cf
commit
2bca55f5b4
@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import vip.mate.approval.ApprovalWorkflowService;
|
import vip.mate.approval.ApprovalWorkflowService;
|
||||||
import vip.mate.common.result.R;
|
import vip.mate.common.result.R;
|
||||||
@ -97,8 +98,12 @@ public class SecurityController {
|
|||||||
public R<ToolGuardRuleEntity> createRule(@RequestBody ToolGuardRuleEntity rule) {
|
public R<ToolGuardRuleEntity> createRule(@RequestBody ToolGuardRuleEntity rule) {
|
||||||
try {
|
try {
|
||||||
return R.ok(ruleService.createRule(rule));
|
return R.ok(ruleService.createRule(rule));
|
||||||
} catch (Exception e) {
|
} catch (IllegalArgumentException e) {
|
||||||
return R.fail(e.getMessage());
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,12 @@ public class ToolGuardRuleService {
|
|||||||
rule.setName(rule.getName().trim());
|
rule.setName(rule.getName().trim());
|
||||||
rule.setPattern(rule.getPattern().trim());
|
rule.setPattern(rule.getPattern().trim());
|
||||||
rule.setBuiltin(false);
|
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);
|
ruleMapper.insert(rule);
|
||||||
ruleRegistry.reload();
|
ruleRegistry.reload();
|
||||||
return rule;
|
return rule;
|
||||||
|
|||||||
@ -1368,6 +1368,9 @@ export default {
|
|||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
ruleIdRequired: 'Rule ID is required',
|
ruleIdRequired: 'Rule ID is required',
|
||||||
|
nameRequired: 'Name is required',
|
||||||
|
patternRequired: 'Regex pattern is required',
|
||||||
|
ruleIdDuplicate: 'Rule ID already exists',
|
||||||
saveFailed: 'Failed to save rule',
|
saveFailed: 'Failed to save rule',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1266,6 +1266,9 @@ export default {
|
|||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
ruleIdRequired: '规则 ID 不能为空',
|
ruleIdRequired: '规则 ID 不能为空',
|
||||||
|
nameRequired: '名称不能为空',
|
||||||
|
patternRequired: '正则模式不能为空',
|
||||||
|
ruleIdDuplicate: '规则 ID 已存在',
|
||||||
saveFailed: '保存规则失败',
|
saveFailed: '保存规则失败',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -155,12 +155,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ t('security.toolGuard.fields.name') }}</label>
|
<label>{{ t('security.toolGuard.fields.name') }} <span class="required">*</span></label>
|
||||||
<input v-model="ruleForm.name" class="form-input" />
|
<input v-model="ruleForm.name" class="form-input" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ t('security.toolGuard.fields.pattern') }}</label>
|
<label>{{ t('security.toolGuard.fields.pattern') }} <span class="required">*</span></label>
|
||||||
<input v-model="ruleForm.pattern" class="form-input mono" placeholder="regex pattern" />
|
<input v-model="ruleForm.pattern" class="form-input mono" placeholder="regex pattern" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ t('security.toolGuard.fields.severity') }}</label>
|
<label>{{ t('security.toolGuard.fields.severity') }}</label>
|
||||||
@ -360,6 +360,14 @@ async function saveRule() {
|
|||||||
ElMessage.error(t('security.toolGuard.messages.ruleIdRequired'))
|
ElMessage.error(t('security.toolGuard.messages.ruleIdRequired'))
|
||||||
return
|
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 {
|
try {
|
||||||
if (editingRule.value) {
|
if (editingRule.value) {
|
||||||
@ -370,7 +378,12 @@ async function saveRule() {
|
|||||||
showRuleModal.value = false
|
showRuleModal.value = false
|
||||||
loadRules()
|
loadRules()
|
||||||
} catch (e: any) {
|
} 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'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user