feat(douyu): 兑换成功后自动刷新积分 & 修复兑换图片首开裂图
- 兑换成功后自动调用积分查询,更新账号最新积分并写入任务结果 - 兑换图片并发生成竞态修复:同任务共享 in-flight Promise,避免 revoke 导致裂图 - 兑换图片移除积分展示
This commit is contained in:
@@ -1830,6 +1830,12 @@ class DouyuBatchRunner:
|
|||||||
)
|
)
|
||||||
account.bind_status = "goods_exchanged"
|
account.bind_status = "goods_exchanged"
|
||||||
account.updated_at = datetime.now(timezone.utc)
|
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(
|
self._mark_task(
|
||||||
db,
|
db,
|
||||||
task,
|
task,
|
||||||
@@ -1841,6 +1847,7 @@ class DouyuBatchRunner:
|
|||||||
"game_channel": account.game_channel or "",
|
"game_channel": account.game_channel or "",
|
||||||
"account_name": account.nickname or account.username or account.uid or f"#{account.id}",
|
"account_name": account.nickname or account.username or account.uid or f"#{account.id}",
|
||||||
**result,
|
**result,
|
||||||
|
**(points_refresh or {}),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ function rawString(raw: Record<string, unknown> | null | undefined, key: string)
|
|||||||
function goodsOf(task: DouyuTaskItem): {
|
function goodsOf(task: DouyuTaskItem): {
|
||||||
name: string;
|
name: string;
|
||||||
pic: string;
|
pic: string;
|
||||||
points: number | null;
|
|
||||||
roleName: string;
|
roleName: string;
|
||||||
channel: string;
|
channel: string;
|
||||||
accountName: string;
|
accountName: string;
|
||||||
@@ -32,13 +31,11 @@ function goodsOf(task: DouyuTaskItem): {
|
|||||||
|| task.message.replace(/^兑换[^::]*[::]?\s*/, '').trim()
|
|| task.message.replace(/^兑换[^::]*[::]?\s*/, '').trim()
|
||||||
|| rawString(task.result, 'commodity_id');
|
|| rawString(task.result, 'commodity_id');
|
||||||
const pic = rawString(goods, 'webPic') || rawString(goods, 'pic');
|
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 roleName = rawString(task.result, 'game_name');
|
||||||
const channel = rawString(task.result, 'game_channel');
|
const channel = rawString(task.result, 'game_channel');
|
||||||
const accountName = rawString(task.result, 'account_name')
|
const accountName = rawString(task.result, 'account_name')
|
||||||
|| task.account_nickname || task.account_username || task.account_uid || `#${task.account_id}`;
|
|| 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> {
|
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.roleName) infoRows.push(['游戏角色', info.roleName]);
|
||||||
if (info.channel) infoRows.push(['游戏区服', info.channel]);
|
if (info.channel) infoRows.push(['游戏区服', info.channel]);
|
||||||
infoRows.push(['兑换账号', info.accountName]);
|
infoRows.push(['兑换账号', info.accountName]);
|
||||||
if (info.points != null) infoRows.push(['当前积分', `${info.points}`]);
|
|
||||||
if (time) infoRows.push(['兑换时间', time]);
|
if (time) infoRows.push(['兑换时间', time]);
|
||||||
|
|
||||||
ctx.font = `15px ${FONT_FAMILY}`;
|
ctx.font = `15px ${FONT_FAMILY}`;
|
||||||
@@ -220,16 +216,27 @@ interface CachedImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imageCache = new Map<number, CachedImage>();
|
const imageCache = new Map<number, CachedImage>();
|
||||||
|
const inflightImages = new Map<number, Promise<CachedImage>>();
|
||||||
|
|
||||||
export async function getExchangeImage(task: DouyuTaskItem): Promise<CachedImage> {
|
export async function getExchangeImage(task: DouyuTaskItem): Promise<CachedImage> {
|
||||||
const cached = imageCache.get(task.id);
|
const cached = imageCache.get(task.id);
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
const blob = await drawExchangeImage(task);
|
const pending = inflightImages.get(task.id);
|
||||||
const entry = { url: URL.createObjectURL(blob), blob };
|
if (pending) return pending;
|
||||||
const previous = imageCache.get(task.id);
|
const creating = (async () => {
|
||||||
if (previous) URL.revokeObjectURL(previous.url);
|
const blob = await drawExchangeImage(task);
|
||||||
imageCache.set(task.id, entry);
|
const entry = { url: URL.createObjectURL(blob), blob };
|
||||||
return entry;
|
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 {
|
export function clearExchangeImageCache(): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user