From 02ea161134af129002b08a10d3ccc81986578dbc Mon Sep 17 00:00:00 2001 From: Xiao-no-love <140927721+Xiao-no-love@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:41:45 +0800 Subject: [PATCH] =?UTF-8?q?v2.6=20=E8=AE=BE=E7=BD=AE=E9=A1=B5=E4=B8=80?= =?UTF-8?q?=E9=94=AE=E5=88=B7=E6=96=B0=E7=BC=BA=E5=A4=B1=E5=B0=81=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /api/music/refresh-covers (requireAdmin) - 设置页封面维护卡片 + 前端事件 - 真实场景验证:清空封面后可刷回 --- HANDOFF.md | 22 +++++++++++++++++++++- public/index.html | 14 ++++++++++++++ public/js/app.js | 26 ++++++++++++++++++++++++++ src/routes/music.js | 25 +++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/HANDOFF.md b/HANDOFF.md index e926d1b..3db23f9 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -456,4 +456,24 @@ python test_enrich.py # 给 5 首补封面(已跑过,5/5 成功) ### 18.7 未做 - **播放延迟调节**(HLS_TIME 运行时热更新):老板明确暂缓。 - 用户密码/管理员密码的 UI 设置界面(现走环境变量)。 -- 会话表已有 role 列,但登录失败锁定的 failMap 是内存态,重启清零(可接受)。 \ No newline at end of file +- 会话表已有 role 列,但登录失败锁定的 failMap 是内存态,重启清零(可接受)。 +--- + +## 19. v2.6 一键刷新缺失封面(已完成 2026-09-25) + +### 19.1 需求 +设置页加「刷新缺失封面」按钮:为所有没有封面的音乐重新匹配网易云封面。 + +### 19.2 实现 +- **后端**:`routes/music.js` 新增 `POST /api/music/refresh-covers`(`requireAdmin`)。遍历 `cover` 和 `cover_remote` **都为空**的歌曲,逐首调 `enrichSong` 重匹配,返回 `{total, success, fail, details}`。 +- **HTML**:`index.html` 设置页新增「封面维护」卡片(`#btnRefreshCovers` + `#coverRefreshProgress`),位于缓存管理和运行配置之间。 +- **前端**:`app.js` 绑定按钮 → confirm 确认 → POST → 显示「共 N 首,成功 X,失败 Y」→ 刷新资源列表 + 收听页封面。 + +### 19.3 验证 +- admin 调用 → `{total:0}`(无缺失时正常);user 调用 → **403**。 +- **真实场景**:手动清空 1 首歌封面 → 调接口返回 `{total:1, success:1}` → 复查无封面歌曲数归 0 ✅。 + +### 19.4 工具坑(重要教训) +- **powershell Select-String 行号 ≠ replace_lines 行号**(差约 2 行,因换行/编码处理不同),导致多段 replace_lines 反复错位、文件被搞乱。 +- **正解**:用 node 写查找小工具(`fs.readFileSync(f,"utf8").split("\n").forEach`),行号与 replace_lines 完全一致。改前必查、改后必 `node --check`。 +- 文件搞乱时:`git checkout -- ` 从最近提交恢复,比手工修快且稳。 \ No newline at end of file diff --git a/public/index.html b/public/index.html index fb82f5f..9fee1a6 100644 --- a/public/index.html +++ b/public/index.html @@ -265,6 +265,20 @@ +
+
+
+ + 封面维护 +
+ +
+
+
为所有没有封面的音乐重新匹配网易云封面(远程优先,本地兜底)
+
+
+
+
diff --git a/public/js/app.js b/public/js/app.js index df276a0..c1aaf8c 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -899,6 +899,32 @@ document.getElementById("btnCleanup").addEventListener("click", async () => { }); document.getElementById("logTail").addEventListener("change", loadLogs); +// 一键刷新缺失封面 +document.getElementById("btnRefreshCovers").addEventListener("click", async () => { + const btn = document.getElementById("btnRefreshCovers"); + const prog = document.getElementById("coverRefreshProgress"); + if (!confirm("为所有没有封面的音乐重新匹配网易云封面?")) return; + btn.disabled = true; + prog.textContent = "正在刷新,请稍候..."; + try { + const r = await post("/api/music/refresh-covers"); + if (r.total === 0) { + prog.textContent = "没有缺失封面的音乐"; + showToast("没有缺失封面的音乐", "success"); + } else { + prog.textContent = "完成:共 " + r.total + " 首,成功 " + r.success + ",失败 " + r.fail; + showToast("封面刷新完成:成功 " + r.success + "/" + r.total, r.fail ? "error" : "success"); + await loadResources(); + if (typeof loadLiveCover === "function") loadLiveCover(); + } + } catch (e) { + prog.textContent = ""; + showToast("刷新失败: " + e.message, "error"); + } finally { + btn.disabled = false; + } +}); + // ==================== Init ==================== (async function init() { try { const me = await api("/api/auth/me"); state.role = me.role || null; applyRoleUI(state.role); } catch (_) {} diff --git a/src/routes/music.js b/src/routes/music.js index 41bc777..afaf30b 100644 --- a/src/routes/music.js +++ b/src/routes/music.js @@ -265,4 +265,29 @@ router.post("/enrich", async (req, res) => { } }); +/* ==================== 一键刷新缺失封面 ==================== */ +router.post("/refresh-covers", require("../auth").requireAdmin, async (req, res) => { + const songs = store.getAllSongs(); + const missing = songs.filter((s) => !s.cover && !s.cover_remote); + let success = 0, fail = 0; + const details = []; + for (const s of missing) { + try { + const song = await meta.enrichSong(s.file); + if (song && (song.cover || song.cover_remote)) { + success++; + details.push({ file: s.file, ok: true }); + } else { + fail++; + details.push({ file: s.file, ok: false, error: "未匹配到封面" }); + } + } catch (e) { + fail++; + details.push({ file: s.file, ok: false, error: e.message }); + } + } + logger.info("[COVER] 刷新缺失封面: 共 " + missing.length + " 首,成功 " + success + ",失败 " + fail); + res.json({ ok: true, total: missing.length, success, fail, details }); +}); + module.exports = router; \ No newline at end of file