Private
Public Access
1
0

重构:文件系统模块化架构,优化应用启动流程

This commit is contained in:
2026-01-28 00:28:54 +08:00
parent 4a9b25a505
commit 8c577f70e7
123 changed files with 32030 additions and 967 deletions

View File

@@ -0,0 +1,392 @@
package filesystem
import (
"crypto/rand"
"encoding/json"
"fmt"
"io"
"math/big"
"os"
"path/filepath"
"time"
)
// RecycleBinEntry 回收站条目
type RecycleBinEntry struct {
OriginalPath string `json:"original_path"` // 原始路径
DeletedPath string `json:"deleted_path"` // 回收站中的路径
DeletedTime time.Time `json:"deleted_time"` // 删除时间
Size int64 `json:"size"` // 文件大小
IsDirectory bool `json:"is_directory"` // 是否为目录
OriginalDevice string `json:"original_device"` // 原始设备(盘符)
}
// RecycleBin 回收站管理器
type RecycleBin struct {
binPath string
metadataFile string
entries []RecycleBinEntry
}
// NewRecycleBin 创建回收站管理器
func NewRecycleBin(binPath string) (*RecycleBin, error) {
// 创建回收站目录
if err := os.MkdirAll(binPath, 0755); err != nil {
return nil, fmt.Errorf("创建回收站目录失败: %v", err)
}
bin := &RecycleBin{
binPath: binPath,
metadataFile: filepath.Join(binPath, "metadata.json"),
entries: make([]RecycleBinEntry, 0),
}
// 加载元数据
if err := bin.loadMetadata(); err != nil {
// 如果文件不存在,这是正常的,忽略错误
if !os.IsNotExist(err) {
return nil, fmt.Errorf("加载回收站元数据失败: %v", err)
}
}
// 启动自动清理协程
go bin.autoCleanup()
return bin, nil
}
// MoveToRecycleBin 移动文件到回收站
func (rb *RecycleBin) MoveToRecycleBin(path string) error {
// 获取文件信息
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("获取文件信息失败: %v", err)
}
// 生成唯一的回收站文件名
timestamp := time.Now().Format("20060102_150405")
randomSuffix := generateRandomString(6)
baseName := filepath.Base(path)
var recycleName string
if info.IsDir() {
recycleName = fmt.Sprintf("%s_%s_%s", timestamp, randomSuffix, baseName)
} else {
ext := filepath.Ext(baseName)
nameWithoutExt := baseName[:len(baseName)-len(ext)]
recycleName = fmt.Sprintf("%s_%s_%s%s", timestamp, randomSuffix, nameWithoutExt, ext)
}
recyclePath := filepath.Join(rb.binPath, recycleName)
// 移动文件到回收站
if err := os.Rename(path, recyclePath); err != nil {
// 如果跨设备移动失败,尝试复制后删除
if err := copyRecursively(path, recyclePath); err != nil {
return fmt.Errorf("移动到回收站失败: %v", err)
}
os.RemoveAll(path)
}
// 创建元数据条目
entry := RecycleBinEntry{
OriginalPath: path,
DeletedPath: recyclePath,
DeletedTime: time.Now(),
Size: info.Size(),
IsDirectory: info.IsDir(),
OriginalDevice: getDevice(path),
}
// 添加到元数据
rb.entries = append(rb.entries, entry)
// 保存元数据
if err := rb.saveMetadata(); err != nil {
return fmt.Errorf("保存回收站元数据失败: %v", err)
}
return nil
}
// RestoreFromRecycleBin 从回收站恢复文件
func (rb *RecycleBin) RestoreFromRecycleBin(recyclePath string) error {
// 查找对应的元数据条目
var entry *RecycleBinEntry
for i := range rb.entries {
if rb.entries[i].DeletedPath == recyclePath {
entry = &rb.entries[i]
// 从列表中移除
rb.entries = append(rb.entries[:i], rb.entries[i+1:]...)
break
}
}
if entry == nil {
return fmt.Errorf("回收站中未找到该文件")
}
// 检查原始路径的父目录是否存在
parentDir := filepath.Dir(entry.OriginalPath)
if err := os.MkdirAll(parentDir, 0755); err != nil {
return fmt.Errorf("创建父目录失败: %v", err)
}
// 检查原始位置是否已有文件
if _, err := os.Stat(entry.OriginalPath); err == nil {
return fmt.Errorf("原始位置已存在同名文件,请先删除或重命名")
}
// 移回文件
if err := os.Rename(recyclePath, entry.OriginalPath); err != nil {
// 如果跨设备移动失败,尝试复制后删除
if err := copyRecursively(recyclePath, entry.OriginalPath); err != nil {
return fmt.Errorf("恢复文件失败: %v", err)
}
os.RemoveAll(recyclePath)
}
// 保存元数据
if err := rb.saveMetadata(); err != nil {
return fmt.Errorf("保存回收站元数据失败: %v", err)
}
return nil
}
// DeletePermanently 永久删除回收站中的文件
func (rb *RecycleBin) DeletePermanently(recyclePath string) error {
// 查找元数据条目
for i, entry := range rb.entries {
if entry.DeletedPath == recyclePath {
// 从列表中移除
rb.entries = append(rb.entries[:i], rb.entries[i+1:]...)
break
}
}
// 删除文件
if err := os.RemoveAll(recyclePath); err != nil {
return fmt.Errorf("永久删除失败: %v", err)
}
// 保存元数据
if err := rb.saveMetadata(); err != nil {
return fmt.Errorf("保存回收站元数据失败: %v", err)
}
return nil
}
// ListEntries 列出回收站中的所有条目
func (rb *RecycleBin) ListEntries() []RecycleBinEntry {
return rb.entries
}
// Empty 清空回收站
func (rb *RecycleBin) Empty() error {
// 删除所有文件
for _, entry := range rb.entries {
if err := os.RemoveAll(entry.DeletedPath); err != nil {
return fmt.Errorf("删除文件失败: %s", err)
}
}
// 清空元数据
rb.entries = make([]RecycleBinEntry, 0)
// 保存元数据
if err := rb.saveMetadata(); err != nil {
return fmt.Errorf("保存回收站元数据失败: %v", err)
}
return nil
}
// autoCleanup 自动清理超过30天的文件
func (rb *RecycleBin) autoCleanup() {
ticker := time.NewTicker(24 * time.Hour) // 每天检查一次
defer ticker.Stop()
for range ticker.C {
rb.cleanupExpiredEntries()
}
}
// cleanupExpiredEntries 清理过期的条目
func (rb *RecycleBin) cleanupExpiredEntries() {
now := time.Now()
expiredEntries := make([]int, 0)
// 找出所有过期的条目超过30天
for i, entry := range rb.entries {
if now.Sub(entry.DeletedTime) > 30*24*time.Hour {
expiredEntries = append(expiredEntries, i)
}
}
// 从后往前删除(避免索引问题)
for i := len(expiredEntries) - 1; i >= 0; i-- {
idx := expiredEntries[i]
entry := rb.entries[idx]
// 删除文件
_ = os.RemoveAll(entry.DeletedPath)
// 从列表中移除
rb.entries = append(rb.entries[:idx], rb.entries[idx+1:]...)
}
// 保存元数据
if len(expiredEntries) > 0 {
_ = rb.saveMetadata()
}
}
// loadMetadata 加载元数据
func (rb *RecycleBin) loadMetadata() error {
data, err := os.ReadFile(rb.metadataFile)
if err != nil {
return err
}
return json.Unmarshal(data, &rb.entries)
}
// saveMetadata 保存元数据
func (rb *RecycleBin) saveMetadata() error {
data, err := json.MarshalIndent(rb.entries, "", " ")
if err != nil {
return err
}
return os.WriteFile(rb.metadataFile, data, 0644)
}
// copyRecursively 递归复制文件或目录
func copyRecursively(src, dst string) error {
info, err := os.Stat(src)
if err != nil {
return err
}
if info.IsDir() {
return copyDirectory(src, dst)
}
return copyFile(src, dst)
}
// copyDirectory 复制目录
func copyDirectory(src, dst string) error {
// 创建目标目录
if err := os.MkdirAll(dst, 0755); err != nil {
return err
}
// 读取源目录
entries, err := os.ReadDir(src)
if err != nil {
return err
}
// 复制每个条目
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
if err := copyDirectory(srcPath, dstPath); err != nil {
return err
}
} else {
if err := copyFile(srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}
// copyFile 复制文件
func copyFile(src, dst string) error {
// 打开源文件
srcFile, err := os.Open(src)
if err != nil {
return err
}
defer srcFile.Close()
// 创建目标文件
dstFile, err := os.Create(dst)
if err != nil {
return err
}
defer dstFile.Close()
// 复制内容
if _, err := io.Copy(dstFile, srcFile); err != nil {
return err
}
// 复制文件权限
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
return os.Chmod(dst, srcInfo.Mode())
}
// getDevice 获取文件所在设备(盘符)
func getDevice(path string) string {
absPath, err := filepath.Abs(path)
if err != nil {
return ""
}
if len(absPath) >= 2 {
return absPath[:2] // 返回 "C:" 这样的盘符
}
return ""
}
// generateRandomString 生成随机字符串
// 使用加密安全的随机数生成器,保证随机性和性能
func generateRandomString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, length)
// 使用 crypto/rand 生成安全的随机数
for i := range b {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
// 如果加密随机数生成失败,回退到时间戳(极低概率)
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
continue
}
b[i] = charset[n.Int64()]
}
return string(b)
}
// 全局回收站实例
var globalRecycleBin *RecycleBin
// InitRecycleBin 初始化全局回收站
func InitRecycleBin(binPath string) error {
bin, err := NewRecycleBin(binPath)
if err != nil {
return err
}
globalRecycleBin = bin
return nil
}
// GetRecycleBin 获取全局回收站实例
func GetRecycleBin() *RecycleBin {
return globalRecycleBin
}