feat(evaluation): 模块2.2 - 增强评查点创建/更新接口验证逻辑
功能变更: 1. 增强 createRule 函数 - 添加必填字段验证(name, code) - 验证名称长度(1-100字符) - 验证编码格式(仅允许字母、数字、连字符和下划线) - 验证编码唯一性(防止重复) - 验证分组ID有效性(检查分组是否存在) - 自动trim名称和编码空格 - 返回详细的错误信息和HTTP状态码 2. 增强 updateRule 函数 - 验证评查点ID有效性(检查评查点是否存在) - 验证名称长度(如果提供) - 验证编码格式(如果提供) - 验证编码唯一性(排除自身,防止与其他评查点冲突) - 验证分组ID有效性(如果提供) - 自动trim名称和编码空格 - 支持部分字段更新 - 返回详细的错误信息和HTTP状态码 技术实现: - 复用 getRulesList 进行编码唯一性检查 - 复用 getRule 进行ID有效性检查 - 使用 PostgREST 查询验证分组存在性 - 精确匹配防止关键词模糊搜索误判 - 统一错误处理和状态码返回 验收标准: ✅ 必填字段验证 ✅ 名称长度验证(1-100字符) ✅ 编码格式验证(^[a-zA-Z0-9-_]+$) ✅ 编码唯一性验证 ✅ 分组ID有效性验证 ✅ 更新时ID存在性验证 ✅ 更新时编码唯一性验证(排除自身) ✅ 支持部分字段更新 ✅ 返回清晰的错误提示 符合实施计划: - 阶段 2.2:评查点创建/更新接口对接 ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -509,14 +509,69 @@ export async function getRule(id: string, token?: string): Promise<{data: Rule;
|
||||
*/
|
||||
export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 1. 验证必填字段
|
||||
if (!ruleData.name || !ruleData.code) {
|
||||
return { error: '评查点名称和编码不能为空', status: 400 };
|
||||
}
|
||||
|
||||
// 2. 验证名称长度(1-100字符)
|
||||
const trimmedName = ruleData.name.trim();
|
||||
if (trimmedName.length === 0 || trimmedName.length > 100) {
|
||||
return { error: '评查点名称长度必须在1-100个字符之间', status: 400 };
|
||||
}
|
||||
|
||||
// 3. 验证编码格式(仅允许字母、数字、连字符和下划线)
|
||||
const trimmedCode = ruleData.code.trim();
|
||||
if (!/^[a-zA-Z0-9-_]+$/.test(trimmedCode)) {
|
||||
return { error: '评查点编码只能包含字母、数字、连字符和下划线', status: 400 };
|
||||
}
|
||||
|
||||
// 4. 验证编码唯一性
|
||||
const existingRulesResponse = await getRulesList({
|
||||
keyword: trimmedCode,
|
||||
pageSize: 10,
|
||||
token
|
||||
});
|
||||
|
||||
if (existingRulesResponse.data && existingRulesResponse.data.rules.length > 0) {
|
||||
// 精确匹配检查(因为keyword是模糊搜索)
|
||||
const exactMatch = existingRulesResponse.data.rules.some(r => r.code === trimmedCode);
|
||||
if (exactMatch) {
|
||||
return { error: '评查点编码已存在,请使用其他编码', status: 409 };
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 验证分组ID有效性
|
||||
if (!ruleData.groupId) {
|
||||
return { error: '必须选择所属规则组', status: 400 };
|
||||
}
|
||||
|
||||
// 检查分组是否存在
|
||||
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('evaluation_point_groups', {
|
||||
filter: { 'id': `eq.${ruleData.groupId}` },
|
||||
select: 'id,name,pid',
|
||||
token
|
||||
});
|
||||
|
||||
let groupExists = false;
|
||||
if (groupResponse.data && 'code' in groupResponse.data && groupResponse.data.data) {
|
||||
groupExists = Array.isArray(groupResponse.data.data) && groupResponse.data.data.length > 0;
|
||||
} else if (Array.isArray(groupResponse.data)) {
|
||||
groupExists = groupResponse.data.length > 0;
|
||||
}
|
||||
|
||||
if (!groupExists) {
|
||||
return { error: '所选规则组不存在', status: 404 };
|
||||
}
|
||||
|
||||
// 将前端模型转换为API接受的格式
|
||||
const apiRuleData = {
|
||||
code: ruleData.code,
|
||||
name: ruleData.name,
|
||||
code: trimmedCode,
|
||||
name: trimmedName,
|
||||
evaluation_point_groups_id: parseInt(ruleData.groupId),
|
||||
risk: ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低',
|
||||
description: ruleData.description,
|
||||
is_enabled: ruleData.isActive,
|
||||
description: ruleData.description || '',
|
||||
is_enabled: ruleData.isActive !== undefined ? ruleData.isActive : true,
|
||||
// 以下是默认值,实际应用中需要根据业务逻辑设置
|
||||
references_laws: {},
|
||||
extraction_config: {
|
||||
@@ -534,27 +589,27 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
|
||||
post_action: "none",
|
||||
action_config: ""
|
||||
};
|
||||
|
||||
|
||||
// 使用postgrestPost创建评查点
|
||||
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('evaluation_points', apiRuleData, token);
|
||||
|
||||
|
||||
// 检查是否有错误响应
|
||||
if (response.error) {
|
||||
return { error: response.error, status: response.status };
|
||||
}
|
||||
|
||||
|
||||
// 确保响应数据存在且符合预期格式
|
||||
if (!response.data || !response.data.data) {
|
||||
return { error: '接口返回数据格式不正确', status: 500 };
|
||||
}
|
||||
|
||||
|
||||
// 将API返回的数据映射到前端模型
|
||||
const rule = mapApiRuleToFrontendModel(response.data.data);
|
||||
|
||||
|
||||
return { data: rule };
|
||||
} catch (error) {
|
||||
console.error('创建评查点出错:', error);
|
||||
return {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '创建评查点失败',
|
||||
status: 500
|
||||
};
|
||||
@@ -570,53 +625,112 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
|
||||
*/
|
||||
export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 1. 验证评查点ID有效性
|
||||
const existingRuleResponse = await getRule(id, token);
|
||||
if (existingRuleResponse.error || !existingRuleResponse.data) {
|
||||
return { error: '评查点不存在', status: 404 };
|
||||
}
|
||||
|
||||
// 2. 验证名称长度(如果提供)
|
||||
if (ruleData.name !== undefined) {
|
||||
const trimmedName = ruleData.name.trim();
|
||||
if (trimmedName.length === 0 || trimmedName.length > 100) {
|
||||
return { error: '评查点名称长度必须在1-100个字符之间', status: 400 };
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 验证编码格式和唯一性(如果提供)
|
||||
if (ruleData.code !== undefined) {
|
||||
const trimmedCode = ruleData.code.trim();
|
||||
|
||||
// 验证编码格式
|
||||
if (!/^[a-zA-Z0-9-_]+$/.test(trimmedCode)) {
|
||||
return { error: '评查点编码只能包含字母、数字、连字符和下划线', status: 400 };
|
||||
}
|
||||
|
||||
// 验证编码唯一性(排除自身)
|
||||
const existingRulesResponse = await getRulesList({
|
||||
keyword: trimmedCode,
|
||||
pageSize: 10,
|
||||
token
|
||||
});
|
||||
|
||||
if (existingRulesResponse.data && existingRulesResponse.data.rules.length > 0) {
|
||||
// 精确匹配检查,排除当前评查点自身
|
||||
const exactMatch = existingRulesResponse.data.rules.some(r => r.code === trimmedCode && r.id !== id);
|
||||
if (exactMatch) {
|
||||
return { error: '评查点编码已被其他评查点使用', status: 409 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 验证分组ID有效性(如果提供)
|
||||
if (ruleData.groupId !== undefined) {
|
||||
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('evaluation_point_groups', {
|
||||
filter: { 'id': `eq.${ruleData.groupId}` },
|
||||
select: 'id,name,pid',
|
||||
token
|
||||
});
|
||||
|
||||
let groupExists = false;
|
||||
if (groupResponse.data && 'code' in groupResponse.data && groupResponse.data.data) {
|
||||
groupExists = Array.isArray(groupResponse.data.data) && groupResponse.data.data.length > 0;
|
||||
} else if (Array.isArray(groupResponse.data)) {
|
||||
groupExists = groupResponse.data.length > 0;
|
||||
}
|
||||
|
||||
if (!groupExists) {
|
||||
return { error: '所选规则组不存在', status: 404 };
|
||||
}
|
||||
}
|
||||
|
||||
// 构建API接受的更新数据
|
||||
const apiRuleData: Record<string, unknown> = {};
|
||||
|
||||
|
||||
if (ruleData.code !== undefined) {
|
||||
apiRuleData.code = ruleData.code;
|
||||
apiRuleData.code = ruleData.code.trim();
|
||||
}
|
||||
|
||||
|
||||
if (ruleData.name !== undefined) {
|
||||
apiRuleData.name = ruleData.name;
|
||||
apiRuleData.name = ruleData.name.trim();
|
||||
}
|
||||
|
||||
|
||||
if (ruleData.groupId !== undefined) {
|
||||
apiRuleData.evaluation_point_groups_id = parseInt(ruleData.groupId);
|
||||
}
|
||||
|
||||
|
||||
if (ruleData.priority !== undefined) {
|
||||
apiRuleData.risk = ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低';
|
||||
}
|
||||
|
||||
|
||||
if (ruleData.description !== undefined) {
|
||||
apiRuleData.description = ruleData.description;
|
||||
}
|
||||
|
||||
|
||||
if (ruleData.isActive !== undefined) {
|
||||
apiRuleData.is_enabled = ruleData.isActive;
|
||||
}
|
||||
|
||||
|
||||
// 使用postgrestPut更新评查点
|
||||
const response = await postgrestPut<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>(`evaluation_points/${id}`, apiRuleData, undefined, token);
|
||||
|
||||
|
||||
// 检查是否有错误响应
|
||||
if (response.error) {
|
||||
return { error: response.error, status: response.status };
|
||||
}
|
||||
|
||||
|
||||
// 确保响应数据存在且符合预期格式
|
||||
if (!response.data || !response.data.data) {
|
||||
return { error: '接口返回数据格式不正确', status: 500 };
|
||||
}
|
||||
|
||||
|
||||
// 将API返回的数据映射到前端模型
|
||||
const rule = mapApiRuleToFrontendModel(response.data.data);
|
||||
|
||||
|
||||
return { data: rule };
|
||||
} catch (error) {
|
||||
console.error('更新评查点出错:', error);
|
||||
return {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '更新评查点失败',
|
||||
status: 500
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user