WIP-CHECKPOINT[auth-refactor]: 固化工程师崩溃前部分成果 + 同树其他未提交WIP(仅源码,不含密钥/二进制)-- 待重激活工程师续作
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# =============================================================================
|
||||
# 企微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}"
|
||||
)
|
||||
Reference in New Issue
Block a user