588 lines
16 KiB
Go
588 lines
16 KiB
Go
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"}, "/"))
|
||
}
|