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
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` 统一管控,无需显式设置。

46
doc.go
View File

@ -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)
if internalID != "" {
record["id"] = internalID
}
delete(record, "binaryContent")
return record
}

View File

@ -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)
}
}

2
go.mod
View File

@ -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