# ============================================================================= # 企微IT智能服务台 — 员工 API # ============================================================================= # 说明:提供员工相关的管理接口 # 接口列表: # PUT /api/employees/{employee_id}/it-level — 更新员工IT技能等级 # POST /api/employees/{employee_id}/avatar/refresh — 手动刷新员工头像 # ============================================================================= from typing import Optional from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field, field_validator from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession import redis.asyncio as aioredis from app.utils.response import success_response from app.schemas.employee import VALID_IT_LEVELS, VALID_LEVEL_SOURCES from app.database import get_db from app.core.config import settings from app.models.employee import Employee from app.dependencies import dep_redis # 导入日志 import logging logger = logging.getLogger(__name__) # 创建路由器 router = APIRouter(prefix="/employees", tags=["员工管理"]) # -------------------------------------------------------------------------- # 请求 Schema # -------------------------------------------------------------------------- class ItLevelUpdateRequest(BaseModel): """IT技能等级更新请求 Schema。""" it_level: str = Field(..., description="IT技能等级: bronze/silver/gold/platinum/diamond/star/king") source: str = Field(default="manual", description="等级来源: system/manual/assessment") @field_validator("it_level") @classmethod def validate_it_level(cls, v: str) -> str: """校验IT等级值是否合法。""" if v not in VALID_IT_LEVELS: raise ValueError(f"无效的IT等级: {v},合法值为: {VALID_IT_LEVELS}") return v @field_validator("source") @classmethod def validate_source(cls, v: str) -> str: """校验等级来源值是否合法。""" if v not in VALID_LEVEL_SOURCES: raise ValueError(f"无效的等级来源: {v},合法值为: {VALID_LEVEL_SOURCES}") return v class ItLevelUpdateResponse(BaseModel): """IT技能等级更新响应 Schema。""" employee_id: str it_level: str it_level_source: str message: str # -------------------------------------------------------------------------- # Mock 员工数据存储(IT 等级映射) # -------------------------------------------------------------------------- # 简单的内存存储,key 为 employee_id,value 为 it_level MOCK_EMPLOYEE_IT_LEVELS: dict = { "emp-001": "silver", "emp-002": "gold", "emp-003": "bronze", "emp-004": "platinum", "emp-005": "diamond", "emp-006": "silver", "emp-007": "star", "emp-008": "king", } # -------------------------------------------------------------------------- # API 接口 # -------------------------------------------------------------------------- @router.put("/{employee_id}/it-level") async def update_employee_it_level( employee_id: str, request: ItLevelUpdateRequest, ): """更新员工IT技能等级。 坐席可以手动调整员工的IT技能等级,等级来源标记为 manual。 更新后等级立即生效,并记录来源以便追溯。 Args: employee_id: 员工ID request: 等级更新请求 Returns: 更新结果 """ # 更新内存中的等级 old_level = MOCK_EMPLOYEE_IT_LEVELS.get(employee_id, "silver") MOCK_EMPLOYEE_IT_LEVELS[employee_id] = request.it_level # 构造等级名称映射 level_names = { "bronze": "青铜", "silver": "白银", "gold": "黄金", "platinum": "铂金", "diamond": "钻石", "star": "星耀", "king": "王者", } return success_response(data=ItLevelUpdateResponse( employee_id=employee_id, it_level=request.it_level, it_level_source=request.source, message=f"IT等级已从 {level_names.get(old_level, old_level)} 调整为 {level_names.get(request.it_level, request.it_level)}", ).model_dump()) # -------------------------------------------------------------------------- # 头像刷新 API # -------------------------------------------------------------------------- class AvatarRefreshResponse(BaseModel): """头像刷新响应 Schema。""" employee_id: str avatar: str message: str async def get_redis() -> aioredis.Redis: """获取Redis客户端依赖""" redis = await dep_redis() if redis is None: raise HTTPException(status_code=500, detail="Redis连接不可用") return redis @router.post("/{employee_id}/avatar/refresh", response_model=dict) async def refresh_employee_avatar( employee_id: str, db: AsyncSession = Depends(get_db), redis: aioredis.Redis = Depends(get_redis), ): """手动刷新员工头像。 调用企微通讯录API获取最新头像URL,更新数据库并刷新Redis缓存。 支持手动触发头像更新,适用于头像URL过期或需要立即更新的场景。 Args: employee_id: 员工ID Returns: 更新后的头像URL """ from datetime import datetime from app.services.session_service import SessionService # 1. 查找员工记录 result = await db.execute( select(Employee).where( Employee.employee_id == employee_id, Employee.corp_id == settings.wecom_corp_id ) ) employee = result.scalars().first() if not employee: raise HTTPException(status_code=404, detail=f"员工不存在: {employee_id}") # 2. 使用 SessionService 从企微API获取最新头像 session_service = SessionService(db, redis_client=redis) new_avatar = "" try: # 调用企微API获取最新头像 from app.services.wecom_service import WeComService wecom_service = WeComService() user_info = await wecom_service.get_user_info(employee_id) new_avatar = user_info.get("avatar", "") logger.info(f"企微API返回头像: employee_id={employee_id}, avatar={'有值(' + str(len(new_avatar)) + '字符)' if new_avatar else '空'}") # 3. 更新数据库 employee.avatar = new_avatar employee.avatar_updated_at = datetime.utcnow() await db.commit() # 4. 刷新Redis缓存 cache_key = f"employee:avatar:{employee_id}" if redis: try: if new_avatar: await redis.setex(cache_key, SessionService.AVATAR_CACHE_TTL, new_avatar) else: # 如果头像为空,删除缓存 await redis.delete(cache_key) except Exception as e: logger.warning(f"刷新Redis头像缓存失败: employee_id={employee_id}, error={e}") return success_response(data=AvatarRefreshResponse( employee_id=employee_id, avatar=new_avatar, message="头像刷新成功" if new_avatar else "企微API未返回头像,已使用原头像", ).model_dump()) except Exception as e: logger.error(f"刷新头像失败: employee_id={employee_id}, error={e}") # 返回原头像,不阻塞流程 return success_response(data=AvatarRefreshResponse( employee_id=employee_id, avatar=employee.avatar, message=f"头像刷新失败,使用原头像: {str(e)}", ).model_dump())