313 lines
12 KiB
Python
313 lines
12 KiB
Python
# =============================================================================
|
||
# 企微IT智能服务台 — 外部系统适配器抽象基类 + 统一数据模型
|
||
# =============================================================================
|
||
# 说明:
|
||
# 1. 定义所有外部系统共用的抽象接口(ABC)
|
||
# 2. 定义统一的DTO模型(TerminalInfo/SecurityStatus/VpnSession)
|
||
# 3. 每个外部系统实现此接口,上层业务只依赖抽象接口
|
||
#
|
||
# 设计原则:
|
||
# - 默认返回None/空 — 子类按需覆写自己支持的方法
|
||
# - 不支持的能力不报错,返回None让调用方走降级逻辑
|
||
# - raw_data字段保留原始响应,调试用,生产环境可关闭
|
||
# =============================================================================
|
||
|
||
import logging
|
||
from abc import ABC, abstractmethod
|
||
from datetime import datetime
|
||
from typing import Dict, List, Optional
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
# =============================================================================
|
||
# 统一数据模型(DTO)
|
||
# =============================================================================
|
||
|
||
class TerminalInfo(BaseModel):
|
||
"""统一终端信息模型 — 所有Adapter返回同一结构
|
||
|
||
做什么:把联软/火绒/aTrust不同格式的终端数据映射到统一结构
|
||
为什么:上层业务代码不需要关心数据来自哪个系统
|
||
"""
|
||
|
||
# ── 来源标识 ──
|
||
source_system: str = Field(..., description="数据来源系统标识: lianruan/huorong/atrust/ehr")
|
||
|
||
# ── 基础标识 ──
|
||
terminal_id: Optional[str] = Field(None, description="终端在来源系统中的唯一ID")
|
||
computer_name: str = Field(..., description="计算机名")
|
||
|
||
# ── 网络信息 ──
|
||
ip_addresses: List[str] = Field(default_factory=list, description="IP地址列表(含VPN虚拟IP)")
|
||
mac_addresses: List[str] = Field(default_factory=list, description="MAC地址列表")
|
||
|
||
# ── 系统信息 ──
|
||
os_version: Optional[str] = Field(None, description="操作系统版本")
|
||
is_online: bool = Field(False, description="是否在线")
|
||
|
||
# ── 用户映射(核心字段)──
|
||
logged_in_user: Optional[str] = Field(None, description="当前登录用户账号 — 映射核心字段")
|
||
logged_in_user_name: Optional[str] = Field(None, description="用户姓名")
|
||
department: Optional[str] = Field(None, description="所属部门")
|
||
|
||
# ── 硬件摘要 ──
|
||
hardware_summary: Optional[Dict] = Field(None, description="硬件摘要(CPU/内存/磁盘使用率等)")
|
||
|
||
# ── 时间信息 ──
|
||
last_seen: Optional[datetime] = Field(None, description="最后在线时间")
|
||
|
||
# ── 调试用 ──
|
||
raw_data: Optional[Dict] = Field(None, description="原始响应数据(调试用,生产可关闭)")
|
||
|
||
|
||
class VulnerabilityItem(BaseModel):
|
||
"""漏洞条目"""
|
||
name: str = Field(..., description="漏洞名称")
|
||
level: str = Field("info", description="严重程度: critical/high/medium/low/info")
|
||
description: Optional[str] = Field(None, description="漏洞描述")
|
||
publish_time: Optional[str] = Field(None, description="发布时间")
|
||
|
||
|
||
class SecurityStatus(BaseModel):
|
||
"""统一安全状态模型
|
||
|
||
做什么:聚合火绒的病毒/漏洞/隔离数据
|
||
为什么:坐席需要一目了然看到终端安全全貌
|
||
"""
|
||
|
||
source_system: str = Field(..., description="数据来源系统标识")
|
||
terminal_id: str = Field(..., description="终端ID")
|
||
computer_name: Optional[str] = Field(None, description="计算机名")
|
||
|
||
# ── 安全指标 ──
|
||
virus_total: int = Field(0, description="病毒事件总数")
|
||
virus_uncleaned: int = Field(0, description="未处理病毒数")
|
||
vulnerabilities: List[VulnerabilityItem] = Field(default_factory=list, description="高危漏洞列表")
|
||
high_vuln_count: int = Field(0, description="高危漏洞数量")
|
||
|
||
# ── 隔离状态 ──
|
||
is_isolated: bool = Field(False, description="是否被隔离")
|
||
isolation_source: Optional[str] = Field(None, description="隔离来源系统")
|
||
|
||
# ── 检查时间 ──
|
||
checked_at: datetime = Field(default_factory=datetime.now, description="检查时间")
|
||
|
||
|
||
class VpnSession(BaseModel):
|
||
"""VPN会话模型(仅aTrust)
|
||
|
||
做什么:描述一个aTrust VPN在线会话
|
||
为什么:坐席需要知道远程员工是否通过VPN在线、VPN IP是什么
|
||
"""
|
||
|
||
source_system: str = "atrust"
|
||
session_id: Optional[str] = Field(None, description="会话ID(用于踢出操作)")
|
||
username: str = Field(..., description="用户名(登录名)")
|
||
display_name: Optional[str] = Field(None, description="显示名")
|
||
remote_ip: str = Field(..., description="接入IP(公网IP或'内网IP')")
|
||
vpn_ip: Optional[str] = Field(None, description="VPN虚拟内网IP — 火绒交叉匹配关键字段")
|
||
is_trusted: bool = Field(False, description="终端是否已授信")
|
||
os: Optional[str] = Field(None, description="接入终端操作系统")
|
||
last_login: Optional[datetime] = Field(None, description="最后登录时间")
|
||
domain: Optional[str] = Field(None, description="登录域")
|
||
|
||
|
||
# =============================================================================
|
||
# 适配器抽象基类
|
||
# =============================================================================
|
||
|
||
class ExternalSystemAdapter(ABC):
|
||
"""外部系统适配器抽象基类
|
||
|
||
做什么:定义所有外部系统共用的接口规范
|
||
为什么:让上层业务代码只依赖抽象接口,不感知底层系统差异
|
||
|
||
设计原则:
|
||
- 默认方法返回None/空列表/False,子类按需覆写自己支持的能力
|
||
- 不支持的能力不报错,让调用方走降级逻辑
|
||
- 每个Adapter只负责一个外部系统的对接
|
||
"""
|
||
|
||
@property
|
||
@abstractmethod
|
||
def system_name(self) -> str:
|
||
"""系统标识名称
|
||
|
||
返回值: 'lianruan' / 'huorong' / 'atrust' / 'ehr' / 'mock'
|
||
"""
|
||
...
|
||
|
||
@property
|
||
@abstractmethod
|
||
def is_available(self) -> bool:
|
||
"""当前系统是否可用(凭证已配置+网络可达)
|
||
|
||
做什么:检查配置是否完整,不实际发起网络请求
|
||
为什么:调用方可据此决定是否跳过本系统
|
||
"""
|
||
...
|
||
|
||
@abstractmethod
|
||
async def health_check(self) -> bool:
|
||
"""健康检查 — 验证凭证和网络连通性
|
||
|
||
做什么:实际发起一次轻量级API调用,确认系统可达
|
||
为什么:定期健康检查可提前发现连接问题
|
||
"""
|
||
...
|
||
|
||
# =========================================================================
|
||
# 终端查询能力
|
||
# =========================================================================
|
||
|
||
async def get_terminal_by_user(self, username: str) -> Optional[TerminalInfo]:
|
||
"""通过员工账号查询终端信息(映射核心方法)
|
||
|
||
做什么:输入员工账号,返回该员工使用的终端信息
|
||
为什么:这是员工→终端映射的核心入口
|
||
|
||
各系统实现方式:
|
||
- 联软:queryDevByParams(strusername=xxx) — 精确匹配
|
||
- 火绒:_list(ip=xxx) — 需配合联软IP交叉匹配
|
||
- aTrust:queryAll(bindUserList) — 终端绑定用户
|
||
- eHR:不提供终端数据,返回None
|
||
|
||
Args:
|
||
username: 员工账号(如 'songxian')
|
||
|
||
Returns:
|
||
TerminalInfo 或 None(系统不支持或未找到)
|
||
"""
|
||
return None
|
||
|
||
async def get_terminal_by_computer(self, computer_name: str) -> Optional[TerminalInfo]:
|
||
"""通过计算机名查询终端信息
|
||
|
||
Args:
|
||
computer_name: 计算机名(如 'IT-SONGXIAN')
|
||
"""
|
||
return None
|
||
|
||
async def get_terminal_detail(self, terminal_id: str) -> Optional[TerminalInfo]:
|
||
"""查询终端详细信息(硬件/软件/网络配置)
|
||
|
||
做什么:返回比 get_terminal_by_user 更详细的信息
|
||
为什么:排查时需要硬件配置、磁盘使用率、已安装软件等
|
||
|
||
各系统实现方式:
|
||
- 联软:getDevAllInfo — 极详细(主板/CPU/内存/硬盘/网卡/显示器)
|
||
- 火绒:_info2 — 中等详细(硬件/软件/网络配置)
|
||
- aTrust/eHR:不支持
|
||
|
||
Args:
|
||
terminal_id: 终端在来源系统中的唯一ID
|
||
"""
|
||
return None
|
||
|
||
# =========================================================================
|
||
# 安全能力
|
||
# =========================================================================
|
||
|
||
async def get_security_status(self, terminal_id: str) -> Optional[SecurityStatus]:
|
||
"""获取终端安全状态(病毒/漏洞/隔离状态)
|
||
|
||
做什么:聚合安全指标,坐席一目了然
|
||
为什么:安全问题通常需要紧急处理
|
||
|
||
仅火绒支持此接口。
|
||
|
||
Args:
|
||
terminal_id: 终端ID(火绒的client_id)
|
||
"""
|
||
return None
|
||
|
||
async def isolate_terminal(self, terminal_id: str, reason: str) -> bool:
|
||
"""隔离终端(断网)
|
||
|
||
做什么:调用火绒 _create(type=netctrl) 隔离终端
|
||
为什么:安全事件紧急处理,阻断威胁扩散
|
||
|
||
仅火绒支持。调用前必须二次确认+审计日志记录。
|
||
|
||
Args:
|
||
terminal_id: 终端ID
|
||
reason: 隔离原因(记入审计日志)
|
||
|
||
Returns:
|
||
True=成功, False=失败
|
||
|
||
Raises:
|
||
NotImplementedError: 本系统不支持隔离操作
|
||
"""
|
||
raise NotImplementedError(f"{self.system_name} 不支持终端隔离")
|
||
|
||
async def unisolate_terminal(self, terminal_id: str) -> bool:
|
||
"""解除终端隔离(恢复网络)
|
||
|
||
仅火绒支持。
|
||
|
||
Args:
|
||
terminal_id: 终端ID
|
||
|
||
Returns:
|
||
True=成功, False=失败
|
||
"""
|
||
raise NotImplementedError(f"{self.system_name} 不支持解除隔离")
|
||
|
||
# =========================================================================
|
||
# VPN/在线状态能力
|
||
# =========================================================================
|
||
|
||
async def get_vpn_sessions(self, username: Optional[str] = None) -> List[VpnSession]:
|
||
"""查询VPN在线会话
|
||
|
||
做什么:获取当前通过aTrust在线的VPN会话
|
||
为什么:坐席需要知道远程员工VPN状态和IP
|
||
|
||
仅aTrust支持。
|
||
|
||
Args:
|
||
username: 可选,过滤指定用户
|
||
|
||
Returns:
|
||
VPN会话列表
|
||
"""
|
||
return []
|
||
|
||
async def get_online_status(self, username: str) -> bool:
|
||
"""查询用户是否在线
|
||
|
||
做什么:检查用户终端是否当前在线
|
||
为什么:坐席需要知道用户是否可达
|
||
|
||
各系统实现方式:
|
||
- 联软:existOnlineUser
|
||
- 火绒:_list(is_online=True) + IP交叉匹配
|
||
- aTrust:getUserStatus
|
||
|
||
Args:
|
||
username: 员工账号
|
||
|
||
Returns:
|
||
True=在线, False=离线或未知
|
||
"""
|
||
return False
|
||
|
||
# =========================================================================
|
||
# 辅助方法
|
||
# =========================================================================
|
||
|
||
def _log_not_implemented(self, method_name: str) -> None:
|
||
"""记录未实现方法的调试日志
|
||
|
||
做什么:当子类未覆写某个方法时记录DEBUG级日志
|
||
为什么:开发期帮助发现调用链路问题,生产环境可关闭DEBUG
|
||
"""
|
||
logger.debug(
|
||
f"[{self.system_name}] {method_name} 未实现,"
|
||
f"将走降级逻辑"
|
||
)
|