// Package mover 负责文件的移动,跨文件系统时自动回退为复制后删除。 package mover import ( "fmt" "io" "os" "path/filepath" "runtime" "strings" "sync" "sync/atomic" uuid "github.com/satori/go.uuid" ) // Mover 文件移动器 type Mover struct{} // workerCount 并行移动的 worker 数量:不超过 CPU 核数,限制在 1~4 之间。 // 移动/复制是磁盘 I/O 密集型操作,过高的并行度反而会降低吞吐(尤其机械硬盘)。 func workerCount() int { n := runtime.NumCPU() if n < 1 { return 1 } if n > 4 { return 4 } return n } // MoveToDir 将一批文件并行移动到目标目录(目标文件名使用 uuid 前缀防止同名覆盖)。 // 返回移动成功的 源路径 → 完整目标文件路径 映射,供记录去向使用。 // onProgress 在每移动一个文件后调用(done 为累计成功移动数,并发安全、单调递增)。 func (m *Mover) MoveToDir(filePaths []string, targetDir string, onProgress func(done int)) map[string]string { moved := make(map[string]string) if len(filePaths) == 0 { return moved } if err := os.MkdirAll(targetDir, 0755); err != nil { fmt.Printf("=== 创建目录失败: %s, 错误: %v\n", targetDir, err) return moved } results := make([]string, len(filePaths)) workers := workerCount() if workers > len(filePaths) { workers = len(filePaths) } var wg sync.WaitGroup var doneCount int32 jobs := make(chan int) for w := 0; w < workers; w++ { wg.Add(1) go func() { defer wg.Done() for i := range jobs { if target, ok := moveOne(filePaths[i], targetDir); ok { results[i] = target } if onProgress != nil { onProgress(int(atomic.AddInt32(&doneCount, 1))) } } }() } for i := range filePaths { jobs <- i } close(jobs) wg.Wait() for i, target := range results { if target != "" { moved[filePaths[i]] = target } } return moved } // moveOne 移动单个文件;跨文件系统时 fallback 为 copy+delete。 // 成功时返回完整目标文件路径;部分成功(复制成功删除源失败)同样返回目标路径。 func moveOne(src, targetDir string) (string, bool) { fileName := strings.ReplaceAll(uuid.NewV4().String(), "-", "") + "_" + filepath.Base(src) target := filepath.Join(targetDir, fileName) if err := os.Rename(src, target); err == nil { return target, true } if err := copyFile(src, target); err != nil { fmt.Printf("=== 复制文件失败, 源: %s, 目标: %s, 错误: %v\n", src, target, err) return "", false } if err := os.Remove(src); err != nil { fmt.Printf("=== 警告: 复制成功但删除源文件失败, 源与目标同时存在, 源: %s, 错误: %v\n", src, err) } return target, true } // copyFile 逐块复制文件;失败时清理已写入的目标文件,避免残留半截文件 func copyFile(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dst) if err != nil { return err } if _, err := io.Copy(out, in); err != nil { out.Close() _ = os.Remove(dst) return err } if err := out.Close(); err != nil { _ = os.Remove(dst) return err } return nil }