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.
This commit is contained in:
kyuuseiryuu 2026-03-25 09:59:54 +09:00
parent 6cf2d13a73
commit 8be15d51b1
2 changed files with 35 additions and 39 deletions

View File

@ -1,6 +1,6 @@
import * as cheerio from "cheerio"; import * as cheerio from "cheerio";
import { htmlRequestHeaders, parseEventInfo, redis } from "../utils/server"; import { htmlRequestHeaders, redis } from "../utils/server";
import type { ClubInfo, IEventInfo } from "../types"; import type { ClubInfo, EventDetail, IEventInfo, Player } from "../types";
import dayjs from "dayjs"; import dayjs from "dayjs";
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
@ -157,6 +157,9 @@ export class KaiqiuService {
const now = dayjs(); const now = dayjs();
const isProcessing = now.isAfter(startTime) && now.isBefore(overTime); const isProcessing = now.isAfter(startTime) && now.isBefore(overTime);
const isFinished = now.isAfter(overTime); const isFinished = now.isAfter(overTime);
if (isFinished) {
await redis.set(key, eventPage);
}
return { return {
title, title,
eventId, eventId,
@ -176,9 +179,37 @@ export class KaiqiuService {
let html = await redis.get(key) ?? ''; let html = await redis.get(key) ?? '';
if (!html || html.includes('连接超时') || force) { if (!html || html.includes('连接超时') || force) {
html = await fetch(url, { headers: htmlRequestHeaders }).then(res => res.text() || ''); 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-(?<uid>\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) { public static async login(username: string, password: string) {

View File

@ -41,41 +41,6 @@ export const htmlRequestHeaders = {
"cookie": "SECKEY_ABVK=oTGgqH4ypGPFVdQ3J9K7PoAOPdZ+8R7CsUzi75gelcg%3D; uchome_sendmail=1" "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-(?<uid>\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) => { export const extractBearerTokenFromHeaders = (authorization: string | null) => {
if (!authorization) { if (!authorization) {
return null; return null;