feat: align with tableDB updates, automate ID/metadata, and refine GetDB signature (by AI)

This commit is contained in:
AI Engineer 2026-05-16 00:09:36 +08:00
parent 081a362b97
commit cf01dcf464
4 changed files with 64 additions and 40 deletions

View File

@ -26,14 +26,15 @@ go get apigo.cc/go/docDB
```go ```go
import ( import (
"apigo.cc/go/docDB" "apigo.cc/go/docDB"
"apigo.cc/go/tableDB" "apigo.cc/go/log"
) )
// 创建基础数据库连接 (tableDB 层) // 获取 docDB 引擎 (自动完成系统表与业务表初始化)
unauthorizedDB := tableDB.GetDB("sqlite://docs.db", logger) // dsn: 数据库连接字符串 (如 sqlite://docs.db)
// logger: 日志对象
// 获取 docDB 引擎 (自动完成表结构初始化) // redis: 分布式 ID 用的 Redis 地址 (可选)
engine := docDB.GetDB(unauthorizedDB, "./storage_root") // baseDir: 物理文件存储根路径
engine := docDB.GetDB("sqlite://docs.db", log.DefaultLogger, "localhost:6379", "./storage_root")
// 授权并获取操作句柄 // 授权并获取操作句柄
db := engine.Auth("user_123") db := engine.Auth("user_123")
@ -57,10 +58,25 @@ db := engine.Auth("user_123")
### 3. 生命周期钩子 (Hooks) ### 3. 生命周期钩子 (Hooks)
```go ```go
engine.Hooks.OnCreatedDoc = func(doc *docDB.Document) { /* ... */ } // 监听新文档创建
engine.Hooks.OnUpdatedDoc = func(doc *docDB.Document) { /* ... */ } engine.Hooks.OnCreatedDoc = func(doc *docDB.Document) {
engine.Hooks.OnRemoved = func(path string) { /* ... */ } fmt.Println("New doc created:", doc.Path)
engine.Hooks.OnMoved = func(oldPath, newPath string) { /* ... */ } }
// 监听内容更新 (SetDoc 触发SetMeta 不触发)
engine.Hooks.OnUpdatedDoc = func(doc *docDB.Document) {
fmt.Println("Doc updated:", doc.Path, "Version:", doc.Version)
}
// 监听文档删除
engine.Hooks.OnRemoved = func(path string) {
fmt.Println("Doc removed:", path)
}
// 监听文档重命名
engine.Hooks.OnMoved = func(oldPath, newPath string) {
fmt.Println("Doc moved from", oldPath, "to", newPath)
}
``` ```
## 📝 数据结构 ## 📝 数据结构
@ -73,8 +89,11 @@ engine.Hooks.OnMoved = func(oldPath, newPath string) { /* ... */ }
| `TextContent` | `string` | 文档正文 | | `TextContent` | `string` | 文档正文 |
| `Version` | `uint64` | 单调递增的版本号 | | `Version` | `uint64` | 单调递增的版本号 |
| `ToC` | `[]ToCNode` | 自动生成的目录树 | | `ToC` | `[]ToCNode` | 自动生成的目录树 |
| `CreateTime` | `int64` | 创建时间 (自动维护) |
| `UpdateTime` | `int64` | 最后更新时间 (自动维护) |
## 💡 注意事项 ## 💡 注意事项
- **ID 封装**:底层依然使用唯一 ID 维护物理存储,但对 API 调用者完全隐藏。 - **ID 封装**:底层依然使用唯一 ID 维护物理存储,但对 API 调用者完全隐藏。
- **自动初始化**`GetDB` 会自动维护 `_Doc``_Doc_History` 的表结构。 - **自动初始化**`GetDB` 内部使用 `_system` 权限自动维护系统表及 `_Doc`, `_Doc_History` 的结构。
- **元数据自动维护**`Creator`, `Updater`, `CreateTime`, `UpdateTime` 由底层 `tableDB` 统一管控,无需显式设置。

48
doc.go
View File

