package filesystem import ( "fmt" "os" "syscall" "time" ) // Windows API 锁相关函数和常量 var ( modkernel32 = syscall.NewLazyDLL("kernel32.dll") procGetLastError = modkernel32.NewProc("GetLastError") procGetProcessId = modkernel32.NewProc("GetProcessId") ) // FileLockChecker 文件锁检查器 type FileLockChecker struct{} // NewFileLockChecker 创建文件锁检查器 func NewFileLockChecker() *FileLockChecker { return &FileLockChecker{} } // IsFileLocked 检查文件是否被锁定(被其他进程占用) // 返回: (是否锁定, 错误信息, 错误) func (c *FileLockChecker) IsFileLocked(path string) (bool, string, error) { // 尝试以独占写模式打开文件 file, err := os.OpenFile(path, os.O_RDWR|syscall.O_CREAT, 0666) if err != nil { // 检查是否是锁相关的错误 if isLockError(err) { // 获取占用该文件的进程信息 processInfo, _ := c.getProcessInfo(path) return true, processInfo, nil } return false, "", err } defer file.Close() // 文件可以被打开,说明没有被锁定 return false, "", nil } // isLockError 判断错误是否为文件锁定错误 func isLockError(err error) bool { if err == nil { return false } // 检查错误类型 if os.IsPermission(err) { return true } // Windows 特定错误检查 if pathErr, ok := err.(*os.PathError); ok { errno, ok := pathErr.Err.(syscall.Errno) if ok && (errno == ERROR_SHARING_VIOLATION || errno == ERROR_LOCK_VIOLATION || errno == syscall.ERROR_ACCESS_DENIED) { return true } } errStr := err.Error() lockErrorStrings := []string{ "used by another process", "being used", "access is denied", "could not be opened", "being used by another process", "process cannot access the file", "used by another process", } for _, lockStr := range lockErrorStrings { if contains(errStr, lockStr) { return true } } return false } // getProcessInfo 获取占用文件的进程信息(Windows专用) func (c *FileLockChecker) getProcessInfo(path string) (string, error) { // 在Windows上,使用重启管理器API查询文件占用 // 这里提供简化版本 // 尝试打开文件获取更多信息 handle, err := syscall.Open(path, syscall.O_RDONLY, 0) if err != nil { // 如果打开失败,返回通用提示 return "", nil } defer syscall.Close(handle) // 使用 Windows API 查询文件信息 // 注意:这需要更复杂的 Windows API 调用 // 这里返回简化的提示信息 return "文件正被其他程序使用", nil } // CheckFileWithRetry 带重试的文件锁检查 func (c *FileLockChecker) CheckFileWithRetry(path string, maxRetries int, retryInterval time.Duration) error { for i := 0; i < maxRetries; i++ { locked, processInfo, err := c.IsFileLocked(path) if err != nil && !locked { // 非锁相关的错误,直接返回 return err } if !locked { // 文件未被锁定,可以操作 return nil } // 文件被锁定 if i < maxRetries-1 { // 还有重试机会,等待后重试 time.Sleep(retryInterval) continue } // 最后一次重试失败,返回错误 if processInfo != "" { return fmt.Errorf("文件被占用: %s", processInfo) } return fmt.Errorf("文件被其他程序占用,请关闭相关程序后重试") } return fmt.Errorf("文件检查超时") } // SafeDeleteWithLockCheck 带锁检查的安全删除 func (c *FileLockChecker) SafeDeleteWithLockCheck(path string) error { // 检查文件是否被锁定 locked, processInfo, err := c.IsFileLocked(path) if err != nil && !locked { return err } if locked { if processInfo != "" { return fmt.Errorf("无法删除文件:文件正被其他程序使用\n\n提示:%s\n\n请关闭相关程序后重试", processInfo) } return fmt.Errorf("无法删除文件:文件正被其他程序使用\n\n请关闭相关程序后重试") } // 文件未被锁定,继续删除 return nil } // Windows 特定的结构体和常量 const ( ERROR_LOCK_VIOLATION = 33 // syscall.Errno(33) ERROR_SHARING_VIOLATION = 32 // syscall.Errno(32) ) // BY_HANDLE_FILE_INFORMATION 文件信息结构体 type BY_HANDLE_FILE_INFORMATION struct { FileAttributes uint32 CreationTime syscall.Filetime LastAccessTime syscall.Filetime LastWriteTime syscall.Filetime VolumeSerialNumber uint32 FileSizeHigh uint32 FileSizeLow uint32 NumberOfLinks uint32 FileIndexHigh uint32 FileIndexLow uint32 } // contains 检查字符串是否包含子串(不区分大小写) func contains(str, substr string) bool { return len(str) >= len(substr) && (str == substr || len(substr) == 0 || (len(str) > 0 && len(substr) > 0 && containsIgnoreCase(str, substr))) } func containsIgnoreCase(str, substr string) bool { // 简化版小写比较 for i := 0; i <= len(str)-len(substr); i++ { match := true for j := 0; j < len(substr); j++ { c1 := str[i+j] c2 := substr[j] if c1 >= 'A' && c1 <= 'Z' { c1 += 32 } if c2 >= 'A' && c2 <= 'Z' { c2 += 32 } if c1 != c2 { match = false break } } if match { return true } } return false } // 全局文件锁检查器 var globalLockChecker *FileLockChecker // InitFileLockChecker 初始化全局文件锁检查器 func InitFileLockChecker() { globalLockChecker = NewFileLockChecker() } // GetFileLockChecker 获取全局文件锁检查器 func GetFileLockChecker() *FileLockChecker { if globalLockChecker == nil { globalLockChecker = NewFileLockChecker() } return globalLockChecker }