package filesystem import ( "context" "encoding/base64" "errors" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "sync" "time" "u-desk/internal/common" ) const maxReadWriteSize = 10 * 1024 * 1024 // 10MB 读写上限 // FileOperationResult 文件操作结果 type FileOperationResult struct { Path string `json:"path"` Name string `json:"name"` Size int64 `json:"size"` SizeStr string `json:"size_str,omitempty"` IsDir bool `json:"is_dir"` ModTime string `json:"mod_time,omitempty"` Mode string `json:"mode,omitempty"` OldPath string `json:"old_path,omitempty"` // 仅重命名操作时有值 Deleted bool `json:"deleted,omitempty"` // 仅删除操作时有值 } // FileSystemService 文件系统服务 // 统一管理所有文件系统相关的功能,使用依赖注入而非全局变量 type FileSystemService struct { // 核心组件 config *Config pathValidator PathValidator fileTypeManager FileTypeManager // 基础设施组件 auditLogger *AuditLogger recycleBin *RecycleBin lockChecker *FileLockChecker // 状态管理 mu sync.RWMutex initialized bool } // NewFileSystemService 创建新的文件系统服务 // 使用依赖注入,所有组件通过参数传入,便于测试和替换 func NewFileSystemService(config *Config) (*FileSystemService, error) { if config == nil { config = DefaultConfig() } service := &FileSystemService{ config: config, pathValidator: NewPathValidator(config), fileTypeManager: NewFileTypeManager(config), } // 初始化基础设施组件 if err := service.initializeComponents(); err != nil { return nil, fmt.Errorf("初始化文件系统服务失败: %w", err) } service.initialized = true return service, nil } // initializeComponents 初始化各个组件 func (s *FileSystemService) initializeComponents() error { // 1. 初始化审计日志 if s.config.Features.AuditLog { if err := s.initAuditLogger(); err != nil { return fmt.Errorf("初始化审计日志失败: %w", err) } } // 2. 初始化回收站 if s.config.Features.RecycleBin { if err := s.initRecycleBin(); err != nil { return fmt.Errorf("初始化回收站失败: %w", err) } } // 3. 初始化文件锁检查器 if s.config.Features.FileLockCheck { s.lockChecker = NewFileLockChecker() } return nil } // initAuditLogger 初始化审计日志 func (s *FileSystemService) initAuditLogger() error { logDir := filepath.Join(common.GetUserDataDir(), "logs") logger, err := NewAuditLogger(logDir) if err != nil { return err } s.auditLogger = logger return nil } // initRecycleBin 初始化回收站 func (s *FileSystemService) initRecycleBin() error { recycleBinPath := filepath.Join(common.GetUserDataDir(), "recycle_bin") bin, err := NewRecycleBin(recycleBinPath) if err != nil { return err } s.recycleBin = bin return nil } // ========== 核心文件操作 ========== // ReadFile 读取文件内容(限制最大 10MB) func (s *FileSystemService) ReadFile(path string) (string, error) { // 路径验证 if err := s.validatePath(path); err != nil { return "", err } // 检查文件大小,避免读取超大文件导致内存问题 info, err := os.Stat(path) if err != nil { return "", fmt.Errorf("获取文件信息失败: %v", err) } if info.Size() > maxReadWriteSize { return "", fmt.Errorf("文件过大 (%.1f MB),超过读取上限 (%d MB)", float64(info.Size())/1024/1024, maxReadWriteSize/1024/1024) } // 读取文件 data, err := os.ReadFile(path) if err != nil { return "", fmt.Errorf("读取文件失败: %v", err) } s.logRead(path, int64(len(data)), nil) return string(data), nil } // Write 写入文件内容(实现 FileService 接口) // writeFile 内部写入实现(路径验证+大小检查+写入+日志) func (s *FileSystemService) writeFileWithLog(path string, data []byte) error { if err := s.validatePath(path); err != nil { return err } dir := filepath.Dir(path) if err := os.MkdirAll(dir, DefaultDirPermissions); err != nil { return fmt.Errorf("创建目录失败: %v", err) } if len(data) > maxReadWriteSize { return fmt.Errorf("文件过大 (%.1f MB),超过写入上限 (%d MB)", float64(len(data))/1024/1024, maxReadWriteSize/1024/1024) } if err := os.WriteFile(path, data, DefaultFilePermissions); err != nil { s.logWrite(path, int64(len(data)), err) return fmt.Errorf("写入文件失败: %v", err) } s.logWrite(path, int64(len(data)), nil) return nil } // WriteFile 写入文件 func (s *FileSystemService) WriteFile(path, content string) error { return s.writeFileWithLog(path, []byte(content)) } // SaveBase64File 将 base64 编码内容解码后写入二进制文件 func (s *FileSystemService) SaveBase64File(path, base64Content string) error { if strings.TrimSpace(base64Content) == "" { return errors.New("base64 内容不能为空") } data, err := base64.StdEncoding.DecodeString(base64Content) if err != nil { return fmt.Errorf("base64 解码失败: %v", err) } return s.writeFileWithLog(path, data) } // DeletePath 删除文件或目录 func (s *FileSystemService) DeletePath(path string) (*FileOperationResult, error) { return s.DeletePathWithContext(context.Background(), path) } // DeletePathWithContext 带上下文的删除操作,返回被删除文件的信息 func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path string) (*FileOperationResult, error) { // 路径验证 if err := s.validatePath(path); err != nil { return nil, err } // 获取文件信息(在删除前保存) info, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { return nil, fmt.Errorf("文件或目录不存在") } return nil, fmt.Errorf("获取文件信息失败: %v", err) } // 检查删除限制 exceeds, details, checkErr := CheckDeleteRestrictions(path, info, s.config) if checkErr != nil { return nil, checkErr } if exceeds { if s.config.Security.DeleteRestrictions.RequireConfirm { return nil, &DeleteRestrictionWarning{ Path: path, Details: details, Info: info, } } return nil, fmt.Errorf("删除限制: %s", details) } // 文件锁检查(可选) if s.lockChecker != nil { if err := s.lockChecker.SafeDeleteWithLockCheck(path); err != nil { return nil, err } } // 执行删除 var deleteErr error if info.IsDir() { deleteErr = os.RemoveAll(path) } else { deleteErr = os.Remove(path) } s.logDelete(path, info.IsDir(), info.Size(), deleteErr) if deleteErr != nil { return nil, fmt.Errorf("删除失败: %v", deleteErr) } // 如果启用回收站,移动到回收站而非永久删除 if s.recycleBin != nil { // 检查是否已在回收站中 if !isInRecycleBin(path) { if err := s.recycleBin.MoveToRecycleBin(path); err != nil { // 回收站失败,记录但继续 fmt.Printf("[警告] 移动到回收站失败: %v\n", err) } } } // 返回被删除的文件信息,用于前端更新 return &FileOperationResult{ Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: FormatBytes(info.Size()), IsDir: info.IsDir(), ModTime: info.ModTime().Format("2006-01-02 15:04:05"), Mode: info.Mode().String(), Deleted: true, }, nil } // ListDir 列出目录内容 func (s *FileSystemService) ListDir(path string) ([]map[string]interface{}, error) { // 路径验证 if err := s.validatePath(path); err != nil { return nil, err } // 读取目录 entries, err := os.ReadDir(path) if err != nil { return nil, fmt.Errorf("读取目录失败: %v", err) } // 转换为结果格式 result := make([]map[string]interface{}, 0, len(entries)) for _, entry := range entries { info, err := entry.Info() if err != nil { continue } fullPath := filepath.Join(path, entry.Name()) result = append(result, map[string]interface{}{ "name": entry.Name(), "path": filepath.ToSlash(fullPath), // 统一使用正斜杠 "is_dir": entry.IsDir(), "size": info.Size(), "mod_time": info.ModTime().Format("2006-01-02 15:04:05"), }) } s.logAudit(AuditLogEntry{ Timestamp: getCurrentTimestamp(), Operation: OperationList, Path: path, IsDirectory: true, Success: true, }) return result, nil } // CreateDir 创建目录,返回创建的目录信息 func (s *FileSystemService) CreateDir(path string) (*FileOperationResult, error) { if err := s.validatePath(path); err != nil { return nil, err } if err := os.MkdirAll(path, DefaultDirPermissions); err != nil { return nil, fmt.Errorf("创建目录失败: %v", err) } s.logAudit(AuditLogEntry{ Timestamp: getCurrentTimestamp(), Operation: OperationCreate, Path: path, IsDirectory: true, Success: true, }) // 获取创建的目录信息 info, err := os.Stat(path) if err != nil { // 创建成功但获取信息失败,返回基本信息 return &FileOperationResult{ Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: filepath.Base(path), IsDir: true, }, nil } return &FileOperationResult{ Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: FormatBytes(info.Size()), IsDir: true, ModTime: info.ModTime().Format("2006-01-02 15:04:05"), Mode: info.Mode().String(), }, nil } // CreateFile 创建空文件,返回创建的文件信息 func (s *FileSystemService) CreateFile(path string) (*FileOperationResult, error) { if err := s.validatePath(path); err != nil { return nil, err } // 检查文件是否已存在 if _, err := os.Stat(path); err == nil { return nil, fmt.Errorf("文件已存在") } file, err := os.Create(path) if err != nil { return nil, fmt.Errorf("创建文件失败: %v", err) } file.Close() s.logAudit(AuditLogEntry{ Timestamp: getCurrentTimestamp(), Operation: OperationCreate, Path: path, IsDirectory: false, Success: true, }) // 获取创建的文件信息 info, err := os.Stat(path) if err != nil { // 创建成功但获取信息失败,返回基本信息 return &FileOperationResult{ Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: filepath.Base(path), IsDir: false, Size: 0, }, nil } return &FileOperationResult{ Path: filepath.ToSlash(path), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: FormatBytes(info.Size()), IsDir: false, ModTime: info.ModTime().Format("2006-01-02 15:04:05"), Mode: info.Mode().String(), }, nil } // GetFileInfo 获取文件信息 func (s *FileSystemService) GetFileInfo(path string) (map[string]interface{}, error) { if err := s.validatePath(path); err != nil { return nil, err } info, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { return nil, fmt.Errorf("文件或目录不存在") } return nil, fmt.Errorf("获取文件信息失败: %v", err) } return map[string]interface{}{ "name": info.Name(), "path": filepath.ToSlash(path), // 统一使用正斜杠 "size": info.Size(), "size_str": FormatBytes(info.Size()), "is_dir": info.IsDir(), "mod_time": info.ModTime().Format("2006-01-02 15:04:05"), "mode": info.Mode().String(), }, nil } // OpenPath 打开文件或目录(使用系统默认程序) func (s *FileSystemService) OpenPath(path string) error { if err := s.validatePath(path); err != nil { return err } return OpenPath(path) } // RenamePath 重命名文件或目录,返回新文件信息 func (s *FileSystemService) RenamePath(oldPath, newPath string) (*FileOperationResult, error) { // 验证旧路径 if err := s.validatePath(oldPath); err != nil { return nil, err } // 验证新路径 if err := s.validatePath(newPath); err != nil { return nil, err } // 执行重命名 if err := os.Rename(oldPath, newPath); err != nil { return nil, fmt.Errorf("重命名失败: %v", err) } s.logAudit(AuditLogEntry{ Timestamp: getCurrentTimestamp(), Operation: OperationRename, Path: newPath, OldPath: oldPath, Success: true, }) // 获取新文件信息 info, err := os.Stat(newPath) if err != nil { // 重命名成功但获取信息失败,返回基本信息 return &FileOperationResult{ Path: filepath.ToSlash(newPath), // 统一使用正斜杠 Name: filepath.Base(newPath), OldPath: filepath.ToSlash(oldPath), }, nil } return &FileOperationResult{ Path: filepath.ToSlash(newPath), // 统一使用正斜杠 Name: info.Name(), Size: info.Size(), SizeStr: FormatBytes(info.Size()), IsDir: info.IsDir(), ModTime: info.ModTime().Format("2006-01-02 15:04:05"), Mode: info.Mode().String(), OldPath: filepath.ToSlash(oldPath), }, nil } // ========== ZIP操作接口 ========== // ListZipContents 列出ZIP文件内容(别名,保持向后兼容) func (s *FileSystemService) ListZipContents(zipPath string) ([]map[string]interface{}, error) { return ListZipContents(zipPath) } // ExtractFileFromZip 从ZIP提取文件内容(别名,保持向后兼容) func (s *FileSystemService) ExtractFileFromZip(zipPath, filePath string) (string, error) { return ExtractFileFromZip(zipPath, filePath) } // ExtractFileFromZipToTemp 从ZIP提取文件到临时目录(别名,保持向后兼容) func (s *FileSystemService) ExtractFileFromZipToTemp(zipPath, filePath string) (string, error) { return ExtractFileFromZipToTemp(zipPath, filePath) } // GetZipFileInfo 获取ZIP文件信息 func (s *FileSystemService) GetZipFileInfo(zipPath, filePath string) (map[string]interface{}, error) { return GetZipFileInfo(zipPath, filePath) } // ========== 辅助函数 ========== // getCurrentTimestamp 获取当前时间戳 func getCurrentTimestamp() time.Time { return time.Now() } // isInRecycleBin 检查路径是否在回收站中 func isInRecycleBin(path string) bool { recycleBinPath := filepath.Join(common.GetUserDataDir(), "recycle_bin") cleanPath := filepath.Clean(path) cleanBinPath := filepath.Clean(recycleBinPath) return len(cleanPath) >= len(cleanBinPath) && cleanPath[:len(cleanBinPath)] == cleanBinPath } // ========== 辅助方法 ========== // validatePath 验证路径 func (s *FileSystemService) validatePath(path string) error { err := s.pathValidator.Validate(path) if err != nil && err.IsError { return err } return nil } // GetConfig 获取配置 func (s *FileSystemService) GetConfig() *Config { return s.config } // GetAuditLogger 获取审计日志记录器 func (s *FileSystemService) GetAuditLogger() *AuditLogger { return s.auditLogger } // GetRecycleBin 获取回收站 func (s *FileSystemService) GetRecycleBin() *RecycleBin { return s.recycleBin } // ========== 审计日志接口 ========== // logAudit 安全记录审计日志(自动处理 nil 检查) func (s *FileSystemService) logAudit(entry AuditLogEntry) { if s.auditLogger != nil { s.auditLogger.Log(entry) } } // logRead 记录读取操作审计日志 func (s *FileSystemService) logRead(path string, size int64, err error) { if s.auditLogger != nil { s.auditLogger.LogRead(path, size, err) } } // logWrite 记录写入操作审计日志 func (s *FileSystemService) logWrite(path string, size int64, err error) { if s.auditLogger != nil { s.auditLogger.LogWrite(path, size, err) } } // logDelete 记录删除操作审计日志 func (s *FileSystemService) logDelete(path string, isDir bool, size int64, err error) { if s.auditLogger != nil { s.auditLogger.LogDelete(path, isDir, size, err) } } // GetAuditLogs 获取审计日志 func (s *FileSystemService) GetAuditLogs(limit int) ([]map[string]interface{}, error) { if s.auditLogger == nil { return []map[string]interface{}{}, nil } logDir := filepath.Join(common.GetUserDataDir(), "logs") entries, err := GetRecentLogs(logDir, limit) if err != nil { return nil, err } result := make([]map[string]interface{}, len(entries)) for i, entry := range entries { result[i] = map[string]interface{}{ "timestamp": entry.Timestamp.Format("2006-01-02 15:04:05"), "operation": entry.Operation, "path": entry.Path, "size": entry.Size, "is_directory": entry.IsDirectory, "success": entry.Success, "error": entry.Error, } } return result, nil } // ========== 回收站接口 ========== // GetRecycleBinEntries 获取回收站条目 func (s *FileSystemService) GetRecycleBinEntries() ([]map[string]interface{}, error) { if s.recycleBin == nil { return []map[string]interface{}{}, nil } entries := s.recycleBin.ListEntries() result := make([]map[string]interface{}, len(entries)) for i, entry := range entries { result[i] = map[string]interface{}{ "original_path": entry.OriginalPath, "deleted_path": entry.DeletedPath, "deleted_time": entry.DeletedTime.Format("2006-01-02 15:04:05"), "size": entry.Size, "is_directory": entry.IsDirectory, } } return result, nil } // RestoreFromRecycleBin 从回收站恢复文件 func (s *FileSystemService) RestoreFromRecycleBin(recyclePath string) error { if s.recycleBin == nil { return fmt.Errorf("回收站未初始化") } return s.recycleBin.RestoreFromRecycleBin(recyclePath) } // DeletePermanently 永久删除回收站中的文件 func (s *FileSystemService) DeletePermanently(recyclePath string) error { if s.recycleBin == nil { return fmt.Errorf("回收站未初始化") } return s.recycleBin.DeletePermanently(recyclePath) } // EmptyRecycleBin 清空回收站 func (s *FileSystemService) EmptyRecycleBin() error { if s.recycleBin == nil { return fmt.Errorf("回收站未初始化") } return s.recycleBin.Empty() } // ResolveShortcut 解析快捷方式(.lnk)文件,返回目标路径 func (s *FileSystemService) ResolveShortcut(lnkPath string) (targetPath string, err error) { // 验证路径 if err := s.validatePath(lnkPath); err != nil { return "", fmt.Errorf("路径验证失败: %w", err) } // 检查文件扩展名 if filepath.Ext(lnkPath) != ".lnk" { return "", fmt.Errorf("不是快捷方式文件") } // 检查文件是否存在 if _, err := os.Stat(lnkPath); os.IsNotExist(err) { return "", fmt.Errorf("快捷方式文件不存在") } // 使用 Windows PowerShell 解析 lnk 文件 // 这种方法更可靠,不需要依赖第三方库 if runtime.GOOS == "windows" { // 创建 PowerShell 脚本 psScript := fmt.Sprintf( "$shell = New-Object -ComObject WScript.Shell; "+ "$shortcut = $shell.CreateShortcut('%s'); "+ "$shortcut.TargetPath", lnkPath, ) // 执行 PowerShell 命令 cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", psScript) output, err := cmd.Output() if err != nil { return "", fmt.Errorf("解析快捷方式失败: %w", err) } // 去除空白字符 targetPath = string(output) targetPath = filepath.Clean(targetPath) // 如果目标路径为空,返回错误 if targetPath == "" || targetPath == "." { return "", fmt.Errorf("快捷方式目标路径为空") } return targetPath, nil } // 非 Windows 系统暂不支持 return "", fmt.Errorf("当前系统不支持快捷方式解析") } // Close 关闭服务,释放资源 func (s *FileSystemService) Close(ctx context.Context) error { s.mu.Lock() defer s.mu.Unlock() if !s.initialized { return nil } // 关闭审计日志 if s.auditLogger != nil { if err := s.auditLogger.Close(); err != nil { return fmt.Errorf("关闭审计日志失败: %w", err) } } s.initialized = false return nil } // ========== 全局服务实例(向后兼容)========== var ( globalService *FileSystemService globalServiceOnce sync.Once ) // GetGlobalService 获取全局文件系统服务实例(单例) // 保持向后兼容,但推荐使用依赖注入 func GetGlobalService() (*FileSystemService, error) { var initErr error globalServiceOnce.Do(func() { globalService, initErr = NewFileSystemService(DefaultConfig()) }) return globalService, initErr }