搜索增加部分缓存快速启动机制

This commit is contained in:
mtvpls
2026-05-16 09:56:40 +08:00
rodzic 09fc63c116
commit 4f0a0db0fd
3 zmienionych plików z 137 dodań i 88 usunięć
+88 -82
Wyświetl plik
@@ -109,6 +109,13 @@ interface PlayFallbackRecommendation {
doubanId?: number;
}
interface SearchCachePayload {
status: 'complete' | 'partial';
results: SearchResult[];
query: string;
updatedAt: number;
}
function PlayPageClient() {
const LOCAL_TRANSCODER_BASE_URL = 'http://localhost:19080';
const router = useRouter();
@@ -3764,53 +3771,88 @@ function PlayPageClient() {
.slice(0, 12);
};
const readSearchCache = (query: string): SearchCachePayload | null => {
if (typeof window === 'undefined' || !query.trim()) {
return null;
}
try {
const cacheKey = `search_cache_${query.trim()}`;
const cached = sessionStorage.getItem(cacheKey);
if (!cached) return null;
const parsed = JSON.parse(cached) as SearchCachePayload;
if (
(parsed?.status === 'complete' || parsed?.status === 'partial') &&
Array.isArray(parsed.results)
) {
return parsed;
}
} catch (error) {
console.error('[Play] 读取缓存失败:', error);
}
return null;
};
const writeCompleteSearchCache = (query: string, results: SearchResult[]) => {
if (typeof window === 'undefined' || !query.trim()) return;
try {
const cacheKey = `search_cache_${query.trim()}`;
const payload: SearchCachePayload = {
status: 'complete',
results,
query: query.trim(),
updatedAt: Date.now(),
};
sessionStorage.setItem(cacheKey, JSON.stringify(payload));
} catch (error) {
console.error('[Play] 写入缓存失败:', error);
}
};
const filterSourcesForCurrentVideo = (items: SearchResult[]): SearchResult[] => {
return items.filter(
(result: SearchResult) =>
normalizeTitle(result.title).toLowerCase() ===
normalizeTitle(videoTitleRef.current).toLowerCase() &&
(videoYearRef.current
? result.year.toLowerCase() === videoYearRef.current.toLowerCase() ||
!result.year ||
result.year.trim() === '' ||
result.year === 'unknown' ||
!/^\d{4}$/.test(result.year)
: true) &&
(searchType
? getType(result) === searchType
: true)
);
};
const fetchSourcesData = async (query: string): Promise<SearchResult[]> => {
// 根据搜索词获取全部源信息
setHasCompletedSearchRequest(false);
setFallbackRecommendations([]);
let fallbackCachedResults: SearchResult[] = [];
try {
// 先检查 sessionStorage 中是否有缓存
const cacheKey = `search_cache_${query.trim()}`;
let results: SearchResult[] = [];
const cachedPayload = readSearchCache(query);
if (cachedPayload) {
console.log(`[Play] 使用 sessionStorage ${cachedPayload.status === 'partial' ? '临时' : '完整'}缓存的搜索结果`);
setFallbackRecommendations(buildFallbackRecommendations(cachedPayload.results, query));
if (typeof window !== 'undefined') {
try {
const cached = sessionStorage.getItem(cacheKey);
if (cached) {
console.log('[Play] 使用 sessionStorage 缓存的搜索结果');
const cachedData = JSON.parse(cached) as SearchResult[];
const cachedResults = filterSourcesForCurrentVideo(cachedPayload.results);
fallbackCachedResults = cachedResults;
setAvailableSources(applyCorrectionsToSources(cachedResults));
setHasCompletedSearchRequest(true);
setFallbackRecommendations(buildFallbackRecommendations(cachedData, query));
// 处理缓存的搜索结果,根据规则过滤
results = cachedData.filter(
(result: SearchResult) =>
normalizeTitle(result.title).toLowerCase() ===
normalizeTitle(videoTitleRef.current).toLowerCase() &&
(videoYearRef.current
? result.year.toLowerCase() === videoYearRef.current.toLowerCase() ||
!result.year ||
result.year.trim() === '' ||
result.year === 'unknown' ||
!/^\d{4}$/.test(result.year)
: true) &&
(searchType
? getType(result) === searchType
: true)
);
setAvailableSources(applyCorrectionsToSources(results));
return results;
}
} catch (error) {
console.error('[Play] 读取缓存失败:', error);
// 继续执行 API 调用
if (cachedPayload.status === 'complete') {
setHasCompletedSearchRequest(true);
return cachedResults;
}
}
// 如果没有缓存,调用 API
// 没有缓存或只有 partial 缓存时,重新请求完整搜索结果
const response = await fetch(
`/api/search?q=${encodeURIComponent(query.trim())}`
);
@@ -3820,29 +3862,18 @@ function PlayPageClient() {
const data = await response.json();
const allResults = (data.results || []) as SearchResult[];
writeCompleteSearchCache(query, allResults);
setHasCompletedSearchRequest(true);
setFallbackRecommendations(buildFallbackRecommendations(allResults, query));
// 处理搜索结果,根据规则过滤
results = allResults.filter(
(result: SearchResult) =>
normalizeTitle(result.title).toLowerCase() ===
normalizeTitle(videoTitleRef.current).toLowerCase() &&
(videoYearRef.current
? result.year.toLowerCase() === videoYearRef.current.toLowerCase() ||
!result.year ||
result.year.trim() === '' ||
result.year === 'unknown' ||
!/^\d{4}$/.test(result.year)
: true) &&
(searchType
? getType(result) === searchType
: true)
);
const results = filterSourcesForCurrentVideo(allResults);
setAvailableSources(applyCorrectionsToSources(results));
return results;
} catch (err) {
setSourceSearchError(err instanceof Error ? err.message : '搜索失败');
if (fallbackCachedResults.length > 0) {
return fallbackCachedResults;
}
setAvailableSources([]);
return [];
} finally {
@@ -3851,39 +3882,14 @@ function PlayPageClient() {
};
const getCachedSourcesData = (query: string): SearchResult[] => {
if (typeof window === 'undefined' || !query.trim()) {
const cachedPayload = readSearchCache(query);
if (!cachedPayload) {
return [];
}
try {
const cacheKey = `search_cache_${query.trim()}`;
const cached = sessionStorage.getItem(cacheKey);
if (!cached) {
return [];
}
const cachedData = JSON.parse(cached);
const results = cachedData.filter(
(result: SearchResult) =>
normalizeTitle(result.title).toLowerCase() ===
normalizeTitle(videoTitleRef.current).toLowerCase() &&
(videoYearRef.current
? result.year.toLowerCase() === videoYearRef.current.toLowerCase() ||
!result.year ||
result.year.trim() === '' ||
result.year === 'unknown' ||
!/^\d{4}$/.test(result.year)
: true) &&
(searchType
? getType(result) === searchType
: true)
);
return applyCorrectionsToSources(results);
} catch (error) {
console.error('[Play] 读取缓存失败:', error);
return [];
}
return applyCorrectionsToSources(
filterSourcesForCurrentVideo(cachedPayload.results)
);
};
const initAll = async () => {
+41 -6
Wyświetl plik
@@ -46,6 +46,13 @@ import SearchSuggestions from '@/components/SearchSuggestions';
import VideoCard, { VideoCardHandle } from '@/components/VideoCard';
import VirtualScrollableGrid from '@/components/VirtualScrollableGrid';
type SearchCachePayload = {
status: 'complete' | 'partial';
results: SearchResult[];
query: string;
updatedAt: number;
};
function SearchPageClient() {
// 搜索历史
const [searchHistory, setSearchHistory] = useState<string[]>([]);
@@ -107,14 +114,17 @@ function SearchPageClient() {
return `search_cache_${query.trim()}`;
};
// 从 sessionStorage 获取缓存的搜索结果
// 从 sessionStorage 获取完整缓存的搜索结果(partial 只给播放页快速启动使用)
const getCachedResults = (query: string): SearchResult[] | null => {
if (typeof window === 'undefined') return null;
try {
const cacheKey = getCacheKey(query);
const cached = sessionStorage.getItem(cacheKey);
if (cached) {
return JSON.parse(cached);
if (!cached) return null;
const parsed = JSON.parse(cached) as SearchCachePayload;
if (parsed?.status === 'complete' && Array.isArray(parsed.results)) {
return parsed.results;
}
} catch (error) {
console.error('Failed to get cached results:', error);
@@ -123,16 +133,36 @@ function SearchPageClient() {
};
// 保存搜索结果到 sessionStorage
const setCachedResults = (query: string, results: SearchResult[]) => {
const setCachedResults = (
query: string,
results: SearchResult[],
status: SearchCachePayload['status'] = 'complete'
) => {
if (typeof window === 'undefined') return;
try {
const cacheKey = getCacheKey(query);
sessionStorage.setItem(cacheKey, JSON.stringify(results));
const payload: SearchCachePayload = {
status,
results,
query: query.trim(),
updatedAt: Date.now(),
};
sessionStorage.setItem(cacheKey, JSON.stringify(payload));
} catch (error) {
console.error('Failed to cache results:', error);
}
};
const savePartialCacheForPlayback = () => {
const query = currentQueryRef.current.trim();
if (!query || !eventSourceRef.current || !isLoading) return;
const snapshot = searchResults.concat(pendingResultsRef.current);
if (snapshot.length > 20) {
setCachedResults(query, snapshot, 'partial');
}
};
// 清除指定查询的缓存
const clearCachedResults = (query: string) => {
if (typeof window === 'undefined') return;
@@ -847,7 +877,10 @@ function SearchPageClient() {
<button
key={item.key}
type='button'
onClick={() => router.push(itemUrl)}
onClick={() => {
savePartialCacheForPlayback();
router.push(itemUrl);
}}
className='group w-full rounded-2xl border border-gray-200/80 bg-white/90 p-3 text-left shadow-sm transition-all hover:border-green-300 hover:shadow-md dark:border-gray-700 dark:bg-gray-900/70 dark:hover:border-green-700'
>
<div className='flex items-start gap-4'>
@@ -1818,6 +1851,7 @@ function SearchPageClient() {
<VideoCard
ref={getGroupRef(mapKey)}
from='search'
onBeforeNavigate={savePartialCacheForPlayback}
isAggregate={true}
title={title}
poster={poster}
@@ -1867,6 +1901,7 @@ function SearchPageClient() {
>
<VideoCard
id={item.id}
onBeforeNavigate={savePartialCacheForPlayback}
title={item.title}
poster={item.poster}
episodes={item.episodes.length}
+8
Wyświetl plik
@@ -69,6 +69,7 @@ export interface VideoCardProps {
episodes?: string[];
episodes_titles?: string[];
};
onBeforeNavigate?: () => void;
}
export type VideoCardHandle = {
@@ -107,6 +108,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
playTime,
totalTime,
cmsData,
onBeforeNavigate,
}: VideoCardProps,
ref
) {
@@ -326,6 +328,8 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
return;
}
onBeforeNavigate?.();
if (origin === 'live' && actualSource && actualId) {
// 直播内容跳转到直播页面
const url = `/live?source=${actualSource.replace('live_', '')}&id=${actualId.replace('live_', '')}`;
@@ -376,6 +380,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
isAggregate,
actualQuery,
actualSearchType,
onBeforeNavigate,
]);
// 新标签页播放处理函数
@@ -385,6 +390,8 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
return;
}
onBeforeNavigate?.();
if (origin === 'live' && actualSource && actualId) {
// 直播内容跳转到直播页面
const url = `/live?source=${actualSource.replace('live_', '')}&id=${actualId.replace('live_', '')}`;
@@ -411,6 +418,7 @@ const VideoCard = forwardRef<VideoCardHandle, VideoCardProps>(function VideoCard
isAggregate,
actualQuery,
actualSearchType,
onBeforeNavigate,
]);
// 检查搜索结果的收藏状态