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

View File

@ -242,7 +242,8 @@ const server = Bun.serve({
"/api/user/nowScores": {
async POST(req) {
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);
}
},
@ -250,7 +251,7 @@ const server = Bun.serve({
async GET(req) {
const uid = req.params.uid;
if (uid === '0') return Response.json(null);
const profile = await xcxApi.getAdvProfile(uid);
const profile = await xcxApi.getAdvProfile(uid, true);
return Response.json(profile);
},
},

View File

@ -30,7 +30,7 @@ const StyledContainer = styled.div`
`;
export function FavePlayersPage() {
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 headers = useAuthHeaders();
const { autoSignIn } = useAutoLogin();
@ -110,7 +110,7 @@ export function FavePlayersPage() {
value={sortType}
onChange={e => setSortType(e)}
options={[
SortType.DEFAULT,
// SortType.DEFAULT,
SortType.SCORE_UP,
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}`;
const TIMEOUT = 60 * 60 * REDIS_CACHE_HOUR; // 24h;
export const getUidScore = async (uids: string[]): Promise<Record<string, string>> => {
export const getUidScore = async (uids: string[], force?: boolean): Promise<Record<string, string>> => {
const jobs = uids.map(async uid => {
const key = getKey(uid);
const value = await redis.get(key);
if (value) {
return [uid, value];
}
const profile = await xcxApi.getAdvProfile(uid);
await redis.setex(key, TIMEOUT, profile?.score ?? '');
const profile = await xcxApi.getAdvProfile(uid, force);
return [uid, profile?.score ?? ''];
});
return Object.fromEntries(await Promise.all(jobs));

View File

@ -33,12 +33,12 @@ export class XCXAPI {
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}`);
if (!cacheProfile) {
if (!cacheProfile || force) {
const url = `/api/User/adv_profile?uid=${uid}`;
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 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 { KAIQIU_BASE_URL, LOGTO_DOMAIN } from "./common";
import { LOGTO_DOMAIN } from "./common";
import { RedisClient } from "bun";
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { LOGTO_RESOURCE } from "./constants";