Files
wecom_it_smart_desk/backend/app/dependencies/automation.py

99 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# 企微IT智能服务台 — 阶段5 自动化 依赖注入
# =============================================================================
# 说明:提供自动化接口的认证/鉴权依赖:
# 1. get_current_employee_id — 从 Bearer Token 解析 H5 员工身份
# Redis key 与 app/api/ws.py 的 employee:token:{token} 保持一致)
# 2. get_automation_agent — 复用坐席认证(转人工接管/审批用)
# 3. get_scenario_config — 加载并校验场景配置是否启用
# 高危配置写接口复用 app.dependencies.require_high_risk_otp(管理端 OTP)。
# =============================================================================
from __future__ import annotations
import logging
from typing import Optional
from fastapi import Depends
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.constants import AutomationErrorCode
from app.database import get_db
from app.dependencies import get_redis
from app.models.automation import ScenarioConfig
from app.services.cache_service import cache_service
from app.utils.response import AppException, ERR_UNAUTHORIZED
logger = logging.getLogger(__name__)
# Bearer 认证方案(auto_error=False:缺失时由我们自行返回 1002)
_security = HTTPBearer(auto_error=False)
async def get_current_employee_id(
credentials: Optional[HTTPAuthorizationCredentials] = Depends(_security),
) -> str:
"""从 Bearer Token 解析 H5 员工身份。
与 app/api/ws.py 中 H5 WebSocket 的鉴权保持一致:
Redis key = employee:token:{token} → employee_id。
Returns:
str: 员工企微 UserID
Raises:
AppException(1002): 令牌缺失或无效
"""
token = credentials.credentials if credentials else ""
if not token:
raise AppException(ERR_UNAUTHORIZED.code, "缺少认证令牌")
# 优先从共享 Rediscache_service),降级到独立连接
employee_id = None
try:
employee_id = await cache_service.get(f"employee:token:{token}")
except Exception as e:
logger.warning(f"employee token 校验 Redis 异常: {e}")
if not employee_id:
redis_client = await get_redis()
if redis_client is not None:
try:
employee_id = await redis_client.get(f"employee:token:{token}")
except Exception as e: # noqa: BLE001
logger.warning(f"employee token 校验失败: {e}")
if not employee_id:
raise AppException(ERR_UNAUTHORIZED.code, "员工令牌无效或已过期")
return employee_id
async def get_automation_agent(agent=Depends(lambda: None)) -> str: # pragma: no cover
"""占位:坐席认证由 app.api.agents.get_current_agent 直接提供。
本函数保留以便路由层统一引用;实际坐席端点应使用
`from app.api.agents import get_current_agent`。
"""
return agent
async def get_scenario_config(
scenario_key: str,
db: AsyncSession = Depends(get_db),
) -> ScenarioConfig:
"""加载场景配置,并校验是否启用。
Raises:
AppException(4001): 场景未配置或已禁用
"""
stmt = select(ScenarioConfig).where(ScenarioConfig.scenario_key == scenario_key)
result = await db.execute(stmt)
config = result.scalar_one_or_none()
if config is None or not config.enabled:
raise AppException(
AutomationErrorCode.SCENARIO_NOT_FOUND,
f"场景未启用或未配置: {scenario_key}",
)
return config