diff --git a/README.md b/README.md index d598d34..4019d49 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,15 @@ go get apigo.cc/go/docDB ```go import ( "apigo.cc/go/docDB" - "apigo.cc/go/tableDB" + "apigo.cc/go/log" ) -// 创建基础数据库连接 (tableDB 层) -unauthorizedDB := tableDB.GetDB("sqlite://docs.db", logger) - -// 获取 docDB 引擎 (自动完成表结构初始化) -engine := docDB.GetDB(unauthorizedDB, "./storage_root") +// 获取 docDB 引擎 (自动完成系统表与业务表初始化) +// dsn: 数据库连接字符串 (如 sqlite://docs.db) +// logger: 日志对象 +// redis: 分布式 ID 用的 Redis 地址 (可选) +// baseDir: 物理文件存储根路径 +engine := docDB.GetDB("sqlite://docs.db", log.DefaultLogger, "localhost:6379", "./storage_root") // 授权并获取操作句柄 db := engine.Auth("user_123") @@ -57,10 +58,25 @@ db := engine.Auth("user_123") ### 3. 生命周期钩子 (Hooks) ```go -engine.Hooks.OnCreatedDoc = func(doc *docDB.Document) { /* ... */ } -engine.Hooks.OnUpdatedDoc = func(doc *docDB.Document) { /* ... */ } -engine.Hooks.OnRemoved = func(path string) { /* ... */ } -engine.Hooks.OnMoved = func(oldPath, newPath string) { /* ... */ } +// 监听新文档创建 +engine.Hooks.OnCreatedDoc = func(doc *docDB.Document) { + fmt.Println("New doc created:", doc.Path) +} + +// 监听内容更新 (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` | 文档正文 | | `Version` | `uint64` | 单调递增的版本号 | | `ToC` | `[]ToCNode` | 自动生成的目录树 | +| `CreateTime` | `int64` | 创建时间 (自动维护) | +| `UpdateTime` | `int64` | 最后更新时间 (自动维护) | ## 💡 注意事项 - **ID 封装**:底层依然使用唯一 ID 维护物理存储,但对 API 调用者完全隐藏。 -- **自动初始化**:`GetDB` 会自动维护 `_Doc` 和 `_Doc_History` 的表结构。 +- **自动初始化**:`GetDB` 内部使用 `_system` 权限自动维护系统表及 `_Doc`, `_Doc_History` 的结构。 +- **元数据自动维护**:`Creator`, `Updater`, `CreateTime`, `UpdateTime` 由底层 `tableDB` 统一管控,无需显式设置。 diff --git a/doc.go b/doc.go index 3ba8084..f7f3951 100644 --- a/doc.go +++ b/doc.go @@ -6,11 +6,10 @@ import ( "os" "path/filepath" "strings" - "time" "apigo.cc/go/cast" "apigo.cc/go/file" - "apigo.cc/go/id" + "apigo.cc/go/log" "apigo.cc/go/tableDB" ) @@ -55,10 +54,10 @@ type Document struct { AsFile bool // 是否以文件存储 Version uint64 // 版本号 IsSecret bool // 是否加密 - CreateTime int64 // 创建时间 - UpdateTime int64 // 更新时间 - Creator string // 创建人 ID - Updater string // 更新人 ID + CreateTime int64 // 创建时间 (自动维护) + UpdateTime int64 // 更新时间 (自动维护) + Creator string // 创建人 ID (自动维护) + Updater string // 更新人 ID (自动维护) } // ToCNode 目录树节点 @@ -69,9 +68,11 @@ type ToCNode struct { } // GetDB 获取 DocDBUnauthorized 实例并初始化表结构 -func GetDB(db *tableDB.TableDBUnauthorized, baseDir string) *DocDBUnauthorized { - sys := db.Auth(tableDB.SystemUserID) +func GetDB(dsn string, logger *log.Logger, redis string, baseDir string) *DocDBUnauthorized { + unauthorizedDB := tableDB.GetDB(dsn, logger, redis) + sys := unauthorizedDB.Auth(tableDB.SystemUserID) + // 确保系统基础表存在 if raw, err := sys.GetRawDB(); err == nil { _ = raw.Sync(tableDB.SystemSchema) } @@ -112,7 +113,7 @@ func GetDB(db *tableDB.TableDBUnauthorized, baseDir string) *DocDBUnauthorized { initTable("_Doc_History") return &DocDBUnauthorized{ - db: db, + db: unauthorizedDB, baseDir: baseDir, Hooks: &Hooks{}, } @@ -131,7 +132,6 @@ func (d *DocDBUnauthorized) Auth(userID string) *DocDB { // SetDoc 更新内容 (强制提升版本号,自动保存历史) func (a *DocDB) SetDoc(docObj *Document) error { - now := time.Now().UnixMilli() existing, _ := a.getRaw(docObj.Path) isUpdate := existing != nil @@ -141,20 +141,23 @@ func (a *DocDB) SetDoc(docObj *Document) error { a.archive(existing) internalID = cast.String(existing["id"]) docObj.Version = cast.Uint64(existing["version"]) + 1 - docObj.CreateTime = cast.Int64(existing["createTime"]) - docObj.Creator = cast.String(existing["creator"]) } else { - internalID = id.MakeID(10) docObj.Version = 1 - docObj.CreateTime = now - docObj.Creator = a.userID } - docObj.UpdateTime = now - docObj.Updater = a.userID - // 处理文件落盘 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 { 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) } - now := time.Now().UnixMilli() for k, v := range meta { existing[k] = v } - existing["updateTime"] = now - existing["updater"] = a.userID return a.table.Set(existing) } @@ -205,8 +205,6 @@ func (a *DocDB) Move(oldPath, newPath string) error { } existing["path"] = newPath - existing["updateTime"] = time.Now().UnixMilli() - existing["updater"] = a.userID err = a.table.Set(existing) 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 { record := make(map[string]any) cast.Convert(&record, docObj) - record["id"] = internalID + if internalID != "" { + record["id"] = internalID + } delete(record, "binaryContent") return record } diff --git a/doc_test.go b/doc_test.go index 6488916..4dc73e0 100644 --- a/doc_test.go +++ b/doc_test.go @@ -6,7 +6,6 @@ import ( "testing" "apigo.cc/go/log" - "apigo.cc/go/tableDB" ) func TestDocDB(t *testing.T) { @@ -17,9 +16,8 @@ func TestDocDB(t *testing.T) { os.RemoveAll("./test_docs") defer os.RemoveAll("./test_docs") - unauthorizedDB := tableDB.GetDB("sqlite://"+dbFile, logger) - - docDBInst := GetDB(unauthorizedDB, "./test_docs") + // 使用新的 GetDB 入口 (支持 redis 参数) + docDBInst := GetDB("sqlite://"+dbFile, logger, "", "./test_docs") app := docDBInst.Auth("user1") var createdCalled, updatedCalled, removedCalled, movedCalled bool @@ -126,3 +124,10 @@ func TestDocDB(t *testing.T) { 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) + } +} diff --git a/go.mod b/go.mod index 901d3ea..fafc151 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.25.0 require ( apigo.cc/go/cast v1.3.2 apigo.cc/go/file v1.3.1 - apigo.cc/go/id v1.3.0 apigo.cc/go/log v1.3.2 apigo.cc/go/tableDB v1.1.6 ) @@ -15,6 +14,7 @@ require ( apigo.cc/go/crypto v1.3.0 // indirect apigo.cc/go/db v1.3.1 // 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/redis v1.3.0 // indirect apigo.cc/go/safe v1.3.0 // indirect