From 26c7aaff6615a8f8a775276d0311616f6c046312 Mon Sep 17 00:00:00 2001 From: lofyer Date: Sun, 9 Aug 2026 15:48:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E9=95=9C=E5=83=8F=E4=BC=98=E5=85=88=E7=BA=A7=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E5=B8=83=20v2.1.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户配置自定义 Z-Library 镜像后,旧会话记录的内置镜像仍被排在前面, 与界面“自定义优先”的约定不一致。调整请求顺序,让自定义镜像严格优先; 全部镜像失败时保留首个自定义镜像的错误,避免被后续兜底错误覆盖。 版本更新到 2.1.6,并补充优先级与错误归因回归测试。 --- package-lock.json | 4 ++-- package.json | 2 +- src/_test/sources.test.js | 47 +++++++++++++++++++++++++++++++++++++++ src/sources/zlib.js | 21 +++++++++++++---- 4 files changed, 67 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58f093a..ce34126 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "peoplelib", - "version": "2.1.5", + "version": "2.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "peoplelib", - "version": "2.1.5", + "version": "2.1.6", "license": "MIT", "dependencies": { "foliate-js": "1.0.1", diff --git a/package.json b/package.json index f311d91..57a7550 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peoplelib", - "version": "2.1.5", + "version": "2.1.6", "description": "多源开放文献、电子书与本地书库客户端", "main": "main.js", "author": "peoplelib", diff --git a/src/_test/sources.test.js b/src/_test/sources.test.js index 747dd50..ad6f3bf 100644 --- a/src/_test/sources.test.js +++ b/src/_test/sources.test.js @@ -624,6 +624,53 @@ test('zlib: 移除当前会话的非内置镜像会清除旧会话', () => { } }); +test('zlib: 自定义镜像优先于旧会话记录的内置镜像', async () => { + const zlib = h.freshRequire('sources/zlib.js'); + const auth = require('../sources/zlib-auth'); + const orig = { + getCustomMirrors: auth.getCustomMirrors, + getSession: auth.getSession, + setSession: auth.setSession + }; + auth.getCustomMirrors = () => ['https://custom.example']; + auth.getSession = () => ({ userId: '1', userKey: 'k', mirror: 'https://1lib.sk' }); + auth.setSession = () => {}; + try { + h.resetCalls(); + h.setHandler((url) => { + assert.ok(url.startsWith('https://custom.example/'), `先请求了旧会话镜像:${url}`); + return h.makeResponse({ body: { success: 1, books: [] } }); + }); + await zlib.list(1); + assert.strictEqual(h.getCalls().length, 1, '自定义镜像成功后仍请求了内置镜像'); + } finally { + Object.assign(auth, orig); + } +}); + +test('zlib: 全部失败时保留自定义镜像的首要错误', async () => { + const zlib = h.freshRequire('sources/zlib.js'); + const auth = require('../sources/zlib-auth'); + const orig = { + getCustomMirrors: auth.getCustomMirrors, + getSession: auth.getSession + }; + auth.getCustomMirrors = () => ['https://custom.example']; + auth.getSession = () => ({ userId: '1', userKey: 'k', mirror: 'https://1lib.sk' }); + try { + h.setHandler((url) => { + if (url.startsWith('https://custom.example/')) throw new Error('请求超时,站点无响应'); + throw new Error('该镜像返回了非预期内容'); + }); + await assert.rejects( + zlib.list(1), + /自定义镜像失败:请求超时,站点无响应;其余 \d+ 个镜像也未成功/ + ); + } finally { + Object.assign(auth, orig); + } +}); + // 实测:会话过期时 /file 返回 400 + {"success":0,"error":"Please login"}。 // 若按 HTTP 状态码短路,真实原因会被吞掉,自动重登也不会触发。 test('zlib: 4xx+JSON 的会话过期能被识别并自动重新登录', async () => { diff --git a/src/sources/zlib.js b/src/sources/zlib.js index dfad890..5e601d9 100644 --- a/src/sources/zlib.js +++ b/src/sources/zlib.js @@ -215,12 +215,17 @@ async function callOn(mirror, path, params, session, method) { } async function attempt(session, path, params, method) { + const custom = auth.getCustomMirrors(); const mirrors = getMirrors(); - const ordered = session.mirror + // 用户显式配置镜像后必须严格优先于旧会话镜像,否则界面写着“自定义优先”, + // 实际却仍先等待上次成功但现在已失效的默认域名。 + const ordered = custom.length + ? mirrors + : session.mirror ? [session.mirror, ...mirrors.filter((m) => m !== session.mirror)] : mirrors; - let lastErr; + const errors = []; for (const m of ordered) { try { const j = await callOn(m, path, params, session, method); @@ -228,10 +233,18 @@ async function attempt(session, path, params, method) { return { ok: true, data: j }; } catch (e) { if (e.code === 'AUTH_STALE') return { ok: false, stale: true, error: e }; - lastErr = e; + errors.push(e); } } - return { ok: false, stale: false, error: lastErr }; + if (errors.length <= 1) { + return { ok: false, stale: false, error: errors[0] }; + } + const first = errors[0]; + const label = custom.length ? '自定义镜像失败' : '首选镜像失败'; + const summary = new Error( + `${label}:${(first && first.message) || String(first)};其余 ${errors.length - 1} 个镜像也未成功` + ); + return { ok: false, stale: false, error: summary }; } async function apiCall(path, params = {}, method = 'GET') {