feat(evaluation): 模块1.3 - 增强评查点分组删除接口安全性
## 主要改进 ### 1. 删除策略优化(从级联删除改为阻止删除) - ✅ **安全优先**:采用阻止删除策略而非级联删除 - ✅ 删除前检查子分组,如有则拒绝删除 - ✅ 删除前检查关联评查点,如有则拒绝删除 - ✅ 只有空分组才能被删除 ### 2. 详细的删除检查 - ✅ ID有效性验证 - ✅ 分组存在性验证 - ✅ 子分组检查(仅一级分组) - ✅ 评查点关联检查(所有分组) - ✅ 返回详细的检查结果(childCount, pointCount) ### 3. 友好的错误提示 - ✅ 明确提示存在多少个子分组 - ✅ 明确提示存在多少个评查点 - ✅ 建议用户先清理关联数据 - ✅ 区分不同类型的删除失败原因 ### 4. 标记废弃函数 - ✅ deleteChildGroup 标记为 @deprecated - ✅ deleteEvaluationPointsByGroupId 标记为 @deprecated - ✅ 保留代码以备将来批量删除功能使用 ## 删除策略对比 ### 旧策略(级联删除)- 高风险 ❌ 删除一级分组时自动删除所有子分组 ❌ 自动删除所有关联的评查点 ❌ 用户可能误删大量数据 ❌ 无法恢复 ### 新策略(阻止删除)- 安全 ✅ 拒绝删除有子分组的一级分组 ✅ 拒绝删除有评查点的分组 ✅ 用户必须手动清理关联数据 ✅ 防止误删除 ✅ 提供清晰的错误提示 ## 返回值增强 ```typescript { success: boolean; error?: string; details?: { hasChildren: boolean; // 是否有子分组 hasPoints: boolean; // 是否有评查点 childCount?: number; // 子分组数量 pointCount?: number; // 评查点数量 } } ``` ## 相关文件 - app/api/evaluation_points/rule-groups.ts ## 验收清单 - [x] TypeScript 类型检查通过 - [x] 删除前完整的关联检查 - [x] 阻止删除有依赖的分组 - [x] 详细的错误提示 - [x] 返回详细的检查结果 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -740,50 +740,114 @@ export async function updateRuleGroup(id: string, data: RuleGroupCreateUpdateDto
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评查点分组
|
||||
* 删除评查点分组(增强版 - 安全的阻止删除策略)
|
||||
*
|
||||
* 删除策略:
|
||||
* - 如果分组下有子分组,拒绝删除,提示用户先删除子分组
|
||||
* - 如果分组下有评查点,拒绝删除,提示用户先删除或移动评查点
|
||||
* - 只有空分组才能被删除
|
||||
*
|
||||
* @param id 分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
*/
|
||||
export async function deleteRuleGroup(id: string, token?: string): Promise<{success: boolean; error?: string}> {
|
||||
export async function deleteRuleGroup(id: string, token?: string): Promise<{success: boolean; error?: string; details?: { hasChildren: boolean; hasPoints: boolean; childCount?: number; pointCount?: number }}> {
|
||||
try {
|
||||
// 1. 首先获取分组信息,判断是一级还是二级分组
|
||||
// ========== 1. ID验证 ==========
|
||||
|
||||
if (!id) {
|
||||
return { success: false, error: '分组ID不能为空' };
|
||||
}
|
||||
|
||||
// 验证分组是否存在
|
||||
const groupResponse = await getRuleGroup(id, token);
|
||||
if (groupResponse.error) {
|
||||
return { success: false, error: groupResponse.error };
|
||||
if (groupResponse.error || !groupResponse.data) {
|
||||
return { success: false, error: '分组不存在或无法访问' };
|
||||
}
|
||||
|
||||
const group = groupResponse.data;
|
||||
if (!group) {
|
||||
return { success: false, error: '未找到指定分组' };
|
||||
}
|
||||
|
||||
// 2. 如果是一级分组(顶级分组,pid为NULL或'0'),需要先删除所有子分组
|
||||
if (!group.pid || group.pid === '0' || group.pid === null) {
|
||||
// 获取所有子分组
|
||||
// ========== 2. 检查是否有子分组(一级分组专用) ==========
|
||||
|
||||
let hasChildren = false;
|
||||
let childCount = 0;
|
||||
|
||||
// 如果是一级分组,检查是否有子分组
|
||||
if (!group.pid || group.pid === '0') {
|
||||
const childGroupsResponse = await getChildGroups(id, token);
|
||||
|
||||
if (childGroupsResponse.error) {
|
||||
return { success: false, error: childGroupsResponse.error };
|
||||
return {
|
||||
success: false,
|
||||
error: `检查子分组时出错: ${childGroupsResponse.error}`
|
||||
};
|
||||
}
|
||||
|
||||
const childGroups = childGroupsResponse.data || [];
|
||||
|
||||
// 遍历删除每个子分组
|
||||
for (const childGroup of childGroups) {
|
||||
const deleteChildResult = await deleteChildGroup(childGroup.id, token);
|
||||
if (!deleteChildResult.success) {
|
||||
return deleteChildResult;
|
||||
}
|
||||
childCount = childGroups.length;
|
||||
hasChildren = childCount > 0;
|
||||
|
||||
if (hasChildren) {
|
||||
return {
|
||||
success: false,
|
||||
error: `该分组下存在 ${childCount} 个子分组,请先删除所有子分组后再删除此分组。`,
|
||||
details: {
|
||||
hasChildren: true,
|
||||
hasPoints: false,
|
||||
childCount
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 删除分组下的所有评查点
|
||||
const deletePointsResult = await deleteEvaluationPointsByGroupId(id, token);
|
||||
if (!deletePointsResult.success) {
|
||||
return deletePointsResult;
|
||||
// ========== 3. 检查是否有关联的评查点 ==========
|
||||
|
||||
const pointsParams: PostgrestParams = {
|
||||
select: 'id',
|
||||
filter: {
|
||||
'evaluation_point_groups_id': `eq.${id}`
|
||||
},
|
||||
limit: 1, // 只需要知道是否存在,不需要获取所有数据
|
||||
token
|
||||
};
|
||||
|
||||
const pointsResponse = await postgrestGet<ApiResponse<Array<{id: number}>>>(
|
||||
'evaluation_points',
|
||||
pointsParams
|
||||
);
|
||||
|
||||
let hasPoints = false;
|
||||
let pointCount = group.ruleCount || 0;
|
||||
|
||||
if (pointsResponse.error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `检查关联评查点时出错: ${pointsResponse.error}`
|
||||
};
|
||||
}
|
||||
|
||||
// 4. 最后删除分组本身
|
||||
if (pointsResponse.data) {
|
||||
if ('code' in pointsResponse.data && pointsResponse.data.data) {
|
||||
hasPoints = Array.isArray(pointsResponse.data.data) && pointsResponse.data.data.length > 0;
|
||||
} else if (Array.isArray(pointsResponse.data)) {
|
||||
hasPoints = pointsResponse.data.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasPoints) {
|
||||
return {
|
||||
success: false,
|
||||
error: `该分组下存在 ${pointCount} 个评查点,请先删除或移动所有评查点后再删除此分组。`,
|
||||
details: {
|
||||
hasChildren: false,
|
||||
hasPoints: true,
|
||||
pointCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ========== 4. 执行删除操作 ==========
|
||||
|
||||
const response = await postgrestDelete<ApiResponse<{id: number}>>('evaluation_point_groups', {
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
@@ -792,21 +856,29 @@ export async function deleteRuleGroup(id: string, token?: string): Promise<{succ
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
return { success: false, error: response.error };
|
||||
return { success: false, error: `删除失败: ${response.error}` };
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
return {
|
||||
success: true,
|
||||
details: {
|
||||
hasChildren: false,
|
||||
hasPoints: false
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('删除评查点分组失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '删除评查点分组失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除子分组及其相关数据
|
||||
* 删除子分组及其相关数据(级联删除)
|
||||
*
|
||||
* @deprecated 当前采用阻止删除策略,此函数暂不使用
|
||||
* @param id 子分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
@@ -842,7 +914,9 @@ async function deleteChildGroup(id: string, token?: string): Promise<{success: b
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定分组下的所有评查点
|
||||
* 删除指定分组下的所有评查点(级联删除)
|
||||
*
|
||||
* @deprecated 当前采用阻止删除策略,此函数暂不使用
|
||||
* @param groupId 分组ID
|
||||
* @param token JWT token (可选)
|
||||
* @returns 删除结果
|
||||
|
||||
Reference in New Issue
Block a user