# ============================================================================= # 企微IT智能服务台 — 阶段5 自动化 北森 EHR 客户端(静态映射兜底) # ============================================================================= # 说明:当联软(主映射源)无法解析员工→终端时,使用北森 EHR 提供的 # 员工-部门-资产静态映射作为兜底。 # # 认证:北森开放 API 通常使用 App Key + App Secret(Bearer 或签名)。 # 此处用占位实现:AUTOMATION_EHR_BASE_URL / AUTOMATION_EHR_API_KEY。 # 具体签名方式以真实环境文档为准,结构上可被单测 mock。 # # 接口设计为占位骨架:方法签名完整、异常可捕获、出入参可被 ActionLog 记录, # 本地无真实密钥/环境时仅结构正确,可被单元测试 mock。 # ============================================================================= from __future__ import annotations import logging from typing import Any, Dict, Optional from app.config import settings from app.integrations.base import BaseClient, BaseClientError logger = logging.getLogger(__name__) class EhrClient(BaseClient): """北森 EHR 客户端(静态映射兜底)。""" system_name = "ehr" def __init__(self, api_key: str, base_url: str, timeout=None, audit=None): super().__init__(base_url=base_url, timeout=timeout, audit=audit) self.api_key = api_key def _headers(self) -> Dict[str, str]: return { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", } async def get_employee_profile(self, employee_id: str) -> Dict[str, Any]: """查询员工档案(部门、岗位、资产编号等),作为映射兜底。""" return await self.request( "GET", f"/api/v1/employees/{employee_id}", headers=self._headers(), event="ehr.get_employee_profile", ) async def get_terminal_by_employee(self, employee_id: str) -> Optional[Dict[str, Any]]: """根据员工查兜底终端信息。 北森通常只给资产编号/部门,真正的终端 IP 仍需联软; 此处返回 hint(如 last_known_hostname / asset_no),供 mapping_resolver 合并。 """ try: profile = await self.get_employee_profile(employee_id) return { "employee_id": employee_id, "department": profile.get("department", ""), "asset_no": profile.get("asset_no", ""), "terminal_hint": profile.get("last_known_hostname", ""), "source": "ehr", } except BaseClientError as e: logger.warning(f"EHR 映射兜底失败 employee={employee_id}: {e}") return None async def get_ehr_client(audit=None) -> Optional[EhrClient]: """从 settings 构建 EHR 客户端;未配置返回 None。""" base_url = settings.automation_ehr_base_url api_key = settings.automation_ehr_api_key if not base_url or not api_key: return None return EhrClient(api_key=api_key, base_url=base_url, audit=audit)