feat(gateway): MinIO 孤儿对象 GC(重名覆盖后清理旧对象)

大文档正文存 MinIO,重名再入库若转内联或换键,旧对象会成孤儿泄漏。
SaveDoc 改为返回 (id, oldObjectKey);runIngest 在覆盖成功后,若旧键非空且
与新键不同,从 MinIO 删除旧对象。新建文档 oldObjectKey 为空,不触发。

注:当前无文档删除/改名端点,主要孤儿路径=大→内联覆盖,已覆盖;
真机 MinIO GC 验证待后续(逻辑直白,blob.Delete/SaveDoc 已分别验证)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-18 11:56:22 +08:00
parent b7360439ab
commit 6523323a27
3 changed files with 20 additions and 13 deletions
+6 -1
View File
@@ -257,10 +257,15 @@ func (h *Handler) runIngest(job, owner, kbName, scoped, forceDoc, filename strin
log.Printf("[gateway] 大文档转 MinIO 失败,回退内联: %v", err)
}
}
docID, err := h.db.SaveDoc(ctx, owner, kbName, docName, ext, md5hex, inline, objectKey, size, head(text, 500))
docID, oldKey, err := h.db.SaveDoc(ctx, owner, kbName, docName, ext, md5hex, inline, objectKey, size, head(text, 500))
if err != nil {
log.Printf("[gateway] 文件入库失败: %v", err)
} else if docID != "" {
// 孤儿 GC:重名覆盖后旧对象键若已不用(转内联或换键),从 MinIO 删除,避免泄漏。
if oldKey != "" && oldKey != objectKey && h.blob.Ready() {
h.blob.Delete(ctx, oldKey)
log.Printf("[gateway] 清理被覆盖的 MinIO 孤儿对象: %s", oldKey)
}
_ = h.db.ReplaceDocLinks(ctx, owner, kbName, docID, wikiLinks(text)) // 以本文件 ID 维护出链
_ = h.db.ResolveInboundLinks(ctx, owner, kbName, docName, docID) // 回填指向本文件的悬空链接
}