package workflow import ( "bytes" "context" "crypto/sha1" "encoding/hex" "fmt" "github.com/google/uuid" "go.uber.org/zap" "os" "os/exec" "rear/internal/api" "rear/pkg/logger" "rear/pkg/utils" "runtime" "sync" "time" ) // --- 状态定义 --- type TaskStatus string const ( StatusPending TaskStatus = "pending" StatusRunning TaskStatus = "running" StatusPaused TaskStatus = "paused" StatusFailed TaskStatus = "failed" StatusDone TaskStatus = "done" ) // --- PictureTask --- type PictureTask struct { ID string Path string Hash string ImageBuf []byte ExifData map[string]string Status TaskStatus Progress float64 Error error ctx context.Context cancel context.CancelFunc mu sync.Mutex pauseCh chan struct{} resumeCh chan struct{} } func NewPictureTask(path string) *PictureTask { ctx, cancel := context.WithCancel(context.Background()) return &PictureTask{ ID: uuid.New().String(), Path: path, Status: StatusPending, ctx: ctx, cancel: cancel, pauseCh: make(chan struct{}, 1), resumeCh: make(chan struct{}, 1), } } func (pt *PictureTask) waitIfPaused() { select { case <-pt.pauseCh: pt.setStatus(StatusPaused) <-pt.resumeCh pt.setStatus(StatusRunning) default: } } func (pt *PictureTask) Pause() { select { case pt.pauseCh <- struct{}{}: default: } } func (pt *PictureTask) Resume() { select { case pt.resumeCh <- struct{}{}: default: } } func (pt *PictureTask) Run() { pt.setStatus(StatusRunning) // 初始化文件处理 imageAPI, _ := api.NewImageAPI(pt.Path) // 获取基础信息 err := imageAPI.GetExif() if err != nil { return } // 获取原图路径 // 获取压缩图片路径 imageAPI.GetImage() return // 分割 Hash 路径 //utils.HashUtils.HashThumbPath(config.CONFIG.AppDir, hash) // 获取 exif // 保存到数据库 pt.waitIfPaused() data, err := os.ReadFile(pt.Path) if err != nil { pt.setError(err) logger.Error( "文件读取失败!", zap.String("filename", pt.Path), zap.Error(err), ) return } pt.ImageBuf = data pt.Hash = calcHash(data) pt.waitIfPaused() compressed, err := compressImage(data) if err != nil { pt.setError(err) return } pt.ImageBuf = compressed pt.waitIfPaused() exif, err := readExifViaCmd(pt.Path) if err != nil { pt.setError(err) return } pt.ExifData = exif pt.waitIfPaused() err = saveToDB(pt.Hash, exif) if err != nil { pt.setError(err) return } pt.setDone() } func (pt *PictureTask) setError(err error) { pt.mu.Lock() defer pt.mu.Unlock() pt.Status = StatusFailed pt.Error = err } func (pt *PictureTask) setDone() { pt.mu.Lock() defer pt.mu.Unlock() pt.Status = StatusDone pt.Progress = 1.0 } func (pt *PictureTask) setStatus(s TaskStatus) { pt.mu.Lock() defer pt.mu.Unlock() pt.Status = s } // --- ImgTaskManager --- type ImgTaskManager struct { tasks map[string]*PictureTask queue chan *PictureTask workerPool chan struct{} workerLimit int mu sync.RWMutex globalPause chan struct{} globalResume chan struct{} poolMu sync.Mutex doneCount int autoAdjust bool } func NewImgTaskManager(concurrency int) *ImgTaskManager { tm := &ImgTaskManager{ tasks: make(map[string]*PictureTask), queue: make(chan *PictureTask, 100), workerPool: make(chan struct{}, concurrency), workerLimit: concurrency, globalPause: make(chan struct{}, 1), globalResume: make(chan struct{}, 1), autoAdjust: true, } go tm.run() go tm.monitorCPU() return tm } func (tm *ImgTaskManager) SetConcurrency(n int) { tm.poolMu.Lock() defer tm.poolMu.Unlock() if n <= 0 || n == tm.workerLimit { return } newPool := make(chan struct{}, n) tm.mu.Lock() used := len(tm.workerPool) for i := 0; i < used && i < n; i++ { newPool <- struct{}{} } tm.workerPool = newPool tm.workerLimit = n tm.mu.Unlock() } func (tm *ImgTaskManager) AddTask(path string) string { task := NewPictureTask(path) tm.mu.Lock() tm.tasks[task.ID] = task tm.mu.Unlock() tm.queue <- task return task.ID } func (tm *ImgTaskManager) PauseAll() { select { case tm.globalPause <- struct{}{}: default: } } func (tm *ImgTaskManager) ResumeAll() { select { case tm.globalResume <- struct{}{}: default: } } func (tm *ImgTaskManager) applyGlobalPauseResume(task *PictureTask) { go func() { for { select { case <-tm.globalPause: task.Pause() case <-tm.globalResume: task.Resume() } } }() } func (tm *ImgTaskManager) run() { for task := range tm.queue { tm.workerPool <- struct{}{} tm.applyGlobalPauseResume(task) go func(t *PictureTask) { defer func() { <-tm.workerPool }() t.Run() tm.mu.Lock() if t.Status == StatusDone { tm.doneCount++ } tm.mu.Unlock() }(task) } } func (tm *ImgTaskManager) GetStatus(id string) TaskStatus { tm.mu.RLock() defer tm.mu.RUnlock() if task, ok := tm.tasks[id]; ok { return task.Status } return "not_found" } func (tm *ImgTaskManager) DoneCount() int { tm.mu.RLock() defer tm.mu.RUnlock() return tm.doneCount } func (tm *ImgTaskManager) RemainingCount() int { tm.mu.RLock() defer tm.mu.RUnlock() return len(tm.tasks) - tm.doneCount } func (tm *ImgTaskManager) monitorCPU() { ticker := time.NewTicker(5 * time.Second) for range ticker.C { if !tm.autoAdjust { continue } cpuPercent := getCurrentCPUUsage() if cpuPercent > 80 { tm.SetConcurrency(tm.workerLimit / 2) } else if cpuPercent < 40 { tm.SetConcurrency(tm.workerLimit + 1) } } } // 模拟 CPU 使用率函数(替换为真实库,如 gopsutil) func getCurrentCPUUsage() int { return runtime.NumGoroutine() * 5 // 简单估算 } // --- 工具函数 --- func fileExists(path string) bool { return utils.FileUtils.Exists(path) } func calcHash(data []byte) string { logger.Info("calcHash") h := sha1.Sum(data) return hex.EncodeToString(h[:]) } func compressImage(data []byte) ([]byte, error) { logger.Info("compressImage") time.Sleep(100 * time.Millisecond) // 模拟耗时 return data, nil } func readExifViaCmd(filePath string) (map[string]string, error) { logger.Info("readExifViaCmd") cmd := exec.Command("exiftool", filePath) out, err := cmd.Output() if err != nil { return nil, err } result := make(map[string]string) lines := bytes.Split(out, []byte("\n")) for _, line := range lines { kv := bytes.SplitN(line, []byte(":"), 2) if len(kv) == 2 { key := string(bytes.TrimSpace(kv[0])) val := string(bytes.TrimSpace(kv[1])) result[key] = val } } return result, nil } func saveToDB(hash string, exif map[string]string) error { logger.Info("saveToDB") fmt.Printf("Saving %s to DB with %d exif fields\n", hash, len(exif)) return nil }