refactor(kb): 文件主表 + 文档关联改用雪花ID(弃用按名关联)
把 sundynix_doc 明确为"文件主表",补齐文件基础信息字段; 文档间 [[双链]] 改为以 Doc.ID 关联,查询/渲染一律按文件 ID。 store: - Doc 增 Ext(后缀)/MD5(内容指纹) 字段;ObjectKey 即"存放链接" - DocLink 由 (FromName,ToName) 改为 (FromID,ToID,ToName) · FromID/ToID 关联 Doc.ID;ToName 保留用于悬空链接展示与回填 - SaveDoc 返回新建/更新文件的雪花 ID(供建链) - 新增 GetDocByID(按 ID + owner 取正文,防越权) - ReplaceDocLinks 以 fromID 重建出链,按 [[名称]] 解析目标 ID - 新增 ResolveInboundLinks:目标入库后回填指向它的悬空链接 - ListLinks 只返回已解析(to_id 非空)的 ID→ID 边 - migrateDocLinkToID:旧按名双链表无 from_id 列则重建为按 ID 关联 gateway/handler: - runIngest 计算 ext/md5,SaveDoc 取回 ID 后建链 + 回填悬空 - KbDoc 改为 GET ?id=(按文件 ID 取全文) - KbVault 返回 id+ext;KbLinks 返回 from/to 为 ID desktop: - VaultDoc 增 id/ext;getDoc(docId) 按 ID 取正文 - VaultPanel 选中态/正文/反链/关系图改用 ID,名↔ID 双向映射 渲染;保存笔记后按名定位回其新 ID 验证(gateway+PG+MinIO 实测):vault 带 id+ext;双链 ID→ID 且 A→B 悬空链接在 B 入库后成功回填;按 ID 取大文档(15006字)从 MinIO 完整取回;跨 owner 按 ID 取文档 404(隔离生效)。桌面端 文库 Tab 按 ID 选中/载入/反链渲染正常,无控制台报错。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
@@ -131,14 +132,14 @@ func (h *Handler) KbVault(c *gin.Context) {
|
||||
}
|
||||
docs := make([]gin.H, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
docs = append(docs, gin.H{"name": r.Name, "size": r.Size, "preview": r.Preview})
|
||||
docs = append(docs, gin.H{"id": r.ID, "name": r.Name, "ext": r.Ext, "size": r.Size, "preview": r.Preview})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"docs": docs})
|
||||
}
|
||||
|
||||
// KbDoc: GET /api/v1/kb/doc?kb=&name= —— 取单篇文档全文(按需加载,不在列表里拉全量)。
|
||||
// KbDoc: GET /api/v1/kb/doc?id= —— 按文件 ID 取单篇全文(按需加载,不在列表里拉全量)。
|
||||
func (h *Handler) KbDoc(c *gin.Context) {
|
||||
d, err := h.db.GetDoc(c.Request.Context(), userID(c), rawKB(c.Query("kb")), c.Query("name"))
|
||||
d, err := h.db.GetDocByID(c.Request.Context(), userID(c), c.Query("id"))
|
||||
if err != nil || d == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "文档不存在"})
|
||||
return
|
||||
@@ -149,10 +150,10 @@ func (h *Handler) KbDoc(c *gin.Context) {
|
||||
content = obj
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"name": d.Name, "content": content, "size": d.Size})
|
||||
c.JSON(http.StatusOK, gin.H{"id": d.ID, "name": d.Name, "ext": d.Ext, "content": content, "size": d.Size})
|
||||
}
|
||||
|
||||
// KbLinks: GET /api/v1/kb/links?kb= —— 某库全部 [[双链]](from→to),供反链/笔记关系图。
|
||||
// KbLinks: GET /api/v1/kb/links?kb= —— 某库已解析的 [[双链]](FromID→ToID),供反链/笔记关系图按 ID 渲染。
|
||||
func (h *Handler) KbLinks(c *gin.Context) {
|
||||
rows, err := h.db.ListLinks(c.Request.Context(), userID(c), rawKB(c.Query("kb")))
|
||||
if err != nil {
|
||||
@@ -161,7 +162,7 @@ func (h *Handler) KbLinks(c *gin.Context) {
|
||||
}
|
||||
links := make([]gin.H, 0, len(rows))
|
||||
for _, l := range rows {
|
||||
links = append(links, gin.H{"from": l.FromName, "to": l.ToName})
|
||||
links = append(links, gin.H{"from": l.FromID, "to": l.ToID})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"links": links})
|
||||
}
|
||||
@@ -243,6 +244,9 @@ func (h *Handler) runIngest(job, owner, kbName, scoped, forceDoc, filename strin
|
||||
}
|
||||
if text != "" {
|
||||
size := len([]rune(text))
|
||||
ext := strings.ToLower(filepath.Ext(filename)) // 笔记/文本入库时 filename 为空 → ext 为空
|
||||
sum := md5.Sum([]byte(text))
|
||||
md5hex := hex.EncodeToString(sum[:])
|
||||
inline, objectKey := text, ""
|
||||
// 大文档正文落对象存储,PG 只留元数据+预览+对象键(避免把十几万字塞进 PG)。
|
||||
if size > docInlineMax && h.blob.Ready() {
|
||||
@@ -253,8 +257,13 @@ func (h *Handler) runIngest(job, owner, kbName, scoped, forceDoc, filename strin
|
||||
log.Printf("[gateway] 大文档转 MinIO 失败,回退内联: %v", err)
|
||||
}
|
||||
}
|
||||
_ = h.db.SaveDoc(ctx, owner, kbName, docName, inline, objectKey, size, head(text, 500))
|
||||
_ = h.db.ReplaceDocLinks(ctx, owner, kbName, docName, wikiLinks(text)) // 维护 [[双链]] 索引
|
||||
docID, 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 != "" {
|
||||
_ = h.db.ReplaceDocLinks(ctx, owner, kbName, docID, wikiLinks(text)) // 以本文件 ID 维护出链
|
||||
_ = h.db.ResolveInboundLinks(ctx, owner, kbName, docName, docID) // 回填指向本文件的悬空链接
|
||||
}
|
||||
}
|
||||
|
||||
// 调 mcp-go kb_ingest(带 job_id):它会发 切块/向量化/写入/完成 事件 + CompleteStream。
|
||||
|
||||
Reference in New Issue
Block a user