feat(cache): remove redis caching and add force refresh mechanism

- Remove Redis-based caching logic from uidScoreStore and xcxApi.
- Add force refresh support to uidScoreRequest in GroupingPrediction component.
- Update server API /api/user/nowScores to accept force parameter.
- Always show refresh button in GroupingPrediction regardless of data state.
- Change default sort type in FavPlayersPage to SCORE_DOWN.
- Clean up unused imports from server.ts.

This change ensures user scores are always up-to-date by bypassing cache
when needed, preventing issues with stale data during manual sync operations.
This commit is contained in:
kyuuseiryuu 2026-03-25 11:19:59 +09:00
parent 8be15d51b1
commit 191906192b
6 changed files with 16 additions and 27 deletions

View File

@ -30,9 +30,9 @@ enum OrderScore {
export const GroupingPrediction: React.FC<Props> = props => { export const GroupingPrediction: React.FC<Props> = props => {
const { uidScore } = useLoaderData<{ uidScore: Map<string, string>}>(); const { uidScore } = useLoaderData<{ uidScore: Map<string, string>}>();
const uidScoreRequest = useRequest(async () => { const uidScoreRequest = useRequest(async (force?: boolean) => {
const uids = props.players?.map(player => player.uid).filter(Boolean); const uids = props.players?.map(player => player.uid).filter(Boolean);
const data = await fetch(`/api/user/nowScores`, { const data = await fetch(`/api/user/nowScores?force=${force ? 'true' : 'false'}`, {
method: "POST", method: "POST",
body: JSON.stringify({ uids }), body: JSON.stringify({ uids }),
}).then(res => res.json()).catch(() => ({})); }).then(res => res.json()).catch(() => ({}));
@ -86,7 +86,7 @@ export const GroupingPrediction: React.FC<Props> = props => {
}); });
}, [players, grouped, groupLen, maxPlayerSize]); }, [players, grouped, groupLen, maxPlayerSize]);
const handleSyncUidScore = useCallback(() => { const handleSyncUidScore = useCallback(() => {
uidScoreRequest.runAsync(); uidScoreRequest.runAsync(true);
}, []); }, []);
return ( return (
<> <>
@ -115,7 +115,7 @@ export const GroupingPrediction: React.FC<Props> = props => {
OrderScore., OrderScore.,
]} /> ]} />
</Form.Item> </Form.Item>
<Form.Item hidden={uidScore.size > 0 || (uidScoreRequest.data?.size || 0) > 0}> <Form.Item>
<Button loading={uidScoreRequest.loading} onClick={handleSyncUidScore} icon={<SyncOutlined />}> <Button loading={uidScoreRequest.loading} onClick={handleSyncUidScore} icon={<SyncOutlined />}>
</Button> </Button>

View File

@ -242,7 +242,8 @@ const server = Bun.serve({
"/api/user/nowScores": { "/api/user/nowScores": {
async POST(req) { async POST(req) {
const { uids } = await req.json(); const { uids } = await req.json();
const uidScore = await getUidScore(uids); const force = Boolean(new URL(req.url).searchParams.get('force') === 'true');
const uidScore = await getUidScore(uids, force);
return Response.json(uidScore); return Response.json(uidScore);
} }
}, },
@ -250,7 +251,7 @@ const server = Bun.serve({
async GET(req) { async GET(req) {
const uid = req.params.uid; const uid = req.params.uid;
if (uid === '0') return Response.json(null); if (uid === '0') return Response.json(null);
const profile = await xcxApi.getAdvProfile(uid); const profile = await xcxApi.getAdvProfile(uid, true);
return Response.json(profile); return Response.json(profile);
}, },
}, },

View File

@ -30,7 +30,7 @@ const StyledContainer = styled.div`
`; `;
export function FavePlayersPage() { export function FavePlayersPage() {
const { favMap, unFav } = useFavPlayerStore(state => state); const { favMap, unFav } = useFavPlayerStore(state => state);
const [sortType, setSortType] = useState<SortType>(SortType.DEFAULT); const [sortType, setSortType] = useState<SortType>(SortType.SCORE_DOWN);
const [showType, setShowType] = useState(ShowType.LOCAL); const [showType, setShowType] = useState(ShowType.LOCAL);
const headers = useAuthHeaders(); const headers = useAuthHeaders();
const { autoSignIn } = useAutoLogin(); const { autoSignIn } = useAutoLogin();
@ -110,7 +110,7 @@ export function FavePlayersPage() {
value={sortType} value={sortType}
onChange={e => setSortType(e)} onChange={e => setSortType(e)}
options={[ options={[
SortType.DEFAULT, // SortType.DEFAULT,
SortType.SCORE_UP, SortType.SCORE_UP,
SortType.SCORE_DOWN, SortType.SCORE_DOWN,
]} ]}

View File

@ -1,18 +1,8 @@
import { redis, REDIS_CACHE_HOUR, xcxApi } from '../utils/server'; import { xcxApi } from '../utils/server';
const getKey = (uid: string) => `my-kaiqiuwang:uid-score:${uid}`; export const getUidScore = async (uids: string[], force?: boolean): Promise<Record<string, string>> => {
const TIMEOUT = 60 * 60 * REDIS_CACHE_HOUR; // 24h;
export const getUidScore = async (uids: string[]): Promise<Record<string, string>> => {
const jobs = uids.map(async uid => { const jobs = uids.map(async uid => {
const key = getKey(uid); const profile = await xcxApi.getAdvProfile(uid, force);
const value = await redis.get(key);
if (value) {
return [uid, value];
}
const profile = await xcxApi.getAdvProfile(uid);
await redis.setex(key, TIMEOUT, profile?.score ?? '');
return [uid, profile?.score ?? '']; return [uid, profile?.score ?? ''];
}); });
return Object.fromEntries(await Promise.all(jobs)); return Object.fromEntries(await Promise.all(jobs));

View File

@ -33,12 +33,12 @@ export class XCXAPI {
return response.data as T; return response.data as T;
} }
async getAdvProfile(uid: string): Promise<XCXProfile | null> { async getAdvProfile(uid: string, force?: boolean): Promise<XCXProfile | null> {
const cacheProfile = await redis.get(`my-kaiqiuwang:profile:${uid}`); const cacheProfile = await redis.get(`my-kaiqiuwang:profile:${uid}`);
if (!cacheProfile) { if (!cacheProfile || force) {
const url = `/api/User/adv_profile?uid=${uid}`; const url = `/api/User/adv_profile?uid=${uid}`;
const profile = await this.#fetch<XCXProfile>(url); const profile = await this.#fetch<XCXProfile>(url);
await redis.setex(`my-kaiqiuwang:profile:${uid}`, 60 * 10, JSON.stringify(profile)); await redis.setex(`my-kaiqiuwang:profile:${uid}`, 60 * 60 * 24, JSON.stringify(profile));
return profile; return profile;
} }
return JSON.parse(cacheProfile); return JSON.parse(cacheProfile);

View File

@ -1,7 +1,5 @@
import type { EventDetail, Player } from "../types";
import * as cheerio from "cheerio";
import { XCXAPI } from "../services/xcxApi"; import { XCXAPI } from "../services/xcxApi";
import { KAIQIU_BASE_URL, LOGTO_DOMAIN } from "./common"; import { LOGTO_DOMAIN } from "./common";
import { RedisClient } from "bun"; import { RedisClient } from "bun";
import { createRemoteJWKSet, jwtVerify } from 'jose'; import { createRemoteJWKSet, jwtVerify } from 'jose';
import { LOGTO_RESOURCE } from "./constants"; import { LOGTO_RESOURCE } from "./constants";