feat(gateway): 入库队列接线 + 正文一律MinIO + file_id级联删端点
- 入库改走 JetStream 队列:claim-check 暂存 MinIO → 发作业 → 立即返 job_id; worker 池(StartIngestWorkers)有界并发消费,崩溃重投续跑(幂等),优雅 drain - 存储:正文一律落 MinIO(去 <8000字内联PG 阈值,仅 MinIO 挂时回退兜底); sundynix_doc 删死字段 MD5 - 下游键传稳定 file_id(非展示名);新增 DELETE /api/v1/kb/doc 级联删 (三库 + MinIO 原文 + PG 元数据/双链,owner 隔离) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -95,11 +95,10 @@ type Doc struct {
|
||||
KB string `gorm:"size:64;uniqueIndex:idx_doc_okn"`
|
||||
Name string `gorm:"size:160;uniqueIndex:idx_doc_okn"` // 文件名称(不含扩展名 / 笔记名)
|
||||
Ext string `gorm:"size:16"` // 文件后缀(.md/.pdf/.docx…;笔记/文本为空)
|
||||
MD5 string `gorm:"size:32;index"` // 正文内容指纹(去重 / 校验)
|
||||
Size int // 原文字数(rune)
|
||||
Preview string `gorm:"size:600"` // 前若干字预览(列表/反链用,不拉全文)
|
||||
Content string `gorm:"type:text"` // 小文档内联正文;大文档转 MinIO 后置空
|
||||
ObjectKey string `gorm:"size:160"` // 存放链接:大文档在 MinIO 的对象键(空=内联 Content)
|
||||
Content string `gorm:"type:text"` // 正文一律落 MinIO 后置空;仅 MinIO 不可用时回退内联存这里
|
||||
ObjectKey string `gorm:"size:160"` // 正文在 MinIO 的对象键(常态非空;空=回退内联 Content)
|
||||
}
|
||||
|
||||
func (Doc) TableName() string { return "sundynix_doc" }
|
||||
@@ -120,14 +119,14 @@ func (DocLink) TableName() string { return "sundynix_doc_link" }
|
||||
// SaveDoc 写入/更新一份文件(owner+kb+name 唯一,重名覆盖),返回 (文件雪花 ID, 被覆盖的旧对象键)。
|
||||
// oldObjectKey 是重名覆盖前该文档的 ObjectKey(新建则为空)—— 供调用方清理 MinIO 孤儿对象。
|
||||
// content 为内联正文(大文档转 MinIO 时传空 + objectKey);ext/md5/preview/size 由调用方按全文给出。
|
||||
func (p *Postgres) SaveDoc(ctx context.Context, owner, kb, name, ext, md5, content, objectKey string, size int, preview string) (id, oldObjectKey string, err error) {
|
||||
func (p *Postgres) SaveDoc(ctx context.Context, owner, kb, name, ext, content, objectKey string, size int, preview string) (id, oldObjectKey string, err error) {
|
||||
if p.db == nil {
|
||||
return "", "", nil
|
||||
}
|
||||
var d Doc
|
||||
qerr := p.db.WithContext(ctx).Where("owner = ? AND kb = ? AND name = ?", owner, kb, name).First(&d).Error
|
||||
if errors.Is(qerr, gorm.ErrRecordNotFound) {
|
||||
d = Doc{Owner: owner, KB: kb, Name: name, Ext: ext, MD5: md5, Content: content, ObjectKey: objectKey, Size: size, Preview: preview}
|
||||
d = Doc{Owner: owner, KB: kb, Name: name, Ext: ext, Content: content, ObjectKey: objectKey, Size: size, Preview: preview}
|
||||
if err := p.db.WithContext(ctx).Create(&d).Error; err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -137,7 +136,7 @@ func (p *Postgres) SaveDoc(ctx context.Context, owner, kb, name, ext, md5, conte
|
||||
return "", "", qerr
|
||||
}
|
||||
oldObjectKey = d.ObjectKey // 覆盖前的旧对象键
|
||||
d.Ext, d.MD5, d.Content, d.ObjectKey, d.Size, d.Preview = ext, md5, content, objectKey, size, preview
|
||||
d.Ext, d.Content, d.ObjectKey, d.Size, d.Preview = ext, content, objectKey, size, preview
|
||||
if err := p.db.WithContext(ctx).Save(&d).Error; err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@@ -168,6 +167,20 @@ func (p *Postgres) GetDocByID(ctx context.Context, owner, id string) (*Doc, erro
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// DeleteDocByID 删除一份文件的 PG 痕迹(owner 作用域防越权):doc 行 + 其出链/入链。
|
||||
// 入链(别处指向本文件)一并删,避免悬空脏链。文档不存在视作已删(返回 nil)。
|
||||
func (p *Postgres) DeleteDocByID(ctx context.Context, owner, kb, id string) error {
|
||||
if p.db == nil {
|
||||
return nil
|
||||
}
|
||||
return p.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("owner = ? AND kb = ? AND (from_id = ? OR to_id = ?)", owner, kb, id, id).Delete(&DocLink{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Where("owner = ? AND id = ?", owner, id).Delete(&Doc{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// ReplaceDocLinks 以源文件 ID 重建其出链(先删旧,再按 [[名称]] 解析目标 ID 后插新)—— 入库/编辑时调用。
|
||||
// 目标文档尚未入库时 ToID 留空(悬空),待其入库时由 ResolveInboundLinks 回填。
|
||||
func (p *Postgres) ReplaceDocLinks(ctx context.Context, owner, kb, fromID string, toNames []string) error {
|
||||
|
||||
Reference in New Issue
Block a user