优化:文件操作精确更新,避免占用问题
后端改进:
- API 返回 FileOperationResult 结构体(类型安全)
- 所有操作返回文件信息,支持精确更新
- 删除过度抽象的接口和全局函数包装器(桌面程序不需要)
前端改进:
- 精确更新文件列表(避免整目录刷新)
- 分离 add/remove/update 三个独立函数
- 重命名前智能关闭文件/文件夹,解决占用问题
- 优化错误提示,用户友好提示
技术细节:
- 定义 FileOperationResult 结构体替代 map[string]interface{}
- 前端 API 返回类型从 void 改为 any
- 保留运行时状态(如 is_favorite)
- 智能识别文件占用错误并给出解决建议
This commit is contained in:
@@ -8,90 +8,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ========== 向后兼容的全局函数包装器 ==========
|
||||
// 这些函数提供向后兼容性,内部委托给 FileSystemService
|
||||
// 新代码应该使用 FileSystemService 而不是这些全局函数
|
||||
|
||||
// ReadFile 读取文件内容(向后兼容包装器)
|
||||
func ReadFile(path string) (string, error) {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.ReadFile(path)
|
||||
}
|
||||
|
||||
// WriteFile 写入文件(向后兼容包装器)
|
||||
func WriteFile(path, content string) error {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.WriteFile(path, content)
|
||||
}
|
||||
|
||||
// ListDir 列出目录内容(向后兼容包装器)
|
||||
func ListDir(path string) ([]map[string]interface{}, error) {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.ListDir(path)
|
||||
}
|
||||
|
||||
// CreateDir 创建目录(向后兼容包装器)
|
||||
func CreateDir(path string) error {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.CreateDir(path)
|
||||
}
|
||||
|
||||
// CreateFile 创建空文件(向后兼容包装器)
|
||||
func CreateFile(path string) error {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.CreateFile(path)
|
||||
}
|
||||
|
||||
// DeletePath 删除文件或目录(向后兼容包装器)
|
||||
func DeletePath(path string) error {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.DeletePath(path)
|
||||
}
|
||||
|
||||
// DeletePathWithConfig 使用指定配置删除文件或目录(向后兼容包装器)
|
||||
func DeletePathWithConfig(path string, config *Config) error {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
|
||||
// 临时替换服务的配置
|
||||
originalConfig := service.config
|
||||
service.config = config
|
||||
defer func() { service.config = originalConfig }()
|
||||
|
||||
return service.DeletePath(path)
|
||||
}
|
||||
|
||||
// GetFileInfo 获取文件信息(向后兼容包装器)
|
||||
func GetFileInfo(path string) (map[string]interface{}, error) {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.GetFileInfo(path)
|
||||
}
|
||||
// ========== 辅助函数 ==========
|
||||
|
||||
// OpenPath 打开文件或目录(使用系统默认程序)
|
||||
// 这是一个核心工具函数,保留为独立函数
|
||||
func OpenPath(path string) error {
|
||||
// 使用 path.validator 进行验证
|
||||
validator := NewPathValidator(DefaultConfig())
|
||||
@@ -132,16 +51,7 @@ func OpenPath(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenamePath 重命名文件或目录(向后兼容包装器)
|
||||
func RenamePath(oldPath, newPath string) error {
|
||||
service, err := GetGlobalService()
|
||||
if err != nil {
|
||||
return fmt.Errorf("服务未初始化: %v", err)
|
||||
}
|
||||
return service.RenamePath(oldPath, newPath)
|
||||
}
|
||||
|
||||
// ========== 辅助函数 ==========
|
||||
// ========== 工具函数 ==========
|
||||
|
||||
// formatBytes 格式化字节大小为人类可读格式
|
||||
func formatBytes(bytes int64) string {
|
||||
|
||||
@@ -13,6 +13,19 @@ import (
|
||||
"u-desk/internal/common"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
@@ -173,52 +186,52 @@ func (s *FileSystemService) Open(path string) error {
|
||||
}
|
||||
|
||||
// Delete 删除文件或目录(实现 FileService 接口)
|
||||
func (s *FileSystemService) Delete(path string) error {
|
||||
func (s *FileSystemService) Delete(path string) (*FileOperationResult, error) {
|
||||
return s.DeletePathWithContext(context.Background(), path)
|
||||
}
|
||||
|
||||
// DeletePath 删除文件或目录
|
||||
func (s *FileSystemService) DeletePath(path string) error {
|
||||
func (s *FileSystemService) DeletePath(path string) (*FileOperationResult, error) {
|
||||
return s.DeletePathWithContext(context.Background(), path)
|
||||
}
|
||||
|
||||
// DeletePathWithContext 带上下文的删除操作
|
||||
func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path string) error {
|
||||
// DeletePathWithContext 带上下文的删除操作,返回被删除文件的信息
|
||||
func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path string) (*FileOperationResult, error) {
|
||||
// 路径验证
|
||||
if err := s.validatePath(path); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取文件信息
|
||||
// 获取文件信息(在删除前保存)
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("文件或目录不存在")
|
||||
return nil, fmt.Errorf("文件或目录不存在")
|
||||
}
|
||||
return fmt.Errorf("获取文件信息失败: %v", err)
|
||||
return nil, fmt.Errorf("获取文件信息失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查删除限制
|
||||
exceeds, details, checkErr := CheckDeleteRestrictions(path, info, s.config)
|
||||
if checkErr != nil {
|
||||
return checkErr
|
||||
return nil, checkErr
|
||||
}
|
||||
|
||||
if exceeds {
|
||||
if s.config.Security.DeleteRestrictions.RequireConfirm {
|
||||
return &DeleteRestrictionWarning{
|
||||
return nil, &DeleteRestrictionWarning{
|
||||
Path: path,
|
||||
Details: details,
|
||||
Info: info,
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("删除限制: %s", details)
|
||||
return nil, fmt.Errorf("删除限制: %s", details)
|
||||
}
|
||||
|
||||
// 文件锁检查(可选)
|
||||
if s.lockChecker != nil {
|
||||
if err := s.lockChecker.SafeDeleteWithLockCheck(path); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +246,7 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri
|
||||
s.logDelete(path, info.IsDir(), info.Size(), deleteErr)
|
||||
|
||||
if deleteErr != nil {
|
||||
return fmt.Errorf("删除失败: %v", deleteErr)
|
||||
return nil, fmt.Errorf("删除失败: %v", deleteErr)
|
||||
}
|
||||
|
||||
// 如果启用回收站,移动到回收站而非永久删除
|
||||
@@ -247,7 +260,17 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// 返回被删除的文件信息,用于前端更新
|
||||
return &FileOperationResult{
|
||||
Path: 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 列出目录内容
|
||||
@@ -292,14 +315,14 @@ func (s *FileSystemService) ListDir(path string) ([]map[string]interface{}, erro
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateDir 创建目录
|
||||
func (s *FileSystemService) CreateDir(path string) error {
|
||||
// CreateDir 创建目录,返回创建的目录信息
|
||||
func (s *FileSystemService) CreateDir(path string) (*FileOperationResult, error) {
|
||||
if err := s.validatePath(path); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(path, DefaultDirPermissions); err != nil {
|
||||
return fmt.Errorf("创建目录失败: %v", err)
|
||||
return nil, fmt.Errorf("创建目录失败: %v", err)
|
||||
}
|
||||
|
||||
s.logAudit(AuditLogEntry{
|
||||
@@ -310,23 +333,42 @@ func (s *FileSystemService) CreateDir(path string) error {
|
||||
Success: true,
|
||||
})
|
||||
|
||||
return nil
|
||||
// 获取创建的目录信息
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
// 创建成功但获取信息失败,返回基本信息
|
||||
return &FileOperationResult{
|
||||
Path: path,
|
||||
Name: filepath.Base(path),
|
||||
IsDir: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &FileOperationResult{
|
||||
Path: 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) error {
|
||||
// CreateFile 创建空文件,返回创建的文件信息
|
||||
func (s *FileSystemService) CreateFile(path string) (*FileOperationResult, error) {
|
||||
if err := s.validatePath(path); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查文件是否已存在
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return fmt.Errorf("文件已存在")
|
||||
return nil, fmt.Errorf("文件已存在")
|
||||
}
|
||||
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建文件失败: %v", err)
|
||||
return nil, fmt.Errorf("创建文件失败: %v", err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
@@ -338,7 +380,27 @@ func (s *FileSystemService) CreateFile(path string) error {
|
||||
Success: true,
|
||||
})
|
||||
|
||||
return nil
|
||||
// 获取创建的文件信息
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
// 创建成功但获取信息失败,返回基本信息
|
||||
return &FileOperationResult{
|
||||
Path: path,
|
||||
Name: filepath.Base(path),
|
||||
IsDir: false,
|
||||
Size: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &FileOperationResult{
|
||||
Path: 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
|
||||
}
|
||||
|
||||
// GetInfo 获取文件信息(实现 FileService 接口)
|
||||
@@ -380,21 +442,21 @@ func (s *FileSystemService) OpenPath(path string) error {
|
||||
return OpenPath(path)
|
||||
}
|
||||
|
||||
// RenamePath 重命名文件或目录
|
||||
func (s *FileSystemService) RenamePath(oldPath, newPath string) error {
|
||||
// RenamePath 重命名文件或目录,返回新文件信息
|
||||
func (s *FileSystemService) RenamePath(oldPath, newPath string) (*FileOperationResult, error) {
|
||||
// 验证旧路径
|
||||
if err := s.validatePath(oldPath); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证新路径
|
||||
if err := s.validatePath(newPath); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 执行重命名
|
||||
if err := os.Rename(oldPath, newPath); err != nil {
|
||||
return fmt.Errorf("重命名失败: %v", err)
|
||||
return nil, fmt.Errorf("重命名失败: %v", err)
|
||||
}
|
||||
|
||||
s.logAudit(AuditLogEntry{
|
||||
@@ -405,7 +467,27 @@ func (s *FileSystemService) RenamePath(oldPath, newPath string) error {
|
||||
Success: true,
|
||||
})
|
||||
|
||||
return nil
|
||||
// 获取新文件信息
|
||||
info, err := os.Stat(newPath)
|
||||
if err != nil {
|
||||
// 重命名成功但获取信息失败,返回基本信息
|
||||
return &FileOperationResult{
|
||||
Path: newPath,
|
||||
Name: filepath.Base(newPath),
|
||||
OldPath: oldPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &FileOperationResult{
|
||||
Path: 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: oldPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ========== ZIP操作接口 ==========
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// FileService 文件操作核心接口
|
||||
// 定义所有文件操作的基本功能,便于mock测试
|
||||
type FileService interface {
|
||||
// 基本操作
|
||||
Read(path string) (string, error)
|
||||
Write(path, content string) error
|
||||
Delete(path string) error
|
||||
List(path string) ([]map[string]interface{}, error)
|
||||
CreateDir(path string) error
|
||||
CreateFile(path string) error
|
||||
GetInfo(path string) (map[string]interface{}, error)
|
||||
Open(path string) error
|
||||
|
||||
// 快捷方式
|
||||
ResolveShortcut(lnkPath string) (targetPath string, err error)
|
||||
|
||||
// 配置
|
||||
GetConfig() *Config
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
// 确保实现接口
|
||||
var _ FileService = (*FileSystemService)(nil)
|
||||
|
||||
Reference in New Issue
Block a user