fix(aihr): clean up orphaned knowledge uploads

This commit is contained in:
2026-07-14 21:43:27 +08:00
parent 30a473b365
commit 04b9b1bb96
4 changed files with 43 additions and 6 deletions
@@ -763,9 +763,14 @@ public class AihrSopSeedService {
docId = reuseDuplicateAttach(hit, config.knowledgeId(), fileName);
remark = "duplicate:" + hit.matchType() + ":" + insight.classifiedBy();
} else {
SysOssVo oss = ossUploader.get();
ossId = oss.getOssId();
docId = upsertAttach(config.knowledgeId(), ossId, fileName);
SysOssVo uploadedOss = ossUploader.get();
ossId = uploadedOss.getOssId();
try {
docId = upsertAttach(config.knowledgeId(), ossId, fileName);
} catch (RuntimeException error) {
deleteUploadedOssQuietly(uploadedOss);
throw error;
}
remark = "oss-upload:" + insight.classifiedBy();
}
try {
@@ -804,8 +809,14 @@ public class AihrSopSeedService {
String defaultName = video ? "培训视频" : "图片素材";
String knowledgeName = isAutoCategory(category) || category == null || category.isBlank() ? defaultName : cleanCategory(category);
KnowledgeConfig config = knowledgeConfig(knowledgeName);
SysOssVo oss = ossUploader.get();
String docId = upsertAttach(config.knowledgeId(), oss.getOssId(), fileName);
SysOssVo uploadedOss = ossUploader.get();
String docId;
try {
docId = upsertAttach(config.knowledgeId(), uploadedOss.getOssId(), fileName);
} catch (RuntimeException error) {
deleteUploadedOssQuietly(uploadedOss);
throw error;
}
markAttachStatus(config.knowledgeId(), docId, 0, video ? "pending-transcribe" : "pending-ocr");
String hint;
if (video) {
@@ -816,7 +827,18 @@ public class AihrSopSeedService {
: "未配置可用视觉模型,图片已入库标记待处理;在模型管理启用 vision/多模态 chat 模型后重新上传即可解析";
}
List<String> tags = video ? List.of("视频", "待转写") : List.of("图片", "待OCR");
return new UploadResponse(docId, oss.getOssId(), fileName, knowledgeName, 0, hint, tags, List.of());
return new UploadResponse(docId, uploadedOss.getOssId(), fileName, knowledgeName, 0, hint, tags, List.of());
}
private void deleteUploadedOssQuietly(SysOssVo oss) {
if (oss == null || oss.getOssId() == null || ossService == null) {
return;
}
try {
ossService.deleteWithValidByIds(List.of(oss.getOssId()), false);
} catch (Exception cleanupError) {
log.warn("knowledge document OSS cleanup failed ossId={}", oss.getOssId(), cleanupError);
}
}
private DocumentInsight documentInsight(String fileName, String content, String requestedCategory) {
@@ -143,6 +143,19 @@ public class AihrSopSeedServiceTest {
assertTrue(code.contains("!modelService.visionAllowed()"));
}
@Test
@Tag("dev")
public void documentUploadCleansNewOssWhenAttachWriteFails() throws Exception {
Path source = Path.of("src/main/java/org/dromara/aihr/service/AihrSopSeedService.java");
if (!Files.exists(source)) {
source = Path.of("ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java");
}
String code = Files.readString(source);
assertTrue(code.contains("deleteUploadedOssQuietly(uploadedOss)"));
assertTrue(code.contains("private void deleteUploadedOssQuietly(SysOssVo oss)"));
}
@Test
@Tag("dev")
public void disabledVisionGateStopsBeforeDatabaseLookup() throws Exception {
+1
View File
@@ -1,5 +1,6 @@
# BRD 功能审查与未完成项记录
- 2026-07-14 BRD G3 SOP 附件绑定补偿:同步/待处理图片文档此前在 `sys_oss` 上传成功后再写 `aihr_knowledge_attach`,附件写入失败时可能留下无业务引用对象;现仅对“附件尚未成功绑定”的异常路径做 OSS 补偿删除,解析失败后已存在的失败/待处理附件仍保留用于重试,不改变现有文档处理状态机。
- 2026-07-14 BRD G3 OSS 生命周期补偿:候选资料和案例音频此前先写 `sys_oss`、再写业务记录,业务数据库写入失败时可能留下没有业务引用的对象;现对“本次上传新建且尚未绑定”的 `oss_id` 增加失败补偿删除,案例同时移除内存态,补充两条回归测试和 `demo-check` marker。该修复不触碰已有业务资源;SOP 分片/ASR 等复杂异步链路仍需按各自状态机单独核查,不能用本次补偿覆盖全部媒体生命周期。
- 2026-07-14 BRD G3 文件名边界复核:候选资料、案例音频、同步 SOP 文档和 ASR 音频此前虽然分别校验或清理文件名,但仍有路径把原始 `MultipartFile` 直接交给 `sys_oss`,清理后的名字没有贯通到 OSS 元数据;现新增共用 `AihrMultipartFiles` 包装器,四条链路均以清理后的文件名上传/外发,并补单测与 `demo-check` marker。该修复不改变文件类型、大小、权限和业务状态,仍需正式环境抽查 OSS 原始文件名与下载响应头。
+1
View File
@@ -631,6 +631,7 @@ contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "AihrMultipartFiles.withOriginalFilename"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrCaseService.java "cleanupFailedUpload"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "AihrMultipartFiles.withOriginalFilename"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "deleteUploadedOssQuietly"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/controller/AihrSpeechController.java "AihrMultipartFiles.withOriginalFilename"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "o.tenant_id = a.tenant_id"
contains backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java "where tenant_id = ? and oss_id = ? limit 1"