File size: 7,325 Bytes
40f23a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
021ee94
40f23a9
021ee94
40f23a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// 图像缓存管理器 - 解决跨域问题并实现本地缓存
class ImageCacheManager {
  constructor() {
    this.cache = new Map()
    this.cacheName = 'music-images-v1'
    this.maxCacheSize = 100 // 最多缓存100张图片
    this.cacheExpiry = 7 * 24 * 60 * 60 * 1000 // 7天过期
    this.init()
  }

  async init() {
    // 清理过期的缓存
    await this.cleanupExpiredCache()
  }

  // 获取图片的缓存键
  getCacheKey(url) {
    return `img_${this.hashUrl(url)}`
  }

  // 简单的URL哈希函数
  hashUrl(url) {
    let hash = 0
    for (let i = 0; i < url.length; i++) {
      const char = url.charCodeAt(i)
      hash = ((hash << 5) - hash) + char
      hash = hash & hash // 转换为32位整数
    }
    return Math.abs(hash).toString(36)
  }

  // 将图片转换为base64并缓存 - 改进跨域处理
  async cacheImage(url, metadata = {}) {
    try {
      // 检查URL是否为空或无效
      if (!url || typeof url !== 'string') {
        return this.getDefaultImage()
      }

      const cacheKey = this.getCacheKey(url)
      
      // 检查内存缓存
      if (this.cache.has(cacheKey)) {
        return this.cache.get(cacheKey).dataUrl
      }

      // 检查localStorage缓存
      const cached = localStorage.getItem(cacheKey)
      if (cached) {
        try {
          const cachedData = JSON.parse(cached)
          if (Date.now() - cachedData.timestamp < this.cacheExpiry) {
            this.cache.set(cacheKey, cachedData)
            return cachedData.dataUrl
          }
        } catch (e) {
          // 缓存数据损坏,删除
          localStorage.removeItem(cacheKey)
        }
      }

      // 直接使用原始URL,不使用代理
      let finalUrl = url

      // 使用Canvas方式转换图片
      const dataUrl = await this.convertToDataUrl(finalUrl)
      
      // 保存到缓存
      const cacheData = {
        dataUrl,
        timestamp: Date.now(),
        originalUrl: url,
        metadata
      }
      
      this.cache.set(cacheKey, cacheData)
      
      // 保存到localStorage
      try {
        if (this.cache.size <= this.maxCacheSize) {
          localStorage.setItem(cacheKey, JSON.stringify(cacheData))
        }
      } catch (e) {
        // localStorage空间不足,清理一些旧缓存
        await this.cleanupOldCache()
        try {
          localStorage.setItem(cacheKey, JSON.stringify(cacheData))
        } catch (e) {
          console.warn('无法保存图片到localStorage:', e)
        }
      }

      return dataUrl
    } catch (error) {
      console.error('缓存图片失败:', error)
      return this.getDefaultImage()
    }
  }

  // 检查是否为跨域URL
  isCrossOrigin(url) {
    try {
      const urlObj = new URL(url, window.location.href)
      return urlObj.origin !== window.location.origin
    } catch (e) {
      // 如果URL解析失败,假设为跨域
      return true
    }
  }


  // 将图片转换为DataUrl
  async convertToDataUrl(url) {
    return new Promise((resolve, reject) => {
      const img = new Image()
      img.crossOrigin = 'anonymous'
      
      img.onload = () => {
        try {
          const canvas = document.createElement('canvas')
          const ctx = canvas.getContext('2d')
          
          canvas.width = img.naturalWidth
          canvas.height = img.naturalHeight
          
          ctx.drawImage(img, 0, 0)
          const result = canvas.toDataURL('image/jpeg', 0.8)
          resolve(result)
        } catch (error) {
          console.warn('Canvas转换失败,使用原始URL:', error)
          resolve(url)
        }
      }
      
      img.onerror = () => {
        console.warn('图片加载失败,使用默认图片')
        resolve(this.getDefaultImage())
      }
      
      // 设置超时
      setTimeout(() => {
        console.warn('图片加载超时,使用默认图片')
        resolve(this.getDefaultImage())
      }, 10000)
      
      img.src = url
    })
  }

  // 获取默认图片 - 返回 null,让组件使用 fas fa-music 图标
  getDefaultImage() {
    return null
  }

  // 获取缓存的图片
  async getCachedImage(url) {
    const cacheKey = this.getCacheKey(url)
    
    // 检查内存缓存
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey).dataUrl
    }

    // 检查localStorage缓存
    const cached = localStorage.getItem(cacheKey)
    if (cached) {
      try {
        const cachedData = JSON.parse(cached)
        if (Date.now() - cachedData.timestamp < this.cacheExpiry) {
          this.cache.set(cacheKey, cachedData)
          return cachedData.dataUrl
        } else {
          // 缓存过期,删除
          localStorage.removeItem(cacheKey)
        }
      } catch (e) {
        localStorage.removeItem(cacheKey)
      }
    }

    return null
  }

  // 预加载图片
  async preloadImage(url, metadata = {}) {
    return this.cacheImage(url, metadata)
  }

  // 清理过期缓存
  async cleanupExpiredCache() {
    const keys = Object.keys(localStorage)
    const now = Date.now()
    
    keys.forEach(key => {
      if (key.startsWith('img_')) {
        try {
          const cached = JSON.parse(localStorage.getItem(key))
          if (now - cached.timestamp > this.cacheExpiry) {
            localStorage.removeItem(key)
          }
        } catch (e) {
          localStorage.removeItem(key)
        }
      }
    })
  }

  // 清理旧缓存(释放空间)
  async cleanupOldCache() {
    const keys = Object.keys(localStorage)
    const imageKeys = keys.filter(key => key.startsWith('img_'))
    
    // 按时间戳排序,删除最旧的一半
    const cached = imageKeys.map(key => {
      try {
        const data = JSON.parse(localStorage.getItem(key))
        return { key, timestamp: data.timestamp }
      } catch (e) {
        return { key, timestamp: 0 }
      }
    }).sort((a, b) => a.timestamp - b.timestamp)

    const toDelete = cached.slice(0, Math.floor(cached.length / 2))
    toDelete.forEach(item => {
      localStorage.removeItem(item.key)
      this.cache.delete(item.key)
    })
  }

  // 清空所有缓存
  async clearCache() {
    const keys = Object.keys(localStorage)
    keys.forEach(key => {
      if (key.startsWith('img_')) {
        localStorage.removeItem(key)
      }
    })
    this.cache.clear()
  }

  // 获取缓存统计信息
  getCacheStats() {
    const keys = Object.keys(localStorage)
    const imageKeys = keys.filter(key => key.startsWith('img_'))
    let totalSize = 0
    
    imageKeys.forEach(key => {
      totalSize += localStorage.getItem(key).length
    })

    return {
      totalImages: imageKeys.length,
      memoryCache: this.cache.size,
      totalSize: (totalSize / 1024 / 1024).toFixed(2) + 'MB'
    }
  }
}

// 创建全局实例
export const imageCacheManager = new ImageCacheManager()

// 导出便捷方法
export const cacheImage = (url, metadata) => imageCacheManager.cacheImage(url, metadata)
export const getCachedImage = (url) => imageCacheManager.getCachedImage(url)
export const preloadImage = (url, metadata) => imageCacheManager.preloadImage(url, metadata)
export const clearImageCache = () => imageCacheManager.clearCache()
export const getImageCacheStats = () => imageCacheManager.getCacheStats()

export default imageCacheManager