@ -6,11 +6,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"apigo.cc/go/cast" "apigo.cc/go/cast"
"apigo.cc/go/file" "apigo.cc/go/file"
"apigo.cc/go/id" "apigo.cc/go/log"
"apigo.cc/go/tableDB" "apigo.cc/go/tableDB"
) )
@ -55,10 +54,10 @@ type Document struct {
AsFile bool // 是否以文件存储 AsFile bool // 是否以文件存储
Version uint64 // 版本号 Version uint64 // 版本号
IsSecret bool // 是否加密 IsSecret bool // 是否加密
CreateTime int64 // 创建时间 CreateTime int64 // 创建时间 (自动维护)
UpdateTime int64 // 更新时间 UpdateTime int64 // 更新时间 (自动维护)
Creator string // 创建人 ID Creator string // 创建人 ID (自动维护)
Updater string // 更新人 ID Updater string // 更新人 ID (自动维护)
} }
// ToCNode 目录树节点 // ToCNode 目录树节点
@ -69,9 +68,11 @@ type ToCNode struct {
} }
// GetDB 获取 DocDBUnauthorized 实例并初始化表结构 // GetDB 获取 DocDBUnauthorized 实例并初始化表结构
func GetDB(db *tableDB.TableDBUnauthorized, baseDir string) *DocDBUnauthorized { func GetDB(dsn string, logger *log.Logger, redis string, baseDir string) *DocDBUnauthorized {
sys := db.Auth(tableDB.SystemUserID) unauthorizedDB := tableDB.GetDB(dsn, logger, redis)
sys := unauthorizedDB.Auth(tableDB.SystemUserID)
// 确保系统基础表存在
if raw, err := sys.GetRawDB(); err == nil { if raw, err := sys.GetRawDB(); err == nil {
_ = raw.Sync(tableDB.SystemSchema) _ = raw.Sync(tableDB.SystemSchema)
} }
@ -112,7 +113,7 @@ func GetDB(db *tableDB.TableDBUnauthorized, baseDir string) *DocDBUnauthorized {
initTable("_Doc_History") initTable("_Doc_History")
return &DocDBUnauthorized{ return &DocDBUnauthorized{
db: db, db: unauthorizedDB,
baseDir: baseDir, baseDir: baseDir,
Hooks: &Hooks{}, Hooks: &Hooks{},
} }
@ -131,7 +132,6 @@ func (d *DocDBUnauthorized) Auth(userID string) *DocDB {
// SetDoc 更新内容 (强制提升版本号,自动保存历史) // SetDoc 更新内容 (强制提升版本号,自动保存历史)
func (a *DocDB) SetDoc(docObj *Document) error { func (a *DocDB) SetDoc(docObj *Document) error {
now := time.Now().UnixMilli()
existing, _ := a.getRaw(docObj.Path) existing, _ := a.getRaw(docObj.Path)
isUpdate := existing != nil isUpdate := existing != nil
@ -141,20 +141,23 @@ func (a *DocDB) SetDoc(docObj *Document) error {
a.archive(existing) a.archive(existing)
internalID = cast.String(existing["id"]) internalID = cast.String(existing["id"])
docObj.Version = cast.Uint64(existing["version"]) + 1 docObj.Version = cast.Uint64(existing["version"]) + 1
docObj.CreateTime = cast.Int64(existing["createTime"])
docObj.Creator = cast.String(existing["creator"])
} else { } else {
internalID = id.MakeID(10)
docObj.Version = 1 docObj.Version = 1
docObj.CreateTime = now
docObj.Creator = a.userID
} }
docObj.UpdateTime = now
docObj.Updater = a.userID
// 处理文件落盘 // 处理文件落盘
if docObj.BinaryContent != nil { if docObj.BinaryContent != nil {
if internalID == "" {
// 先执行一次空的 Set 来占坑获取 ID
placeholder := map[string]any{"path": docObj.Path, "version": docObj.Version}
_ = a.table.Set(placeholder)
// 重新获取
existing, _ = a.getRaw(docObj.Path)
if existing != nil {
internalID = cast.String(existing["id"])
}
}
if err := a.saveFile(internalID, docObj); err != nil { if err := a.saveFile(internalID, docObj); err != nil {
return err return err
} }
@ -187,12 +190,9 @@ func (a *DocDB) SetMeta(path string, meta map[string]any) error {
return fmt.Errorf("document not found: %s", path) return fmt.Errorf("document not found: %s", path)
} }
now := time.Now().UnixMilli()
for k, v := range meta { for k, v := range meta {
existing[k] = v existing[k] = v
} }
existing["updateTime"] = now
existing["updater"] = a.userID
return a.table.Set(existing) return a.table.Set(existing)
} }
@ -205,8 +205,6 @@ func (a *DocDB) Move(oldPath, newPath string) error {
} }
existing["path"] = newPath existing["path"] = newPath
existing["updateTime"] = time.Now().UnixMilli()
existing["updater"] = a.userID
err = a.table.Set(existing) err = a.table.Set(existing)
if err == nil { if err == nil {
@ -238,7 +236,9 @@ func (a *DocDB) archive(existing map[string]any) {
func (a *DocDB) toRecord(internalID string, docObj *Document) map[string]any { func (a *DocDB) toRecord(internalID string, docObj *Document) map[string]any {
record := make(map[string]any) record := make(map[string]any)
cast.Convert(&record, docObj) cast.Convert(&record, docObj)
record["id"] = internalID if internalID != "" {
record["id"] = internalID
}
delete(record, "binaryContent") delete(record, "binaryContent")
return record return record
} }

View File

@ -6,7 +6,6 @@ import (
"testing" "testing"
"apigo.cc/go/log" "apigo.cc/go/log"
"apigo.cc/go/tableDB"
) )
func TestDocDB(t *testing.T) { func TestDocDB(t *testing.T) {
@ -17,9 +16,8 @@ func TestDocDB(t *testing.T) {
os.RemoveAll("./test_docs") os.RemoveAll("./test_docs")
defer os.RemoveAll("./test_docs") defer os.RemoveAll("./test_docs")
unauthorizedDB := tableDB.GetDB("sqlite://"+dbFile, logger) // 使用新的 GetDB 入口 (支持 redis 参数)
docDBInst := GetDB("sqlite://"+dbFile, logger, "", "./test_docs")
docDBInst := GetDB(unauthorizedDB, "./test_docs")
app := docDBInst.Auth("user1") app := docDBInst.Auth("user1")
var createdCalled, updatedCalled, removedCalled, movedCalled bool var createdCalled, updatedCalled, removedCalled, movedCalled bool
@ -126,3 +124,10 @@ func TestDocDB(t *testing.T) {
t.Fatalf("Expected version 2, got %d", lastHist.Version) t.Fatalf("Expected version 2, got %d", lastHist.Version)
} }
} }
func BenchmarkExtractToC(b *testing.B) {
content := "# H1\n## H1.1\n### H1.1.1\n" + strings.Repeat("Some content\n", 100) + "# H2\n## H2.1\n"
for i := 0; i < b.N; i++ {
_ = ExtractToC(content)
}
}

2
go.mod
View File

@ -5,7 +5,6 @@ go 1.25.0
require ( require (
apigo.cc/go/cast v1.3.2 apigo.cc/go/cast v1.3.2
apigo.cc/go/file v1.3.1 apigo.cc/go/file v1.3.1
apigo.cc/go/id v1.3.0
apigo.cc/go/log v1.3.2 apigo.cc/go/log v1.3.2
apigo.cc/go/tableDB v1.1.6 apigo.cc/go/tableDB v1.1.6
) )
@ -15,6 +14,7 @@ require (
apigo.cc/go/crypto v1.3.0 // indirect apigo.cc/go/crypto v1.3.0 // indirect
apigo.cc/go/db v1.3.1 // indirect apigo.cc/go/db v1.3.1 // indirect
apigo.cc/go/encoding v1.3.0 // indirect apigo.cc/go/encoding v1.3.0 // indirect
apigo.cc/go/id v1.3.0 // indirect
apigo.cc/go/rand v1.3.0 // indirect apigo.cc/go/rand v1.3.0 // indirect
apigo.cc/go/redis v1.3.0 // indirect apigo.cc/go/redis v1.3.0 // indirect
apigo.cc/go/safe v1.3.0 // indirect apigo.cc/go/safe v1.3.0 // indirect