Files
playground/trie.go.bak
XuanLee-HEALER df39693dff commit
2025-11-10 15:30:21 +08:00

588 lines
16 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"fmt"
"path"
"strings"
"time"
"github.com/dghubble/trie"
)
// PathNode 表示路径节点用于BFS遍历
type PathNode struct {
Path string // 完整路径
Level int // 层级深度
ParentID *int64 // 父节点ID根节点为nil
IsFile bool // 是否是文件
}
// MFiles 表示 m_files 表的记录
type MFiles struct {
ID int64 `json:"id"`
FileName string `json:"file_name"`
FileType string `json:"file_type"`
FileSize int64 `json:"file_size"`
FileKey string `json:"file_key"`
CreatedAt string `json:"created_at"`
}
// MDocuments 表示 m_documents 表的记录
type MDocuments struct {
ID int64 `json:"id"`
ParentID int64 `json:"parent_id"`
FileID *int64 `json:"file_id"`
Type string `json:"type"` // 'file' or 'dir'
Path string `json:"path"`
ProjectID int64 `json:"project_id"`
UID int64 `json:"uid"`
IsDelete int8 `json:"is_delete"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
DeletedAt *string `json:"deleted_at"`
Alias string `json:"alias"`
}
// DatabaseSimulator 数据库模拟器
type DatabaseSimulator struct {
filesTable map[string]*MFiles // file_key -> record
documentsTable map[string]*MDocuments // path -> record
nextFileID int64
nextDocID int64
projectID int64 // 固定项目ID
uid int64 // 固定用户ID
}
// NewDatabaseSimulator 创建数据库模拟器
func NewDatabaseSimulator(projectID, uid int64) *DatabaseSimulator {
return &DatabaseSimulator{
filesTable: make(map[string]*MFiles),
documentsTable: make(map[string]*MDocuments),
nextFileID: 1,
nextDocID: 1,
projectID: projectID,
uid: uid,
}
}
// isFilePath 判断路径是否为文件(基于是否有扩展名)
func isFilePath(path string) bool {
// 获取路径的最后一部分
parts := strings.Split(path, "/")
if len(parts) == 0 {
return false
}
fileName := parts[len(parts)-1]
// 如果包含点且不是以点开头,则认为是文件
return strings.Contains(fileName, ".") && !strings.HasPrefix(fileName, ".")
}
// getFileName 从路径获取文件名
func getFileName(path string) string {
parts := strings.Split(path, "/")
if len(parts) == 0 {
return ""
}
return parts[len(parts)-1]
}
// getFileExtension 获取文件扩展名
func getFileExtension(fileName string) string {
parts := strings.Split(fileName, ".")
if len(parts) <= 1 {
return ""
}
return parts[len(parts)-1]
}
// ProcessFileOperation 处理文件操作(插入或更新 m_files 表)
func (db *DatabaseSimulator) ProcessFileOperation(fileKey string) (*MFiles, string) {
fileName := getFileName(fileKey)
fileExt := getFileExtension(fileName)
currentTime := time.Now().Format("2006-01-02 15:04:05.000")
if existingFile, exists := db.filesTable[fileKey]; exists {
// 文件已存在,更新记录
updateSQL := fmt.Sprintf(`UPDATE m_files SET
file_name = '%s',
file_type = '%s',
updated_at = '%s'
WHERE file_key = '%s';`,
fileName, fileExt, currentTime, fileKey)
existingFile.FileName = fileName
existingFile.FileType = fileExt
return existingFile, updateSQL
} else {
// 插入新文件记录
newFile := &MFiles{
ID: db.nextFileID,
FileName: fileName,
FileType: fileExt,
FileSize: 1024, // 模拟文件大小
FileKey: fileKey,
CreatedAt: currentTime,
}
insertSQL := fmt.Sprintf(`INSERT INTO m_files (id, file_name, file_type, file_size, file_key, created_at)
VALUES (%d, '%s', '%s', %d, '%s', '%s');`,
newFile.ID, newFile.FileName, newFile.FileType, newFile.FileSize, newFile.FileKey, newFile.CreatedAt)
db.filesTable[fileKey] = newFile
db.nextFileID++
return newFile, insertSQL
}
}
// ProcessDocumentOperation 处理文档操作(插入或跳过 m_documents 表)
func (db *DatabaseSimulator) ProcessDocumentOperation(path string, parentID *int64, isFile bool, fileID *int64) (*MDocuments, string) {
currentTime := time.Now().Format("2006-01-02 15:04:05.000")
// 检查是否已存在且未删除
if existingDoc, exists := db.documentsTable[path]; exists && existingDoc.IsDelete == 0 {
// 文档已存在且未删除,不处理
return existingDoc, ""
}
// 确定父级ID如果没有父级则为0
actualParentID := int64(0)
if parentID != nil {
actualParentID = *parentID
}
// 确定文档类型
docType := "dir"
if isFile {
docType = "file"
}
// 插入新文档记录
newDoc := &MDocuments{
ID: db.nextDocID,
ParentID: actualParentID,
FileID: fileID,
Type: docType,
Path: path,
ProjectID: db.projectID,
UID: db.uid,
IsDelete: 0,
CreatedAt: currentTime,
UpdatedAt: currentTime,
DeletedAt: nil,
Alias: getFileName(path),
}
fileIDStr := "NULL"
if fileID != nil {
fileIDStr = fmt.Sprintf("%d", *fileID)
}
insertSQL := fmt.Sprintf(`INSERT INTO m_documents (id, parent_id, file_id, type, path, project_id, uid, is_delete, created_at, updated_at, alias)
VALUES (%d, %d, %s, '%s', '%s', %d, %d, %d, '%s', '%s', '%s');`,
newDoc.ID, newDoc.ParentID, fileIDStr, newDoc.Type, newDoc.Path,
newDoc.ProjectID, newDoc.UID, newDoc.IsDelete, newDoc.CreatedAt, newDoc.UpdatedAt, newDoc.Alias)
db.documentsTable[path] = newDoc
db.nextDocID++
return newDoc, insertSQL
}
// PathTrieBuilder 路径字典树构建器
type PathTrieBuilder struct {
trie *trie.PathTrie
}
// NewPathTrieBuilder 创建新的路径字典树构建器
func NewPathTrieBuilder() *PathTrieBuilder {
return &PathTrieBuilder{
trie: trie.NewPathTrie(),
}
}
// BuildTrie 构建字典树
func (ptb *PathTrieBuilder) BuildTrie(paths []string) {
for _, path := range paths {
// 将路径按分隔符分割
parts := strings.Split(path, "/")
// 逐步构建路径并插入到trie中
currentPath := ""
for i, part := range parts {
if i == 0 {
currentPath = part
} else {
currentPath = currentPath + "/" + part
}
// 将当前路径插入到trie中值为路径的层级
ptb.trie.Put(currentPath, len(strings.Split(currentPath, "/")))
}
}
}
// BFSTraverse 使用BFS遍历字典树构建带有父子关系的路径节点
func (ptb *PathTrieBuilder) BFSTraverse() []PathNode {
var result []PathNode
var queue []PathNode
// 获取所有根路径(第一层)
ptb.trie.Walk(func(key string, value interface{}) error {
level := value.(int)
if level == 1 { // 第一层的路径
queue = append(queue, PathNode{
Path: key,
Level: level,
ParentID: nil,
IsFile: isFilePath(key),
})
}
return nil
})
// BFS遍历
for len(queue) > 0 {
// 取出队列头部节点
current := queue[0]
queue = queue[1:]
// 添加到结果中
result = append(result, current)
// 查找当前节点的子节点
ptb.trie.Walk(func(key string, value interface{}) error {
level := value.(int)
// 如果是当前路径的直接子路径
if strings.HasPrefix(key, current.Path+"/") && level == current.Level+1 {
// 确保是直接子路径,而不是孙子路径
remainingPath := strings.TrimPrefix(key, current.Path+"/")
if !strings.Contains(remainingPath, "/") {
childNode := PathNode{
Path: key,
Level: level,
ParentID: nil,
IsFile: isFilePath(key),
}
queue = append(queue, childNode)
}
}
return nil
})
}
return result
}
// ProcessPathsWithDatabase 处理路径并生成数据库操作
func ProcessPathsWithDatabase(paths []PathNode) {
fmt.Println("=== 模拟数据库操作 ===")
fmt.Println("按BFS顺序处理路径并生成SQL语句")
fmt.Println()
// 创建数据库模拟器
db := NewDatabaseSimulator(1, 1001) // projectID=1, uid=1001
pathToDocIDMap := make(map[string]int64) // 路径到document ID的映射
// 预先插入一些已存在的记录
fmt.Println("=== 预置数据库记录 ===")
existingDocs := []*MDocuments{
{ID: 100, ParentID: 0, FileID: nil, Type: "dir", Path: "home", ProjectID: 1, UID: 1001, IsDelete: 0, Alias: "home"},
{ID: 101, ParentID: 100, FileID: nil, Type: "dir", Path: "home/user", ProjectID: 1, UID: 1001, IsDelete: 0, Alias: "user"},
}
for _, doc := range existingDocs {
db.documentsTable[doc.Path] = doc
pathToDocIDMap[doc.Path] = doc.ID
if doc.ID >= db.nextDocID {
db.nextDocID = doc.ID + 1
}
fmt.Printf("已存在文档: ID=%d, Path=%s, Type=%s, ParentID=%d\n",
doc.ID, doc.Path, doc.Type, doc.ParentID)
}
fmt.Println()
// 统计计数器
var (
filesInserted = 0
filesUpdated = 0
dirsInserted = 0
dirsSkipped = 0
sqlStatements []string
)
// 处理BFS遍历的路径
for i, node := range paths {
// 获取父节点ID
parentID := getParentDocumentID(node.Path, pathToDocIDMap)
fmt.Printf("[%03d] 处理路径: %s (Level %d, Type: %s)\n",
i+1, node.Path, node.Level, func() string {
if node.IsFile {
return "文件"
}
return "目录"
}())
if node.IsFile {
// 处理文件
fmt.Printf(" → 1. 处理文件表 (m_files):\n")
// 先处理 m_files 表
fileRecord, fileSQL := db.ProcessFileOperation(node.Path)
if strings.Contains(fileSQL, "INSERT") {
fmt.Printf(" INSERT文件: %s\n", fileSQL)
filesInserted++
} else if strings.Contains(fileSQL, "UPDATE") {
fmt.Printf(" UPDATE文件: %s\n", fileSQL)
filesUpdated++
}
sqlStatements = append(sqlStatements, fileSQL)
// 再处理 m_documents 表
fmt.Printf(" → 2. 处理文档表 (m_documents):\n")
docRecord, docSQL := db.ProcessDocumentOperation(node.Path, parentID, true, &fileRecord.ID)
if docSQL != "" {
fmt.Printf(" INSERT文档: %s\n", docSQL)
pathToDocIDMap[node.Path] = docRecord.ID
dirsInserted++
sqlStatements = append(sqlStatements, docSQL)
} else {
fmt.Printf(" SKIP文档: 路径已存在且未删除\n")
dirsSkipped++
}
} else {
// 处理目录
fmt.Printf(" → 处理文档表 (m_documents) - 目录:\n")
docRecord, docSQL := db.ProcessDocumentOperation(node.Path, parentID, false, nil)
if docSQL != "" {
fmt.Printf(" INSERT目录: %s\n", docSQL)
pathToDocIDMap[node.Path] = docRecord.ID
dirsInserted++
sqlStatements = append(sqlStatements, docSQL)
} else {
fmt.Printf(" SKIP目录: 路径已存在且未删除\n")
// 即使跳过也要记录ID供子节点使用
if existingDoc, exists := db.documentsTable[node.Path]; exists {
pathToDocIDMap[node.Path] = existingDoc.ID
}
dirsSkipped++
}
}
fmt.Println()
}
// 输出统计信息
fmt.Println("=== 操作统计 ===")
fmt.Printf("总共处理路径: %d 个\n", len(paths))
fmt.Printf("文件插入: %d 条\n", filesInserted)
fmt.Printf("文件更新: %d 条\n", filesUpdated)
fmt.Printf("目录插入: %d 条\n", dirsInserted)
fmt.Printf("目录跳过: %d 条\n", dirsSkipped)
fmt.Printf("生成SQL语句: %d 条\n", len(sqlStatements))
fmt.Println("\n=== 完整SQL执行顺序 ===")
for i, sql := range sqlStatements {
fmt.Printf("[%03d] %s\n", i+1, sql)
}
}
// getParentDocumentID 根据路径获取父文档ID
func getParentDocumentID(path string, pathToDocIDMap map[string]int64) *int64 {
parts := strings.Split(path, "/")
if len(parts) <= 1 {
return nil // 根节点没有父节点
}
// 构建父路径
parentParts := parts[:len(parts)-1]
parentPath := strings.Join(parentParts, "/")
if parentID, exists := pathToDocIDMap[parentPath]; exists {
return &parentID
}
return nil
} // GenerateSamplePaths 生成示例路径数据,包含文件和目录
func GenerateSamplePaths() []string {
return []string{
// 文档目录相关
"home/user/documents",
"home/user/documents/readme.txt",
"home/user/documents/project.pdf",
"home/user/downloads",
"home/user/downloads/setup.exe",
"home/user/pictures/vacation",
"home/user/pictures/vacation/beach.jpg",
"home/user/pictures/vacation/sunset.png",
"home/user/pictures/family",
"home/user/pictures/family/photo1.jpg",
// 管理员目录
"home/admin/config",
"home/admin/config/app.yaml",
"home/admin/logs/error",
"home/admin/logs/error/app.log",
"home/admin/logs/access",
"home/admin/logs/access/access.log",
// 系统日志
"var/log/system",
"var/log/system/boot.log",
"var/log/application/debug",
"var/log/application/debug/debug.log",
"var/log/application/info",
"var/log/application/info/info.log",
// 缓存目录
"var/cache/temp",
"var/cache/temp/temp_file.tmp",
"var/cache/sessions",
"var/cache/sessions/session_123.dat",
// 用户程序目录
"usr/local/bin",
"usr/local/bin/app",
"usr/local/lib/python",
"usr/local/lib/python/module.py",
"usr/local/lib/nodejs",
"usr/local/lib/nodejs/package.json",
// 共享资源
"usr/share/docs",
"usr/share/docs/manual.pdf",
"usr/share/icons/themes",
"usr/share/icons/themes/icon.png",
// 配置文件
"etc/nginx/sites",
"etc/nginx/sites/default.conf",
"etc/nginx/conf.d",
"etc/nginx/conf.d/ssl.conf",
"etc/ssl/certs",
"etc/ssl/certs/server.crt",
"etc/ssh/keys",
"etc/ssh/keys/id_rsa.pub",
// 软件目录
"opt/software/database",
"opt/software/database/config.ini",
"opt/software/webserver",
"opt/software/webserver/httpd.conf",
// 临时上传
"tmp/uploads/images",
"tmp/uploads/images/upload.jpg",
"tmp/uploads/documents",
"tmp/uploads/documents/doc.docx",
"tmp/cache/thumbnails",
"tmp/cache/thumbnails/thumb.jpg",
// 数据备份
"data/backups/daily",
"data/backups/daily/backup_20240829.sql",
"data/backups/weekly",
"data/backups/weekly/backup_week34.tar.gz",
"data/exports/csv",
"data/exports/csv/users.csv",
"data/exports/json",
"data/exports/json/data.json",
}
}
func main() {
// // 密钥长度32字节256位
// key := make([]byte, 32)
// // Nonce长度12字节96位
// nonce := make([]byte, 12)
// // 使用crypto/rand包安全地生成随机字节
// _, err := rand.Read(key)
// if err != nil {
// log.Fatalf("Error generating key: %v", err)
// }
// _, err = rand.Read(nonce)
// if err != nil {
// log.Fatalf("Error generating nonce: %v", err)
// }
// // 打印生成的字节数组的字面值
// fmt.Printf("生成的ChaCha20密钥\n%#v\n", key)
// fmt.Printf("生成的ChaCha20 Nonce\n%#v\n", nonce)
// db, err := gorm.Open(mysql.Open(""), &gorm.Config{
// Logger: logger.Default.LogMode(logger.Info),
// })
// if err != nil {
// return nil, err
// }
// // 配置连接池
// sqlDB, err := db.DB()
// if err != nil {
// return nil, err
// }
// // 设置连接池参数
// if conf.MaxIdleConns > 0 {
// sqlDB.SetMaxIdleConns(conf.MaxIdleConns)
// }
// if conf.MaxOpenConns > 0 {
// sqlDB.SetMaxOpenConns(conf.MaxOpenConns)
// }
// if conf.ConnMaxLifetime > 0 {
// sqlDB.SetConnMaxLifetime(time.Duration(conf.ConnMaxLifetime) * time.Second)
// }
fmt.Println("=== 路径字典树构建和数据库操作演示 ===")
fmt.Println()
// 生成示例路径
paths := GenerateSamplePaths()
fmt.Printf("生成了 %d 个示例路径(包含文件和目录):\n", len(paths))
for i, path := range paths {
pathType := "目录"
if isFilePath(path) {
pathType = "文件"
}
fmt.Printf(" [%02d] %s (%s)\n", i+1, path, pathType)
}
fmt.Println()
// 创建路径字典树构建器
builder := NewPathTrieBuilder()
// 构建字典树
fmt.Println("正在构建字典树...")
builder.BuildTrie(paths)
fmt.Println("字典树构建完成!")
fmt.Println()
builder.trie.Walk(func(key string, value interface{}) error {
println("key is ", key)
return nil
})
// BFS遍历字典树
// fmt.Println("开始BFS遍历字典树...")
// bfsResult := builder.BFSTraverse()
// fmt.Printf("BFS遍历完成共获得 %d 个节点\n", len(bfsResult))
// fmt.Println()
// 处理路径并生成数据库操作
// ProcessPathsWithDatabase(bfsResult)
fmt.Println()
fmt.Println("=== 程序执行完成 ===")
p := "a/b/c"
dir, base := path.Split(p)
println("dir:", dir, "base:", base)
p = "a/b/c/"
dir, base = path.Split(p)
println("dir:", dir, "base:", base)
println("tst", strings.Join([]string{"good"}, "/"))
}