From 8be15d51b116301a16bde125a271c4c1f7a9835a Mon Sep 17 00:00:00 2001 From: kyuuseiryuu Date: Wed, 25 Mar 2026 09:59:54 +0900 Subject: [PATCH] refactor(KaiqiuService): optimize caching for finished matches - Move `parseEventInfo` and `fetchEventContentHTML` logic into `KaiqiuService`. - Implement differentiated caching strategies: - For finished matches: Cache indefinitely (no TTL) to stop unnecessary HTTP requests since the result is final. - For ongoing matches: Retain the 5-minute TTL to fetch live updates. - Remove unused utility functions from `server.ts`. - Update type imports to include `EventDetail` and `Player`. This change reduces server load by avoiding repeated requests for matches with confirmed results, while ensuring live games remain up-to-date. --- src/services/KaiqiuService.ts | 39 +++++++++++++++++++++++++++++++---- src/utils/server.ts | 35 ------------------------------- 2 files changed, 35 insertions(+), 39 deletions(-) diff --git a/src/services/KaiqiuService.ts b/src/services/KaiqiuService.ts index 76057c5..1e9ae2c 100644 --- a/src/services/KaiqiuService.ts +++ b/src/services/KaiqiuService.ts @@ -1,6 +1,6 @@ import * as cheerio from "cheerio"; -import { htmlRequestHeaders, parseEventInfo, redis } from "../utils/server"; -import type { ClubInfo, IEventInfo } from "../types"; +import { htmlRequestHeaders, redis } from "../utils/server"; +import type { ClubInfo, EventDetail, IEventInfo, Player } from "../types"; import dayjs from "dayjs"; import utc from 'dayjs/plugin/utc'; import timezone from 'dayjs/plugin/timezone'; @@ -157,6 +157,9 @@ export class KaiqiuService { const now = dayjs(); const isProcessing = now.isAfter(startTime) && now.isBefore(overTime); const isFinished = now.isAfter(overTime); + if (isFinished) { + await redis.set(key, eventPage); + } return { title, eventId, @@ -176,9 +179,37 @@ export class KaiqiuService { let html = await redis.get(key) ?? ''; if (!html || html.includes('连接超时') || force) { html = await fetch(url, { headers: htmlRequestHeaders }).then(res => res.text() || ''); - await redis.setex(key, 60 * 5, html); + const info = await this.getEventInfo(eventId) + if (info.isFinished) { + await redis.set(key, html); + } else { + await redis.setex(key, 60 * 5, html); + } + } + return this.parseEventInfo(html, eventId); + } + + private static parseEventInfo(html: string, eventId: string): EventDetail { + const $ = cheerio.load(html); + const title = $('h2.title a').text(); + const itemHref = $('.sub_menu a.active').attr('href') ?? ''; + const itemId = /\S+item_id=(\d+)$/.exec(itemHref)?.[1] ?? ''; + const players: Player[] = []; + const playersEl = $('.thumb'); + for (const player of playersEl) { + const img = $(player).find('.image img').attr('src') ?? ''; + const name = $(player).find('h6').text().trim(); + const uid = /space-(?\d+).html/.exec($(player).find('h6 a').attr('href') ?? '')?.groups?.uid ?? ''; + const info = $(player).find('p:nth-of-type(2)').text().replace(/\s/g, ''); + const score = /^.*?\b(\d+)\b/.exec(info)?.[1] ?? ''; + players.push({ name, avatar: img, score, info, uid }); + } + return { + itemId, + eventId, + title, + players, } - return parseEventInfo(html, eventId); } public static async login(username: string, password: string) { diff --git a/src/utils/server.ts b/src/utils/server.ts index 78dfe21..1852711 100644 --- a/src/utils/server.ts +++ b/src/utils/server.ts @@ -41,41 +41,6 @@ export const htmlRequestHeaders = { "cookie": "SECKEY_ABVK=oTGgqH4ypGPFVdQ3J9K7PoAOPdZ+8R7CsUzi75gelcg%3D; uchome_sendmail=1" } - -/** - * - * @param matchId 比赛 ID - * @returns HTML - */ -export async function fetchEventContentHTML(matchId: string) { - const url = `${KAIQIU_BASE_URL}/home/space.php?do=event&id=${matchId}&view=member&status=2`; - const resp = await fetch(url, { headers: htmlRequestHeaders }); - return resp.text() ?? '' -} - -export function parseEventInfo(html: string, eventId: string): EventDetail { - const $ = cheerio.load(html); - const title = $('h2.title a').text(); - const itemHref = $('.sub_menu a.active').attr('href') ?? ''; - const itemId = /\S+item_id=(\d+)$/.exec(itemHref)?.[1] ?? ''; - const players: Player[] = []; - const playersEl = $('.thumb'); - for (const player of playersEl) { - const img = $(player).find('.image img').attr('src') ?? ''; - const name = $(player).find('h6').text().trim(); - const uid = /space-(?\d+).html/.exec($(player).find('h6 a').attr('href') ?? '')?.groups?.uid ?? ''; - const info = $(player).find('p:nth-of-type(2)').text().replace(/\s/g, ''); - const score = /^.*?\b(\d+)\b/.exec(info)?.[1] ?? ''; - players.push({ name, avatar: img, score, info, uid }); - } - return { - itemId, - eventId, - title, - players, - } -} - export const extractBearerTokenFromHeaders = (authorization: string | null) => { if (!authorization) { return null;