Improve music processing robustness and workflow UX

Unify safe file-move behavior and richer progress semantics across backend tasks, while upgrading traditional-to-simplified conversion and refining the frontend multi-step panels for clearer execution feedback.
This commit is contained in:
2026-03-08 04:26:18 +08:00
parent 20a70270c7
commit 81977a157e
39 changed files with 2131 additions and 1511 deletions

View File

@@ -1,5 +1,6 @@
package com.music.service;
import com.music.common.FileTransferUtils;
import com.music.dto.ProgressMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,6 +30,9 @@ public class LibraryMergeService {
private static final Set<String> LYRICS_EXTENSIONS = new HashSet<>(Arrays.asList("lrc"));
private static final Set<String> COVER_NAMES = new HashSet<>(Arrays.asList("cover.jpg", "cover.png", "folder.jpg", "folder.png"));
private static final Set<String> EXCLUDED_ROOT_DIRS = new HashSet<>(Arrays.asList(
"_Manual_Fix_Required_", "_Reports"
));
private final SimpMessagingTemplate messagingTemplate;
private final ProgressStore progressStore;
@@ -46,19 +50,19 @@ public class LibraryMergeService {
try {
// 基本校验
if (srcDir == null || srcDir.trim().isEmpty()) {
sendProgress(taskId, 0, 0, 0, 0, 0, null, "源目录不能为空", true);
sendProgress(taskId, 0, 0, 0, 0, 0, 0, null, "源目录不能为空", true);
return;
}
if (!Files.exists(srcPath) || !Files.isDirectory(srcPath)) {
sendProgress(taskId, 0, 0, 0, 0, 0, null, "源目录不存在或不是目录", true);
sendProgress(taskId, 0, 0, 0, 0, 0, 0, null, "源目录不存在或不是目录", true);
return;
}
if (dstDir == null || dstDir.trim().isEmpty()) {
sendProgress(taskId, 0, 0, 0, 0, 0, null, "目标目录不能为空", true);
sendProgress(taskId, 0, 0, 0, 0, 0, 0, null, "目标目录不能为空", true);
return;
}
if (srcPath.normalize().equals(dstPath.normalize())) {
sendProgress(taskId, 0, 0, 0, 0, 0, null, "源目录与目标目录不能相同", true);
sendProgress(taskId, 0, 0, 0, 0, 0, 0, null, "源目录与目标目录不能相同", true);
return;
}
@@ -72,6 +76,14 @@ public class LibraryMergeService {
Map<Path, Path> coverMap = new HashMap<>(); // 专辑目录 -> 封面文件
Files.walkFileTree(srcPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (isExcludedSystemDirectory(srcPath, dir)) {
return FileVisitResult.SKIP_SUBTREE;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
String fileName = file.getFileName().toString().toLowerCase();
@@ -97,7 +109,7 @@ public class LibraryMergeService {
int total = audioFiles.size();
if (total == 0) {
sendProgress(taskId, 0, 0, 0, 0, 0, null, "未在源目录中找到音频文件", true);
sendProgress(taskId, 0, 0, 0, 0, 0, 0, null, "未在源目录中找到音频文件", true);
return;
}
@@ -109,7 +121,7 @@ public class LibraryMergeService {
Set<String> processedAlbums = new HashSet<>();
sendProgress(taskId, total, 0, 0, 0, 0, null, "开始合并...", false);
sendProgress(taskId, total, 0, 0, 0, 0, 0, null, "开始合并...", false);
for (Path audioFile : audioFiles) {
try {
@@ -141,7 +153,7 @@ public class LibraryMergeService {
targetPath.getFileName().toString() + ".backup");
Files.copy(targetPath, backupPath, StandardCopyOption.REPLACE_EXISTING);
}
Files.move(audioFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
FileTransferUtils.moveWithFallback(audioFile, targetPath);
wasUpgraded = true;
upgraded.incrementAndGet();
} else {
@@ -149,7 +161,7 @@ public class LibraryMergeService {
int p = processed.incrementAndGet();
if (p % 20 == 0 || p == total) {
sendProgress(taskId, total, p, albumsMerged.get(), tracksMerged.get(),
upgraded.get(), audioFile.getFileName().toString(),
upgraded.get(), skipped.get(), audioFile.getFileName().toString(),
String.format("已处理 %d/%d (升级: %d)", p, total, upgraded.get()), false);
}
continue; // 跳过,不升级
@@ -160,7 +172,7 @@ public class LibraryMergeService {
int p = processed.incrementAndGet();
if (p % 20 == 0 || p == total) {
sendProgress(taskId, total, p, albumsMerged.get(), tracksMerged.get(),
upgraded.get(), audioFile.getFileName().toString(),
upgraded.get(), skipped.get(), audioFile.getFileName().toString(),
String.format("已处理 %d/%d (升级: %d)", p, total, upgraded.get()), false);
}
continue;
@@ -168,7 +180,7 @@ public class LibraryMergeService {
} else {
// 新文件,直接移动
Files.createDirectories(targetPath.getParent());
Files.move(audioFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
FileTransferUtils.moveWithFallback(audioFile, targetPath);
}
tracksMerged.incrementAndGet();
@@ -178,7 +190,7 @@ public class LibraryMergeService {
if (lyricsFile != null && Files.exists(lyricsFile)) {
Path lyricsTarget = targetPath.resolveSibling(lyricsFile.getFileName().toString());
if (!Files.exists(lyricsTarget) || wasUpgraded) {
Files.move(lyricsFile, lyricsTarget, StandardCopyOption.REPLACE_EXISTING);
FileTransferUtils.moveWithFallback(lyricsFile, lyricsTarget);
}
}
@@ -190,7 +202,7 @@ public class LibraryMergeService {
Path coverTarget = albumDir.resolve("cover.jpg");
if (!Files.exists(coverTarget)) {
// 目标目录没有封面,直接移动
Files.move(coverFile, coverTarget, StandardCopyOption.REPLACE_EXISTING);
FileTransferUtils.moveWithFallback(coverFile, coverTarget);
} else {
// 比较封面,保留更好的版本
if (isBetterCover(coverFile, coverTarget)) {
@@ -198,7 +210,7 @@ public class LibraryMergeService {
Path backupPath = coverTarget.resolveSibling("cover.jpg.backup");
Files.copy(coverTarget, backupPath, StandardCopyOption.REPLACE_EXISTING);
}
Files.move(coverFile, coverTarget, StandardCopyOption.REPLACE_EXISTING);
FileTransferUtils.moveWithFallback(coverFile, coverTarget);
}
}
}
@@ -212,20 +224,20 @@ public class LibraryMergeService {
int p = processed.incrementAndGet();
if (p % 20 == 0 || p == total) {
sendProgress(taskId, total, p, albumsMerged.get(), tracksMerged.get(),
upgraded.get(), audioFile.getFileName().toString(),
upgraded.get(), skipped.get(), audioFile.getFileName().toString(),
String.format("已处理 %d/%d (升级: %d)", p, total, upgraded.get()), false);
}
}
sendProgress(taskId, total, processed.get(), albumsMerged.get(), tracksMerged.get(),
upgraded.get(), null,
upgraded.get(), skipped.get(), null,
String.format("合并完成!专辑: %d, 曲目: %d, 升级: %d, 跳过: %d",
albumsMerged.get(), tracksMerged.get(), upgraded.get(), skipped.get()),
true);
} catch (Exception e) {
log.error("合并任务执行失败", e);
sendProgress(taskId, 0, 0, 0, 0, 0, null, "任务执行失败: " + e.getMessage(), true);
sendProgress(taskId, 0, 0, 0, 0, 0, 0, null, "任务执行失败: " + e.getMessage(), true);
}
}
@@ -325,11 +337,29 @@ public class LibraryMergeService {
return i > 0 ? fileName.substring(0, i) : fileName;
}
private boolean isExcludedSystemDirectory(Path sourceRoot, Path dir) {
if (dir == null || sourceRoot == null) {
return false;
}
if (dir.equals(sourceRoot)) {
return false;
}
Path relative = sourceRoot.relativize(dir);
if (relative.getNameCount() <= 0) {
return false;
}
String firstSegment = relative.getName(0).toString();
return EXCLUDED_ROOT_DIRS.contains(firstSegment);
}
/**
* 发送进度消息
*/
private void sendProgress(String taskId, int total, int processed, int albumsMerged,
int tracksMerged, int upgraded, String currentFile, String message, boolean completed) {
int tracksMerged, int upgraded, int skipped,
String currentFile, String message, boolean completed) {
try {
ProgressMessage pm = new ProgressMessage();
pm.setTaskId(taskId);
@@ -338,6 +368,10 @@ public class LibraryMergeService {
pm.setProcessed(processed);
pm.setSuccess(albumsMerged); // 使用 success 字段存储专辑数
pm.setFailed(tracksMerged); // 使用 failed 字段存储曲目数
pm.setAlbumsMerged(albumsMerged);
pm.setTracksMerged(tracksMerged);
pm.setUpgradedFiles(upgraded);
pm.setSkippedFiles(skipped);
pm.setCurrentFile(currentFile);
pm.setMessage(message);
pm.setCompleted(completed);