feat(api & services): add error handling for club summary and refresh match details
- Add `.catch(() => null)` in `ClubSummary.tsx` to gracefully handle failed API requests and prevent UI crashes. - Return `null` from `ClubSummary` when the club data is `null` to skip rendering during errors. - Extract `getMatchInfo` logic into a new static method `getMatchDetail` within `KaiqiuService` for better separation of concerns. - Update `getClubLocation` and `getEventInfo` to accept an optional `force` parameter, allowing cache bypass when network errors (like '连接超时') occur. - Remove the old `getMatchInfo` utility function from `server.ts` and update the `/api/match` route to use `KaiqiuService.getMatchDetail`.
This commit is contained in:
parent
76b68c0ea6
commit
0c82384fd5
@ -14,7 +14,7 @@ interface Props {
|
|||||||
export const ClubSummary = (props: Props) => {
|
export const ClubSummary = (props: Props) => {
|
||||||
const [isArticleOpen, setIsArticleOpen] = useState(false);
|
const [isArticleOpen, setIsArticleOpen] = useState(false);
|
||||||
const requestClubSummary = useRequest<ClubDetail, []>(async () => {
|
const requestClubSummary = useRequest<ClubDetail, []>(async () => {
|
||||||
return fetch(`/api/club/${props.clubId}`).then(r => r.json());
|
return fetch(`/api/club/${props.clubId}`).then(r => r.json()).catch(() => null);
|
||||||
}, { manual: false, refreshDeps: [props.clubId], debounceWait: 300 })
|
}, { manual: false, refreshDeps: [props.clubId], debounceWait: 300 })
|
||||||
const info = useMemo(() => requestClubSummary.data, [requestClubSummary]);
|
const info = useMemo(() => requestClubSummary.data, [requestClubSummary]);
|
||||||
const noArticle = !info?.article || info.article === '还没有公告';
|
const noArticle = !info?.article || info.article === '还没有公告';
|
||||||
@ -43,6 +43,7 @@ export const ClubSummary = (props: Props) => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}, [info]);
|
}, [info]);
|
||||||
|
if (requestClubSummary.data === null) return null;
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
{requestClubSummary.loading ? (
|
{requestClubSummary.loading ? (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { getMatchInfo, verifyLogtoToken, xcxApi } from "./utils/server";
|
import { verifyLogtoToken, xcxApi } from "./utils/server";
|
||||||
import ics from 'ics';
|
import ics from 'ics';
|
||||||
import index from "./index.html";
|
import index from "./index.html";
|
||||||
import { getUidScore } from "./services/uidScoreStore";
|
import { getUidScore } from "./services/uidScoreStore";
|
||||||
@ -159,7 +159,7 @@ const server = Bun.serve({
|
|||||||
},
|
},
|
||||||
"/api/match/:matchId": {
|
"/api/match/:matchId": {
|
||||||
async GET(req) {
|
async GET(req) {
|
||||||
const data = await getMatchInfo(req.params.matchId);
|
const data = await KaiqiuService.getMatchDetail(req.params.matchId);
|
||||||
return Response.json(data);
|
return Response.json(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import * as cheerio from "cheerio";
|
import * as cheerio from "cheerio";
|
||||||
import { htmlRequestHeaders, redis, REDIS_CACHE_HOUR } from "../utils/server";
|
import { htmlRequestHeaders, parseEventInfo, redis, REDIS_CACHE_HOUR } from "../utils/server";
|
||||||
import type { ClubInfo, IEventInfo } from "../types";
|
import type { ClubInfo, IEventInfo } from "../types";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from 'dayjs/plugin/utc';
|
import utc from 'dayjs/plugin/utc';
|
||||||
@ -112,11 +112,11 @@ export class KaiqiuService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #getClubLocation(clubId: string) {
|
static async #getClubLocation(clubId: string, force?: boolean) {
|
||||||
const url = `${this.#baseURL}/home/space-mtag-tagid-${clubId}-view-map.html`;
|
const url = `${this.#baseURL}/home/space-mtag-tagid-${clubId}-view-map.html`;
|
||||||
const key = `my-kaiqiuwang:location:${clubId}`;
|
const key = `my-kaiqiuwang:location:${clubId}`;
|
||||||
let html: string = await redis.get(key) || '';
|
let html: string = await redis.get(key) || '';
|
||||||
if (!html) {
|
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());
|
||||||
redis.setex(key, 60 * 60 * 24, html);
|
redis.setex(key, 60 * 60 * 24, html);
|
||||||
}
|
}
|
||||||
@ -127,12 +127,12 @@ export class KaiqiuService {
|
|||||||
return { lng, lat };
|
return { lng, lat };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async getEventInfo(eventId: string) {
|
public static async getEventInfo(eventId: string, force?: boolean) {
|
||||||
const key = `my-kaiqiuwang:match:${eventId}`;
|
const key = `my-kaiqiuwang:match:${eventId}`;
|
||||||
let eventPage = await redis.get(key) ?? '';
|
let eventPage = await redis.get(key) ?? '';
|
||||||
// https://kaiqiuwang.cc/home/space-event-id-175775.html
|
// https://kaiqiuwang.cc/home/space-event-id-175775.html
|
||||||
const eventURL = `${this.#baseURL}/home/space-event-id-${eventId}.html`;
|
const eventURL = `${this.#baseURL}/home/space-event-id-${eventId}.html`;
|
||||||
if (!eventPage) {
|
if (!eventPage || eventPage.includes('连接超时') || force) {
|
||||||
eventPage = await fetch(eventURL, { headers: htmlRequestHeaders }).then(res => res.text() ?? '');
|
eventPage = await fetch(eventURL, { headers: htmlRequestHeaders }).then(res => res.text() ?? '');
|
||||||
await redis.setex(key, 60 * 60 * 10, eventPage)
|
await redis.setex(key, 60 * 60 * 10, eventPage)
|
||||||
}
|
}
|
||||||
@ -159,6 +159,17 @@ export class KaiqiuService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async getMatchDetail(eventId: string, force?: boolean) {
|
||||||
|
const key = `my-kaiqiuwang:match-member:${eventId}`;
|
||||||
|
let html = await redis.get(key).catch(() => '') || '';
|
||||||
|
if (!html || force) {
|
||||||
|
const url = `${this.#baseURL}/home/space.php?do=event&id=${eventId}&view=member&status=2`;
|
||||||
|
const html = await fetch(url, { headers: htmlRequestHeaders }).then(res => res.text() || '');
|
||||||
|
redis.setex(key, 60 * 60 * REDIS_CACHE_HOUR, html);
|
||||||
|
}
|
||||||
|
return parseEventInfo(html);
|
||||||
|
}
|
||||||
|
|
||||||
public static async login(username: string, password: string) {
|
public static async login(username: string, password: string) {
|
||||||
const loginPageRes = await fetch('https://kaiqiuwang.cc/home/do.php?ac=668g&&ref');
|
const loginPageRes = await fetch('https://kaiqiuwang.cc/home/do.php?ac=668g&&ref');
|
||||||
const cookies = loginPageRes.headers.getSetCookie().map(c => c.split(';')[0]).join('; ');
|
const cookies = loginPageRes.headers.getSetCookie().map(c => c.split(';')[0]).join('; ');
|
||||||
|
|||||||
@ -75,16 +75,6 @@ export function parseEventInfo(html: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMatchInfo(matchId: string) {
|
|
||||||
const key = `my-kaiqiuwang:match-member:${matchId}`;
|
|
||||||
let html = await redis.get(key).catch(() => '');
|
|
||||||
if (!html) {
|
|
||||||
html = await fetchEventContentHTML(matchId);
|
|
||||||
redis.setex(key, 60 * 60 * REDIS_CACHE_HOUR, html);
|
|
||||||
}
|
|
||||||
return parseEventInfo(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const extractBearerTokenFromHeaders = (authorization: string | null) => {
|
export const extractBearerTokenFromHeaders = (authorization: string | null) => {
|
||||||
if (!authorization) {
|
if (!authorization) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user