From c95d6963b8be2a32dfc17e692a07a412c7905800 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 1 Aug 2026 17:28:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(douyu):=20=E5=85=91=E6=8D=A2=E6=88=90?= =?UTF-8?q?=E5=8A=9F=E5=90=8E=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0=E7=A7=AF?= =?UTF-8?q?=E5=88=86=20&=20=E4=BF=AE=E5=A4=8D=E5=85=91=E6=8D=A2=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=A6=96=E5=BC=80=E8=A3=82=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 兑换成功后自动调用积分查询,更新账号最新积分并写入任务结果 - 兑换图片并发生成竞态修复:同任务共享 in-flight Promise,避免 revoke 导致裂图 - 兑换图片移除积分展示 --- web/backend/services/douyu_runner.py | 7 ++++++ web/frontend/src/utils/exchangeImage.ts | 29 +++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/web/backend/services/douyu_runner.py b/web/backend/services/douyu_runner.py index dd29a99..ed7f298 100644 --- a/web/backend/services/douyu_runner.py +++ b/web/backend/services/douyu_runner.py @@ -1830,6 +1830,12 @@ class DouyuBatchRunner: ) account.bind_status = "goods_exchanged" account.updated_at = datetime.now(timezone.utc) + # 兑换成功后自动刷新积分,更新账号最新积分信息(失败不阻断兑换成功) + points_refresh = None + try: + points_refresh = self._refresh_account_points(client, account, cookie, ctn=ctn) + except Exception as exc: + self._push_log("warning", f"兑换后刷新积分失败: {exc}") self._mark_task( db, task, @@ -1841,6 +1847,7 @@ class DouyuBatchRunner: "game_channel": account.game_channel or "", "account_name": account.nickname or account.username or account.uid or f"#{account.id}", **result, + **(points_refresh or {}), }, ) diff --git a/web/frontend/src/utils/exchangeImage.ts b/web/frontend/src/utils/exchangeImage.ts index ed27858..100e083 100644 --- a/web/frontend/src/utils/exchangeImage.ts +++ b/web/frontend/src/utils/exchangeImage.ts @@ -21,7 +21,6 @@ function rawString(raw: Record | null | undefined, key: string) function goodsOf(task: DouyuTaskItem): { name: string; pic: string; - points: number | null; roleName: string; channel: string; accountName: string; @@ -32,13 +31,11 @@ function goodsOf(task: DouyuTaskItem): { || task.message.replace(/^兑换[^::]*[::]?\s*/, '').trim() || rawString(task.result, 'commodity_id'); const pic = rawString(goods, 'webPic') || rawString(goods, 'pic'); - const pointsValue = task.result?.esports_points_after_exchange; - const points = typeof pointsValue === 'number' && Number.isFinite(pointsValue) ? pointsValue : null; const roleName = rawString(task.result, 'game_name'); const channel = rawString(task.result, 'game_channel'); const accountName = rawString(task.result, 'account_name') || task.account_nickname || task.account_username || task.account_uid || `#${task.account_id}`; - return { name, pic, points, roleName, channel, accountName }; + return { name, pic, roleName, channel, accountName }; } function loadImage(url: string, timeoutMs = 8000): Promise { @@ -172,7 +169,6 @@ export async function drawExchangeImage(task: DouyuTaskItem): Promise { if (info.roleName) infoRows.push(['游戏角色', info.roleName]); if (info.channel) infoRows.push(['游戏区服', info.channel]); infoRows.push(['兑换账号', info.accountName]); - if (info.points != null) infoRows.push(['当前积分', `${info.points}`]); if (time) infoRows.push(['兑换时间', time]); ctx.font = `15px ${FONT_FAMILY}`; @@ -220,16 +216,27 @@ interface CachedImage { } const imageCache = new Map(); +const inflightImages = new Map>(); export async function getExchangeImage(task: DouyuTaskItem): Promise { const cached = imageCache.get(task.id); if (cached) return cached; - const blob = await drawExchangeImage(task); - const entry = { url: URL.createObjectURL(blob), blob }; - const previous = imageCache.get(task.id); - if (previous) URL.revokeObjectURL(previous.url); - imageCache.set(task.id, entry); - return entry; + const pending = inflightImages.get(task.id); + if (pending) return pending; + const creating = (async () => { + const blob = await drawExchangeImage(task); + const entry = { url: URL.createObjectURL(blob), blob }; + const previous = imageCache.get(task.id); + if (previous) URL.revokeObjectURL(previous.url); + imageCache.set(task.id, entry); + return entry; + })(); + inflightImages.set(task.id, creating); + try { + return await creating; + } finally { + inflightImages.delete(task.id); + } } export function clearExchangeImageCache(): void {