优化社交图展示并为通讯录接入本地头像缓存

This commit is contained in:
liuwei
2026-04-27 09:13:01 +08:00
parent 0636e0453f
commit e573fd9c37
4 changed files with 274 additions and 37 deletions

View File

@@ -4,7 +4,8 @@ import re
import threading
import xml.etree.ElementTree as ET
from concurrent.futures import ThreadPoolExecutor
from flask import Blueprint, render_template, jsonify, request, current_app
from urllib.parse import quote
from flask import Blueprint, render_template, jsonify, request, current_app, redirect, send_file
from .auth import login_required
from loguru import logger
@@ -242,6 +243,27 @@ def _normalize_recent_message(server, raw_message: dict, chat_type: str, target_
}
def _build_dashboard_head_images(contact_manager):
"""构造后台可直接使用的头像地址映射。
说明:
1. 前端统一访问本蓝图的头像代理接口,这样可以优先命中本地缓存;
2. 头像 URL 哈希会拼到查询参数里,头像变更后浏览器会自然拉取最新版本;
3. 即便本地缓存暂时不存在,代理接口也还能回退到远端头像地址,不影响页面展示。
"""
result = {}
for wxid, remote_url in (contact_manager.get_all_head_images() or {}).items():
if not remote_url:
result[wxid] = ""
continue
version = contact_manager.get_head_image_version(wxid)
avatar_url = f"/contacts/api/avatar/{quote(str(wxid), safe='')}"
if version:
avatar_url = f"{avatar_url}?v={version}"
result[wxid] = avatar_url
return result
# 联系人管理页面
@contacts_bp.route('/')
@login_required
@@ -375,7 +397,9 @@ def api_head_images():
"""获取联系人头像信息API"""
try:
server = current_app.dashboard_server
head_images = server.contact_manager.get_all_head_images()
# 后台页拿到的是“可展示地址”而不是原始远端 URL
# 这样通讯录页会优先读本地缓存,头像变化时也能自动刷新最新版本。
head_images = _build_dashboard_head_images(server.contact_manager)
return jsonify({
"success": True,
@@ -388,6 +412,27 @@ def api_head_images():
return jsonify({"success": False, "error": str(e)}), 500
@contacts_bp.route('/api/avatar/<path:wxid>', methods=['GET'])
@login_required
def api_contact_avatar(wxid):
"""返回通讯录头像,本地缓存优先,远端地址兜底。"""
try:
server = current_app.dashboard_server
# 先尝试把头像补齐到本地缓存。
# 这样页面首次访问某个联系人时,也能顺手把缓存热起来。
cached_path = server.contact_manager.ensure_head_image_cached(wxid)
if cached_path and os.path.exists(cached_path):
return send_file(cached_path, conditional=True, max_age=86400)
remote_url = str(server.contact_manager.get_head_image(wxid) or "").strip()
if remote_url:
return redirect(remote_url, code=302)
return jsonify({"success": False, "error": "头像不存在"}), 404
except Exception as e:
logger.error(f"读取联系人头像失败 wxid={wxid}: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@contacts_bp.route('/api/group_members/<roomid>', methods=['GET'])
@login_required
def api_group_members(roomid):

View File

@@ -669,7 +669,7 @@
<el-dialog title="公共好友详情" :visible.sync="publicDetailDialogVisible" width="50%">
<div class="detail-avatar-wrap">
<el-avatar size="large" :src="currentPublic.small_head_img_url" @error="() => true" class="detail-avatar">
<el-avatar size="large" :src="getHeadImage(currentPublic.wxid)" @error="() => true" class="detail-avatar">
<img src="/static/logo.png"/>
</el-avatar>
</div>
@@ -968,6 +968,10 @@
},
refreshContacts() { this.loadContactsData(); this.$message.success('联系人数据已刷新'); },
handleTabClick() { this.currentPage = 1; },
// 通讯录头像统一走后台代理接口:
// 1. 优先命中服务端已缓存的本地头像;
// 2. 头像更新后会附带版本参数,浏览器不会一直吃旧图;
// 3. 代理接口兜底远端地址,因此这里保持简单读取即可。
getHeadImage(wxid) { return this.headImages[wxid] || ''; },
handleSizeChange(size) { this.pageSize = size; },
handleCurrentChange(page) { this.currentPage = page; },