返回 AiToEarn
thumbnailCache.ts
根目录 / project / aitoearn-web / src / store / thumbnailCache.ts
1 /**
2 * thumbnailCache - 视频封面缓存 Store
3 * 使用 IndexedDB 持久化存储视频封面 URL,避免重复请求
4 */
5
6 import { getVideoThumbnail } from '@/api/materials/material.api'
7 import { createPersistStore } from '@/utils/storage/createPersistStore'
8
9 export interface IThumbnailCacheState {
10 cache: Record<string, string>
11 }
12
13 const initialState: IThumbnailCacheState = {
14 cache: {},
15 }
16
17 // 并发控制(闭包变量,不进入 store state)
18 const MAX_CONCURRENT = 3
19 let activeCount = 0
20 const pending = new Map<string, Promise<string>>()
21 const queue: Array<() => void> = []
22
23 function runNext() {
24 while (activeCount < MAX_CONCURRENT && queue.length > 0) {
25 const next = queue.shift()!
26 activeCount++
27 next()
28 }
29 }
30
31 export const useThumbnailCache = createPersistStore(
32 { ...initialState },
33 (set, get) => ({
34 fetchThumbnail(videoUrl: string): Promise<string> {
35 // 1. 缓存命中
36 const cached = get().cache[videoUrl]
37 if (cached)
38 return Promise.resolve(cached)
39
40 // 2. 去重:已在请求中
41 if (pending.has(videoUrl))
42 return pending.get(videoUrl)!
43
44 // 3. 创建请求 Promise
45 const promise = new Promise<string>((resolve) => {
46 const execute = async () => {
47 try {
48 const res = await getVideoThumbnail(videoUrl)
49 const thumbnailUrl = res?.data?.thumbnailUrl || ''
50 if (thumbnailUrl) {
51 set(state => ({
52 cache: { ...state.cache, [videoUrl]: thumbnailUrl },
53 }))
54 }
55 resolve(thumbnailUrl)
56 }
57 catch {
58 resolve('')
59 }
60 finally {
61 activeCount--
62 pending.delete(videoUrl)
63 runNext()
64 }
65 }
66
67 queue.push(execute)
68 runNext()
69 })
70
71 pending.set(videoUrl, promise)
72 return promise
73 },
74 }),
75 { name: 'thumbnail-cache', version: 2 },
76 'indexedDB',
77 )
78
78 lines TYPESCRIPT