92 lines
3.6 KiB
Python
92 lines
3.6 KiB
Python
# =============================================================================
|
|
# 企微IT智能服务台 — 员工头像同步服务
|
|
# =============================================================================
|
|
# 说明:集中处理"从企微拿到头像 URL 后,更新 employee 表 + 清 Redis 缓存"的逻辑,
|
|
# 确保所有登录/认证路径(H5 OAuth、坐席密码、扫码确认、dev 登录)行为一致,
|
|
# 避免部分路径漏清缓存导致前端仍是旧图(要求 C:全路径一致性)。
|
|
#
|
|
# 设计原则:
|
|
# 1. 头像更新失败绝不阻塞登录主流程 → 所有异常内部吞掉并记录 warning。
|
|
# 2. 复用已有写法:先查 Employee,有则 update 字段,无则跳过(创建由各自登录逻辑负责)。
|
|
# 3. 统一清理 Redis 缓存 key = employee:avatar:{employee_id},强制后续读库取最新。
|
|
# 4. 清理企微头像 URL 的多余查询参数,保留稳定部分,降低 404 / 过期概率(要求 B)。
|
|
# =============================================================================
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import settings
|
|
from app.models.employee import Employee
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def clean_avatar_url(url: str) -> str:
|
|
"""清理企微头像 URL,去掉查询字符串(? 及其之后),保留稳定部分。
|
|
|
|
企微头像 URL 形如 https://wework.qpic.cn/...png?...,常带尺寸 / 时效参数,
|
|
去参后链接更稳定、不易因时效参数失效而 404。
|
|
|
|
Args:
|
|
url: 原始头像 URL
|
|
|
|
Returns:
|
|
str: 去参后的稳定 URL;空输入返回空串。
|
|
"""
|
|
if not url:
|
|
return ""
|
|
return url.split("?", 1)[0]
|
|
|
|
|
|
async def sync_employee_avatar(
|
|
db: AsyncSession,
|
|
redis_client: Optional[object],
|
|
employee_id: str,
|
|
avatar: str,
|
|
) -> None:
|
|
"""用企微返回的头像 URL 同步 employee 表并清缓存。
|
|
|
|
任一异常都内部处理,不向上抛出(头像更新失败绝不能阻塞登录)。
|
|
|
|
Args:
|
|
db: 数据库会话(本函数内部会 commit 一次以落库)
|
|
redis_client: Redis 客户端(可为 None,此时跳过清缓存)
|
|
employee_id: 企微 userid
|
|
avatar: 企微返回的头像 URL(空字符串表示无头像,不更新)
|
|
"""
|
|
# 清理多余查询参数,保留稳定部分
|
|
avatar = clean_avatar_url(avatar)
|
|
if not avatar:
|
|
# 企微未返回头像时不做任何写入,避免把已有头像清空
|
|
return
|
|
|
|
try:
|
|
stmt = select(Employee).where(
|
|
Employee.employee_id == employee_id,
|
|
Employee.corp_id == settings.wecom_corp_id,
|
|
)
|
|
result = await db.execute(stmt)
|
|
employee = result.scalars().first()
|
|
if employee:
|
|
employee.avatar = avatar
|
|
employee.avatar_updated_at = datetime.utcnow()
|
|
await db.commit()
|
|
logger.info(f"同步员工头像: employee_id={employee_id}")
|
|
else:
|
|
logger.debug(f"员工不存在,跳过头像同步: employee_id={employee_id}")
|
|
|
|
# 删除 Redis 头像缓存,强制后续读取数据库最新头像
|
|
if redis_client is not None:
|
|
try:
|
|
await redis_client.delete(f"employee:avatar:{employee_id}")
|
|
except Exception as e:
|
|
logger.warning(f"删除头像Redis缓存失败: employee_id={employee_id}, error={e}")
|
|
except Exception as e:
|
|
logger.warning(
|
|
f"同步员工头像失败(不阻塞登录): employee_id={employee_id}, error={e}"
|
|
)
|