From 534967c4ea475960ac342afdc9272ddea86358ae Mon Sep 17 00:00:00 2001 From: sirius60111 Date: Fri, 24 Jul 2026 05:22:35 +0800 Subject: [PATCH] fix(route/xueqiu): serialize show.json requests and skip caching transient failures (#22708) Co-authored-by: Claude Opus 4.8 (1M context) --- lib/routes/xueqiu/user.ts | 66 ++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/lib/routes/xueqiu/user.ts b/lib/routes/xueqiu/user.ts index 97b0bd244..e3056c1e2 100644 --- a/lib/routes/xueqiu/user.ts +++ b/lib/routes/xueqiu/user.ts @@ -1,4 +1,5 @@ import sanitizeHtml from 'sanitize-html'; +import pMap from 'p-map'; import { parseToken } from '@/routes/xueqiu/cookies'; import type { Route } from '@/types'; @@ -115,36 +116,45 @@ async function handler(ctx) { const data = response.statuses.filter((s) => s.mark !== 1); // 去除置顶动态 - const items = await Promise.all( - data.map((item) => - cache.tryGet(item.target, async () => { - // legal_user_visible 为 true 时列表已含完整内容,无需再请求详情 - if (item.legal_user_visible) { - return buildListItem(item); - } + // Use p-map to limit concurrency and avoid triggering Xueqiu show.json rate limiting. + const items = await pMap( + data, + async (item) => { + try { + return await cache.tryGet(item.target, async () => { + // legal_user_visible 为 true 时列表已含完整内容,无需再请求详情 + if (item.legal_user_visible) { + return buildListItem(item); + } - try { - const detail = await ofetch(`${apiUrl}/statuses/show.json`, { - query: { - id: item.id, - }, - headers: { - Cookie: cookie, - Referer: link, - }, - }); + try { + const detail = await ofetch(`${apiUrl}/statuses/show.json`, { + query: { id: item.id }, + headers: { Cookie: cookie, Referer: link }, + }); - return { - title: buildTitle(item, detail), - description: buildDescription(detail), - pubDate: parseDate(item.created_at), - link: rootUrl + item.target, - }; - } catch { - return buildListItem(item); - } - }) - ) + return { + title: buildTitle(item, detail), + description: buildDescription(detail), + pubDate: parseDate(item.created_at), + link: rootUrl + item.target, + }; + } catch (error: any) { + // Permanent failures (post deleted / not found): cache the fallback. + const data = error.response?._data || error.data; + if (data && typeof data === 'object' && data.error_code) { + return buildListItem(item); + } + // Transient failures (rate limit / WAF): throw to skip caching, retry next request. + throw error; + } + }); + } catch { + // Transient failure: provide a fallback item without caching, retry next request. + return buildListItem(item); + } + }, + { concurrency: 3 }, ); const user = data[0]?.user;