# 头像显示修复报告 — 2026-07-10 ## 问题摘要 坐席端(`https://itsupport.servyou.com.cn/itagent/workspace`)员工头像仍不显示,浏览器控制台报三类错误: 1. **COEP 阻断** — `ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep` 2. **Mixed Content** — HTTPS 页面不允许加载 HTTP 资源 3. **CSP 限制** — `img-src` 白名单不含 `wework.qpic.cn` ## 根因分析 三层安全策略叠加导致企微头像域名 `wework.qpic.cn` 被完全拦截: | 安全策略 | nginx 配置 | 拦截表现 | |---------|-----------|---------| | COEP | `require-corp` | 跨域图片无 CORS 头被拦截 | | Mixed Content | — | HTTP 图片在 HTTPS 页面被拦截 | | CSP | `img-src 'self' data: https://res.wx.qq.com` | `wework.qpic.cn` 不在白名单 | ## 修复方案:后端头像代理 **不修改 nginx 安全头**(保持安全级别不变),通过后端代理绕过所有三层限制: ``` 前端 img.src = "/api/avatar/proxy?url=" | v 后端 httpx.AsyncClient → wework.qpic.cn | v 返回图片字节流 (同域, HTTPS, 不受 CSP 限制) ``` ## 修改文件 | 文件 | 操作 | 修改内容 | |-----|------|---------| | `backend/app/services/avatar_service.py` | 修改 | `clean_avatar_url` 增加 HTTP→HTTPS 转换;新增 `wrap_avatar_url` 代理 URL 包装函数 | | `backend/app/services/session_service.py` | 修改 | 3个头像返回路径统一应用 `wrap_avatar_url` | | `backend/app/api/avatar.py` | 新建 | 头像代理端点 `/api/avatar/proxy`,SSRF 白名单校验 `wework.qpic.cn`,`httpx.AsyncClient` 异步获取,返回 `Response(content=..., media_type="image/jpeg")` | | `backend/app/api/router.py` | 修改 | 注册 `avatar_router` 路由,前缀 `/api/avatar/proxy` | ## 验证结果 ### 1. API 层(curl) | 测试项 | 结果 | |-------|------| | 代理 HTTPS 头像 URL | ✅ HTTP 200, image/jpeg, 42KB | | 代理 HTTP 头像 URL(自动转 HTTPS) | ✅ HTTP 200, image/jpeg, 65KB | | 无效 URL(非企微域名) | ✅ 403 拒绝 | ### 2. 浏览器验证(agent-browser) - ✅ 坐席工作台正常加载,无重定向到登录页 - ✅ **3个头像全部通过代理加载**: - `GET /api/avatar/proxy?url=... (Image) 200` × 3 - ✅ **控制台零错误**:无 COEP、无 Mixed Content、无 CSP 报错 - ✅ **网络请求零 4xx/5xx** - ✅ 头像在左侧会话列表和顶部导航栏均正常显示 ### 3. 截图证据 见 `screenshot-1783683005062.png` — 坐席工作台界面,头像正常显示在左侧列表和顶部用户信息区域。 ## 部署摘要 1. 上传 4 个修改文件 → `/tmp/` 2. `cp` 到 `/opt/wecom-it-desk/app/` 对应路径 3. `docker compose restart backend` 4. 清除 Redis 头像缓存 `employee:avatar:*`(3 个 key) 5. 代理端点 `/api/avatar/proxy` 即时生效 ## 关于"获取坐席列表失败" 本次浏览器验证中未复现该错误。该错误与头像代理无关,如持续出现需检查后端日志中 `/api/agents` 的具体报错(可能为认证或数据库问题)。