浏览代码

feat(avatar-cache): 实现并发下载去重功能,优化头像获取逻辑

mcbaiyun 2 周之前
父节点
当前提交
bd5f98a4f5
共有 2 个文件被更改,包括 88 次插入32 次删除
  1. 55 32
      src/pages/doctor/index/index.vue
  2. 33 0
      src/utils/avatarCache.ts

+ 55 - 32
src/pages/doctor/index/index.vue

@@ -182,27 +182,30 @@ const fetchUserInfo = async () => {
         const userIdRaw = resp.data.id || resp.data.userId
         const userId = userIdRaw ? String(userIdRaw) : ''
         if (userId) {
-          // 检查是否有缓存的头像
-          if (avatarCache.has(userId)) {
-            resp.data.avatar = avatarCache.get(userId)
-          } else {
             try {
-              const downloadRes = await uni.downloadFile({
-                url: `https://wx.baiyun.work/user/avatar/${userId}`,
-                header: {
-                  Authorization: `Bearer ${token}`
+              // 使用 avatarCache.getOrFetch 做并发去重,loader 只在真正需要下载时被调用一次
+              const path = await avatarCache.getOrFetch(userId, async (id) => {
+                try {
+                  const downloadRes = await uni.downloadFile({
+                    url: `https://wx.baiyun.work/user/avatar/${id}`,
+                    header: {
+                      Authorization: `Bearer ${token}`
+                    }
+                  })
+                  if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
+                    return downloadRes.tempFilePath
+                  }
+                } catch (e) {
+                  console.error('Download avatar error:', e)
                 }
+                return undefined
               })
-              if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
-                resp.data.avatar = downloadRes.tempFilePath
-                // 缓存头像路径
-                avatarCache.set(userId, downloadRes.tempFilePath)
-              }
+
+              if (path) resp.data.avatar = path
             } catch (e) {
-              console.error('Download avatar error:', e)
+              console.error('avatar fetch error:', e)
             }
           }
-        }
       }
       user.value = resp.data
       uni.setStorageSync('user_info', resp.data)
@@ -405,24 +408,29 @@ const getPatientAvatar = async (userId: string | number): Promise<string> => {
     const token = uni.getStorageSync('token')
     if (!token) return defaultAvatarUrl
     
-    // 检查是否有缓存的头像
+    // 使用 getOrFetch 去重并缓存下载结果
     const idStr = String(userId)
-    if (avatarCache.has(idStr)) {
-      return avatarCache.get(idStr)!
-    }
-    
-    // 尝试下载用户头像
-    const downloadRes = await uni.downloadFile({
-      url: `https://wx.baiyun.work/user/avatar/${idStr}`,
-      header: {
-        Authorization: `Bearer ${token}`
-      }
-    })
-    
-    if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
-      // 缓存头像路径
-      avatarCache.set(idStr, downloadRes.tempFilePath)
-      return downloadRes.tempFilePath
+    try {
+      const path = await avatarCache.getOrFetch(idStr, async (id) => {
+        try {
+          const downloadRes = await uni.downloadFile({
+            url: `https://wx.baiyun.work/user/avatar/${id}`,
+            header: {
+              Authorization: `Bearer ${token}`
+            }
+          })
+          if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
+            return downloadRes.tempFilePath
+          }
+        } catch (e) {
+          console.error('Download patient avatar error:', e)
+        }
+        return undefined
+      })
+
+      if (path) return path
+    } catch (e) {
+      console.error('getPatientAvatar error:', e)
     }
   } catch (e) {
     console.error('Download patient avatar error:', e)
@@ -747,6 +755,21 @@ const formatActivityDescription = (activity: any) => {
   overflow: hidden;
 }
 
+/* 卡片头样式(用于患者动态等卡片) */
+.card-header {
+  padding: 24rpx 28rpx;
+  display: flex;
+  align-items: center;
+  background: linear-gradient(90deg, rgba(255,255,255,0.9), rgba(255,255,255,0.75));
+  border-bottom: 1rpx solid #f3f4f6;
+}
+
+.card-title {
+  font-size: 32rpx;
+  font-weight: 700;
+  color: #222;
+}
+
 .activity-item {
   display: flex;
   align-items: center;

+ 33 - 0
src/utils/avatarCache.ts

@@ -1,6 +1,8 @@
 // 头像缓存工具模块
 class AvatarCache {
   private cache: Map<string, string> = new Map()
+  // 并发下载去重:记录正在进行的下载 Promise
+  private pending: Map<string, Promise<string | undefined>> = new Map()
 
   // 获取缓存的头像路径
   get(userId: string): string | undefined {
@@ -21,6 +23,37 @@ class AvatarCache {
   clear(): void {
     this.cache.clear()
   }
+
+  /**
+   * 如果缓存存在则立即返回缓存字符串。
+   * 否则调用 `loader(id)` 执行一次下载(并发请求会复用同一个 Promise),
+   * 下载成功时写入缓存并返回路径,失败返回 undefined。
+   */
+  async getOrFetch(userId: string, loader: (id: string) => Promise<string | undefined>): Promise<string | undefined> {
+    const id = String(userId)
+    const cached = this.cache.get(id)
+    if (cached) return cached
+
+    // 如果已有进行中的下载,复用该 Promise
+    const pending = this.pending.get(id)
+    if (pending) return pending
+
+    const p = (async () => {
+      try {
+        const result = await loader(id)
+        if (result) {
+          this.cache.set(id, result)
+        }
+        return result
+      } finally {
+        // 无论成功或失败,移除 pending
+        this.pending.delete(id)
+      }
+    })()
+
+    this.pending.set(id, p)
+    return p
+  }
 }
 
 // 导出单例实例