feat(douyu): 兑换成功后自动刷新积分 & 修复兑换图片首开裂图

- 兑换成功后自动调用积分查询,更新账号最新积分并写入任务结果
- 兑换图片并发生成竞态修复:同任务共享 in-flight Promise,避免 revoke 导致裂图
- 兑换图片移除积分展示
This commit is contained in:
yml2213
2026-08-01 17:28:36 +08:00
parent e22436bfd6
commit c95d6963b8
2 changed files with 25 additions and 11 deletions
+7
View File
@@ -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 {}),
},
)
+18 -11
View File
@@ -21,7 +21,6 @@ function rawString(raw: Record<string, unknown> | 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<HTMLImageElement> {
@@ -172,7 +169,6 @@ export async function drawExchangeImage(task: DouyuTaskItem): Promise<Blob> {
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<number, CachedImage>();
const inflightImages = new Map<number, Promise<CachedImage>>();
export async function getExchangeImage(task: DouyuTaskItem): Promise<CachedImage> {
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 {