feat: align with tableDB updates, automate ID/metadata, and refine GetDB signature (by AI)
This commit is contained in:
parent
081a362b97
commit
cf01dcf464
41
README.md
41
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` 统一管控,无需显式设置。
|
||||
|
||||
48
doc.go
48
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
|
||||
}
|
||||
|
||||
13
doc_test.go
13
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)
|
||||
}
|
||||
}
|
||||
|
||||
2
go.mod
2
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user