Ruoyi-AI知识库文件删除异常问题分析与修复方案
·
Ruoyi-AI知识库文件删除异常问题分析与修复方案
问题背景
在RuoYi AI平台的知识库管理模块中,用户反馈在删除知识库文件时出现异常情况。具体表现为:删除操作表面上成功,但实际上相关数据并未完全清理,导致后续操作中出现数据不一致和系统错误。
问题分析
1. 当前删除逻辑分析
通过分析代码发现,知识库文件删除操作主要涉及以下核心方法:
@Override
public void removeKnowledgeAttach(String docId) {
Map<String, Object> map = new HashMap<>();
map.put("doc_id", docId);
baseMapper.deleteByMap(map);
fragmentMapper.deleteByMap(map);
}
该方法通过doc_id字段同时删除knowledge_attach表和knowledge_fragment表中的相关记录。
2. 潜在问题点
2.1 事务一致性缺失
当前实现缺乏事务管理,如果在删除片段表记录时发生异常,会导致数据不一致。
2.2 外键约束处理不足
系统可能存在以下外键关系:
| 表名 | 字段 | 关联表 | 关联字段 |
|---|---|---|---|
| knowledge_attach | kid | knowledge_info | id |
| knowledge_fragment | doc_id | knowledge_attach | doc_id |
2.3 异常处理机制不完善
当前代码没有对删除操作进行异常捕获和处理,可能导致:
- 数据库连接异常
- 并发操作冲突
- 数据不存在时的处理
解决方案
1. 事务管理增强
@Transactional(rollbackFor = Exception.class)
@Override
public void removeKnowledgeAttach(String docId) {
try {
// 验证数据存在性
LambdaQueryWrapper<KnowledgeAttach> attachQuery = Wrappers.lambdaQuery();
attachQuery.eq(KnowledgeAttach::getDocId, docId);
Long attachCount = baseMapper.selectCount(attachQuery);
LambdaQueryWrapper<KnowledgeFragment> fragmentQuery = Wrappers.lambdaQuery();
fragmentQuery.eq(KnowledgeFragment::getDocId, docId);
Long fragmentCount = fragmentMapper.selectCount(fragmentQuery);
if (attachCount == 0 && fragmentCount == 0) {
log.warn("尝试删除不存在的文档: {}", docId);
return;
}
// 执行删除操作
Map<String, Object> map = new HashMap<>();
map.put("doc_id", docId);
int attachDeleted = baseMapper.deleteByMap(map);
int fragmentDeleted = fragmentMapper.deleteByMap(map);
log.info("成功删除文档 {}: 附件记录 {} 条, 片段记录 {} 条",
docId, attachDeleted, fragmentDeleted);
} catch (Exception e) {
log.error("删除知识库文档失败: {}", docId, e);
throw new BusinessException("删除知识库文档失败: " + e.getMessage());
}
}
2. 级联删除策略
/**
* 知识库文档删除状态机
*/
stateDiagram-v2
[*] --> 验证权限
验证权限 --> 检查依赖
检查依赖 --> 执行删除
执行删除 --> 清理资源
清理资源 --> [*]
验证权限 --> 权限不足: 无权限
检查依赖 --> 存在依赖: 有外键约束
执行删除 --> 删除失败: 数据库异常
权限不足 --> [*]
存在依赖 --> 处理依赖
处理依赖 --> 执行删除
删除失败 --> 回滚操作
回滚操作 --> [*]
3. 完整的修复方案
3.1 服务层增强
@Service
@RequiredArgsConstructor
@Slf4j
public class KnowledgeAttachServiceImpl implements IKnowledgeAttachService {
private final KnowledgeAttachMapper baseMapper;
private final KnowledgeFragmentMapper fragmentMapper;
private final KnowledgeInfoMapper knowledgeInfoMapper;
@Transactional(rollbackFor = Exception.class)
@Override
public void removeKnowledgeAttach(String docId) {
removeKnowledgeAttachWithValidation(docId, true);
}
/**
* 带验证的知识库文档删除
*/
@Transactional(rollbackFor = Exception.class)
public void removeKnowledgeAttachWithValidation(String docId, boolean validateDependencies) {
try {
// 1. 数据存在性验证
KnowledgeAttach attach = getAttachByDocId(docId);
if (attach == null) {
log.warn("文档不存在: {}", docId);
return;
}
// 2. 业务规则验证
if (validateDependencies) {
validateDeleteConditions(attach);
}
// 3. 执行删除操作
deleteAttachAndFragments(docId);
// 4. 后续清理操作
postDeleteCleanup(attach);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("删除知识库文档异常: {}", docId, e);
throw new BusinessException("删除操作失败: " + e.getMessage());
}
}
private KnowledgeAttach getAttachByDocId(String docId) {
LambdaQueryWrapper<KnowledgeAttach> query = Wrappers.lambdaQuery();
query.eq(KnowledgeAttach::getDocId, docId);
return baseMapper.selectOne(query);
}
private void validateDeleteConditions(KnowledgeAttach attach) {
// 检查是否允许删除的业务规则
if ("protected".equals(attach.getStatus())) {
throw new BusinessException("受保护的文档不允许删除");
}
}
private void deleteAttachAndFragments(String docId) {
Map<String, Object> condition = new HashMap<>();
condition.put("doc_id", docId);
int fragmentDeleted = fragmentMapper.deleteByMap(condition);
int attachDeleted = baseMapper.deleteByMap(condition);
if (attachDeleted == 0) {
throw new BusinessException("删除附件记录失败");
}
log.debug("删除完成 - 附件: {}, 片段: {}", attachDeleted, fragmentDeleted);
}
private void postDeleteCleanup(KnowledgeAttach attach) {
// 可扩展的后续清理操作
// 如:清理缓存、通知其他服务等
}
}
3.2 控制器层优化
@PostMapping("attach/remove/{kid}")
public R<Void> removeAttach(@NotEmpty(message = "主键不能为空")
@PathVariable String kid) {
try {
attachService.removeKnowledgeAttach(kid);
return R.ok("删除成功");
} catch (BusinessException e) {
return R.fail(e.getMessage());
} catch (Exception e) {
log.error("删除知识库附件异常", e);
return R.fail("系统异常,删除失败");
}
}
4. 预防措施
4.1 数据库约束优化
-- 添加外键约束(如果尚未存在)
ALTER TABLE knowledge_fragment
ADD CONSTRAINT fk_fragment_attach
FOREIGN KEY (doc_id)
REFERENCES knowledge_attach(doc_id)
ON DELETE CASCADE;
4.2 监控和日志
// 添加详细的监控指标
@Aspect
@Component
@Slf4j
public class KnowledgeDeleteMonitor {
@Around("execution(* org.ruoyi.service.IKnowledgeAttachService.removeKnowledgeAttach(..))")
public Object monitorDeleteOperation(ProceedingJoinPoint joinPoint) throws Throwable {
String docId = (String) joinPoint.getArgs()[0];
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
log.info("知识库删除操作成功 - DocID: {}, 耗时: {}ms", docId, duration);
Metrics.counter("knowledge.delete.success").increment();
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
log.error("知识库删除操作失败 - DocID: {}, 耗时: {}ms, 错误: {}",
docId, duration, e.getMessage());
Metrics.counter("knowledge.delete.failure").increment();
throw e;
}
}
}
测试方案
1. 单元测试用例
@Test
public void testRemoveKnowledgeAttach_Success() {
// 准备测试数据
String docId = "test-doc-001";
prepareTestData(docId);
// 执行删除操作
attachService.removeKnowledgeAttach(docId);
// 验证结果
assertFalse(attachExists(docId));
assertFalse(fragmentExists(docId));
}
@Test
public void testRemoveKnowledgeAttach_NotExists() {
String nonExistingDocId = "non-existing-doc";
// 执行删除操作,应该不会抛出异常
assertDoesNotThrow(() -> {
attachService.removeKnowledgeAttach(nonExistingDocId);
});
}
@Test
public void testRemoveKnowledgeAttach_Concurrent() {
String docId = "concurrent-doc";
prepareTestData(docId);
// 并发删除测试
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Callable<Boolean>> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
tasks.add(() -> {
try {
attachService.removeKnowledgeAttach(docId);
return true;
} catch (Exception e) {
return false;
}
});
}
List<Future<Boolean>> results = executor.invokeAll(tasks);
// 应该只有一个线程成功执行删除
long successCount = results.stream().filter(f -> {
try { return f.get(); }
catch (Exception e) { return false; }
}).count();
assertEquals(1, successCount);
}
2. 集成测试场景
| 测试场景 | 预期结果 | 验证点 |
|---|---|---|
| 正常删除存在的文档 | 成功删除 | 附件和片段记录都被删除 |
| 删除不存在的文档 | 静默成功 | 无异常抛出 |
| 并发删除同一文档 | 只有一个成功 | 数据一致性保持 |
| 删除受保护的文档 | 操作失败 | 抛出业务异常 |
| 数据库连接异常 | 操作失败 | 事务回滚,数据不变 |
总结
通过本次问题分析和修复,我们实现了:
- 事务一致性:确保删除操作的原子性
- 异常处理:完善的错误处理和日志记录
- 业务验证:增加删除前的业务规则检查
- 监控能力:添加操作监控和性能指标
- 测试覆盖:全面的单元测试和集成测试
该修复方案不仅解决了当前的删除异常问题,还为知识库模块的稳定性和可维护性提供了坚实基础。建议在后续开发中继续加强事务管理、异常处理和监控告警机制的建设。
更多推荐



所有评论(0)