# ============================================================================= # 企微IT智能服务台 — 外部系统数据缓存层 # ============================================================================= # 说明: # 1. 封装外部系统数据的缓存读写逻辑 # 2. 统一缓存key格式:ext:{system}:{method}:{param_hash} # 3. 不同数据类型使用不同TTL(终端映射30分钟、安全状态5分钟等) # 4. Redis不可用时自动降级(不缓存,直接透传) # # 与 CacheService 的关系: # CacheService 是全局Redis客户端封装,ExternalSystemCache 基于它 # 添加外部系统专用的缓存策略(TTL、key格式、刷新机制) # ============================================================================= import hashlib import json import logging from datetime import datetime from typing import Any, Dict, Optional from app.services.cache_service import CacheService logger = logging.getLogger(__name__) # ============================================================================= # 缓存TTL配置(秒) # ============================================================================= CACHE_TTL = { # 终端映射(员工→终端)— 映射关系不常变,缓存较长 "terminal_mapping": 30 * 60, # 30分钟 # 终端详情(硬件/软件)— 硬件配置极少变,缓存最长 "terminal_detail": 60 * 60, # 60分钟 # 安全状态(漏洞/病毒)— 安全状态需近实时,缓存短 "security_status": 5 * 60, # 5分钟 # VPN在线状态 — 在线状态变化快,缓存最短 "vpn_status": 1 * 60, # 1分钟 # eHR员工信息 — 静态数据,缓存最长 "employee_info": 24 * 60 * 60, # 24小时 } class ExternalSystemCache: """外部系统数据缓存 做什么:为外部系统查询结果提供统一缓存读写 为什么:减少外部API调用频率,降低延迟和出错率 降级策略:Redis不可用时,缓存读写均跳过,直接透传到外部系统 """ def __init__(self, cache_service: Optional[CacheService] = None): """初始化缓存层 Args: cache_service: Redis缓存服务实例。None时降级为无缓存模式 """ self._cache = cache_service @staticmethod def _make_key(system: str, method: str, param: str) -> str: """生成缓存key 做什么:按统一格式生成缓存key 为什么:避免不同系统/方法的key冲突 Args: system: 系统标识(lianruan/huorong/atrust/ehr) method: 方法名(terminal_mapping/terminal_detail/...) param: 查询参数(用户名/计算机名等) Returns: 缓存key,格式: ext:lianruan:terminal_mapping:abc123 """ # 对参数做哈希,避免特殊字符问题 param_hash = hashlib.md5(param.encode()).hexdigest()[:12] return f"ext:{system}:{method}:{param_hash}" async def get(self, system: str, method: str, param: str) -> Optional[Dict]: """从缓存读取数据 做什么:按系统+方法+参数查找缓存 为什么:命中缓存可避免一次外部API调用 Args: system: 系统标识 method: 方法名 param: 查询参数 Returns: 缓存的字典数据,或 None(未命中/Redis不可用) """ if not self._cache or not self._cache.redis: return None key = self._make_key(system, method, param) try: data = await self._cache.get(key) if data: logger.debug(f"缓存命中: {key}") return json.loads(data) if isinstance(data, str) else data return None except Exception as e: # Redis错误不阻断业务,降级为无缓存 logger.warning(f"缓存读取失败(降级为无缓存): {key}, error={e}") return None async def set( self, system: str, method: str, param: str, data: Dict, ttl_override: Optional[int] = None, ) -> bool: """写入缓存 做什么:将外部系统查询结果存入缓存 为什么:后续相同查询可直接命中缓存 Args: system: 系统标识 method: 方法名 param: 查询参数 data: 要缓存的数据 ttl_override: 自定义TTL(秒),None则使用默认TTL Returns: True=成功, False=失败/Redis不可用 """ if not self._cache or not self._cache.redis: return False key = self._make_key(system, method, param) ttl = ttl_override or CACHE_TTL.get(method, 5 * 60) # 默认5分钟 try: # 添加缓存时间戳,便于判断数据新鲜度 data_with_meta = { **data, "_cached_at": datetime.now().isoformat(), "_source_system": system, } await self._cache.set(key, json.dumps(data_with_meta, default=str), ex=ttl) logger.debug(f"缓存写入: {key}, TTL={ttl}s") return True except Exception as e: logger.warning(f"缓存写入失败: {key}, error={e}") return False async def invalidate(self, system: str, method: str, param: str) -> bool: """主动失效缓存 做什么:删除指定缓存条目 为什么:外部数据变更时(如终端隔离后),需主动失效缓存 Args: system: 系统标识 method: 方法名 param: 查询参数 Returns: True=成功, False=失败/Redis不可用 """ if not self._cache or not self._cache.redis: return False key = self._make_key(system, method, param) try: await self._cache.delete(key) logger.debug(f"缓存失效: {key}") return True except Exception as e: logger.warning(f"缓存失效失败: {key}, error={e}") return False