fix(main): 修复下载过期无法恢复、配置写放大、主进程 i18n 崩溃

- downloadManager: axios 放行 403/410,让直链过期能触发重新解析(修复重启恢复队列时下载失效)
- cache: getCacheConfig 纯读不再落盘 config.json,消除播放/下载热路径的写放大与文件争用
- i18n/main: 未知/非法 locale 回退默认语言并加空值防护,避免托盘/登录窗构建时崩溃
- lxMusicHttp: 超时定时器在 fetch 出错路径也清理
- loginWindow: 标题改为打开窗口时求值,跟随当前语言
This commit is contained in:
alger
2026-07-05 19:53:12 +08:00
parent 095b5c3029
commit 58d4ffee2a
5 changed files with 26 additions and 8 deletions
+6 -2
View File
@@ -18,9 +18,13 @@ const mainI18n = {
}, },
t(key: string) { t(key: string) {
const keys = key.split('.'); const keys = key.split('.');
let current: any = messages[this.currentLocale]; // 未知/非法 locale 时回退默认语言,避免 messages[locale] 为 undefined 导致崩溃
let current: any = messages[this.currentLocale] ?? messages[DEFAULT_LANGUAGE as Language];
if (current == null) {
return key;
}
for (const k of keys) { for (const k of keys) {
if (current[k] === undefined) { if (current == null || current[k] === undefined) {
// 如果找不到翻译,返回键名 // 如果找不到翻译,返回键名
return key; return key;
} }
+3 -1
View File
@@ -413,7 +413,9 @@ class DiskCacheManager {
cleanupPolicy cleanupPolicy
}; };
this.saveConfig(normalizedConfig); // 注意:getCacheConfig 是纯读取,处于播放/下载/歌词等多个热路径。
// 此处不再落盘(electron-store.set 每次整文件写 config.json,会造成写放大与文件争用),
// 持久化交由 updateCacheConfig / setCacheDirectory 等真正的写操作完成。
return normalizedConfig; return normalizedConfig;
} }
+7 -1
View File
@@ -484,13 +484,17 @@ class DownloadManager {
} }
// Start download // Start download
// 注意:axios 默认只接受 2xx403/410 会直接抛错进入 catch,导致下方"直链过期重新解析"
// 分支永远走不到。这里放行 403/410,让过期直链能触发重新解析(尤其是重启后恢复队列时)。
const response = await axios({ const response = await axios({
url: task.url, url: task.url,
method: 'GET', method: 'GET',
responseType: 'stream', responseType: 'stream',
timeout: 30000, timeout: 30000,
signal: controller.signal, signal: controller.signal,
headers headers,
validateStatus: (status) =>
(status >= 200 && status < 300) || status === 403 || status === 410
}); });
// Handle response status // Handle response status
@@ -498,6 +502,8 @@ class DownloadManager {
if (status === 403 || status === 410) { if (status === 403 || status === 410) {
// URL expired, request re-resolution from renderer // URL expired, request re-resolution from renderer
// 排空未消费的错误响应流,避免连接悬挂
response.data?.destroy?.();
this.sendToRenderer('download:request-url', { this.sendToRenderer('download:request-url', {
taskId: task.taskId, taskId: task.taskId,
songInfo: task.songInfo songInfo: task.songInfo
+2 -2
View File
@@ -6,7 +6,6 @@ import i18n from '../../i18n/main';
let loginWindow: BrowserWindow | null = null; let loginWindow: BrowserWindow | null = null;
const loginUrl = 'https://music.163.com/#/login/'; const loginUrl = 'https://music.163.com/#/login/';
const loginTitle = i18n.global.t('login.qrTitle');
/** /**
* 打开登录窗口获取Cookie * 打开登录窗口获取Cookie
@@ -29,7 +28,8 @@ const openLoginWindow = async (mainWin: BrowserWindow) => {
loginWindow = new BrowserWindow({ loginWindow = new BrowserWindow({
parent: mainWin, parent: mainWin,
title: loginTitle, // 在打开窗口时求值,确保跟随当前语言(模块加载时 i18n locale 可能尚未设置)
title: i18n.global.t('login.qrTitle'),
width: 1280, width: 1280,
height: 800, height: 800,
center: true, center: true,
+8 -2
View File
@@ -42,6 +42,8 @@ export const initLxMusicHttp = () => {
// 保存取消控制器 // 保存取消控制器
abortControllers.set(requestId, controller); abortControllers.set(requestId, controller);
let timeoutId: ReturnType<typeof setTimeout> | null = null;
try { try {
console.log(`[LxMusicHttp] 请求: ${options.method || 'GET'} ${url}`); console.log(`[LxMusicHttp] 请求: ${options.method || 'GET'} ${url}`);
@@ -77,13 +79,14 @@ export const initLxMusicHttp = () => {
// 设置超时 // 设置超时
const timeout = options.timeout || 30000; const timeout = options.timeout || 30000;
const timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
console.warn(`[LxMusicHttp] 请求超时: ${url}`); console.warn(`[LxMusicHttp] 请求超时: ${url}`);
controller.abort(); controller.abort();
}, timeout); }, timeout);
const response = await fetch(url, fetchOptions); const response = await fetch(url, fetchOptions);
clearTimeout(timeoutId); clearTimeout(timeoutId);
timeoutId = null;
console.log(`[LxMusicHttp] 响应: ${response.status} ${url}`); console.log(`[LxMusicHttp] 响应: ${response.status} ${url}`);
@@ -122,7 +125,10 @@ export const initLxMusicHttp = () => {
console.error(`[LxMusicHttp] 请求失败: ${url}`, error.message); console.error(`[LxMusicHttp] 请求失败: ${url}`, error.message);
throw error; throw error;
} finally { } finally {
// 清理取消控制器 // 清理超时定时器(fetch 出错时前面来不及 clear)与取消控制器
if (timeoutId) {
clearTimeout(timeoutId);
}
abortControllers.delete(requestId); abortControllers.delete(requestId);
} }
} }