12 KiB
12 KiB
ExternalSystemAdapter 抽象层设计文档
版本:V1.0 | 日期:2026-06-11 | 作者:IT智能服务台项目组
一、设计目标
为联软、火绒、aTrust、eHR 四个外部系统提供统一适配层,实现:
- 接口统一:上层业务代码只依赖抽象接口,不感知底层系统差异
- 可替换性:Mock数据开发 → 真实API无缝切换,只需改配置
- 缓存透明:外部数据自动缓存+定时刷新,业务层无感
- 降级安全:外部系统不可用时自动降级,不阻断主流程
- 横向扩展:新增系统只需实现一个 Adapter,零改动业务层
二、系统角色与优先级
| 系统 | 角色 | 核心能力 | 认证方式 | 凭证状态 |
|---|---|---|---|---|
| 联软LV7000 | 主映射源(P0) | 终端查询(含strusername)、硬件详情、在线状态 | IP白名单+账号密码+Token | 明天可拿 |
| 火绒企业版 | 安全源(P0) | 终端列表、漏洞/病毒事件、一键隔离 | HMAC-SHA1 AccessKey | 现在可拿 |
| aTrust | VPN源(P1) | 在线用户+VPN IP、终端查询、踢出用户 | HMAC-SHA256签名 | 约一周 |
| 北森eHR | 辅助静态数据(P2) | 员工基础信息、任职信息 | OAuth2.0 | 待对接HR |
三、架构分层
┌─────────────────────────────────────────────────┐
│ 上层业务代码(AI Wingman等) │
├─────────────────────────────────────────────────┤
│ ExternalSystemService(统一门面) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 缓存层 │ │ 降级策略 │ │ 配置管理 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────┤
│ ExternalSystemAdapter(抽象基类) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────┐│
│ │ LianRuan │ │ HuoRong │ │ aTrust │ │eHR ││
│ │ Adapter │ │ Adapter │ │ Adapter │ │ 适配││
│ └──────────┘ └──────────┘ └──────────┘ └────┘│
├─────────────────────────────────────────────────┤
│ MockAdapter(开发期) │
└─────────────────────────────────────────────────┘
四、核心抽象接口
4.1 数据模型(统一DTO)
class TerminalInfo(BaseModel):
"""统一终端信息模型 — 所有Adapter返回同一结构"""
source_system: str # 数据来源系统标识
computer_name: str # 计算机名
ip_addresses: List[str] # IP地址列表(含VPN虚拟IP)
mac_addresses: List[str] # MAC地址列表
os_version: Optional[str] # 操作系统版本
is_online: bool # 是否在线
logged_in_user: Optional[str] # 当前登录用户账号(映射核心字段)
logged_in_user_name: Optional[str] # 用户姓名
department: Optional[str] # 所属部门
hardware_summary: Optional[Dict] # 硬件摘要(CPU/内存/磁盘)
last_seen: Optional[datetime] # 最后在线时间
raw_data: Optional[Dict] # 原始响应(调试用,生产可关闭)
class SecurityStatus(BaseModel):
"""统一安全状态模型"""
source_system: str
terminal_id: str
virus_events: Optional[Dict] # 病毒事件统计
vulnerabilities: Optional[List] # 高危漏洞列表
is_isolated: bool # 是否被隔离
isolation_source: Optional[str] # 隔离来源系统
class VpnSession(BaseModel):
"""VPN会话模型(仅aTrust)"""
source_system: str = "atrust"
username: str
display_name: Optional[str]
remote_ip: str
vpn_ip: Optional[str] # 虚拟内网IP
is_trusted: bool
last_login: Optional[datetime]
4.2 Adapter抽象基类
from abc import ABC, abstractmethod
from typing import Optional, List
class ExternalSystemAdapter(ABC):
"""外部系统适配器抽象基类
每个外部系统实现此接口,上层业务只依赖此接口。
"""
@property
@abstractmethod
def system_name(self) -> str:
"""系统标识名称,如 'lianruan' / 'huorong' / 'atrust' / 'ehr'"""
...
@property
@abstractmethod
def is_available(self) -> bool:
"""当前系统是否可用(凭证已配置+网络可达)"""
...
@abstractmethod
async def health_check(self) -> bool:
"""健康检查 — 验证凭证和网络连通性"""
...
# ── 终端查询能力 ──
async def get_terminal_by_user(self, username: str) -> Optional[TerminalInfo]:
"""通过员工账号查询终端信息(映射核心方法)
联软:queryDevByParams(strusername=xxx)
火绒:_list(ip=xxx) 需配合联软IP交叉匹配
aTrust:queryAll(bindUserList) 终端绑定用户
eHR:不提供终端数据,返回None
"""
return None # 默认不支持,子类按需覆写
async def get_terminal_by_computer(self, computer_name: str) -> Optional[TerminalInfo]:
"""通过计算机名查询终端信息"""
return None
async def get_terminal_detail(self, terminal_id: str) -> Optional[TerminalInfo]:
"""查询终端详细信息(硬件/软件/网络配置)"""
return None
# ── 安全能力 ──
async def get_security_status(self, terminal_id: str) -> Optional[SecurityStatus]:
"""获取终端安全状态(病毒/漏洞/隔离状态)"""
return None
async def isolate_terminal(self, terminal_id: str, reason: str) -> bool:
"""隔离终端(仅火绒支持,需admin角色二次确认)"""
raise NotImplementedError(f"{self.system_name} 不支持终端隔离")
async def unisolate_terminal(self, terminal_id: str) -> bool:
"""解除终端隔离"""
raise NotImplementedError(f"{self.system_name} 不支持解除隔离")
# ── VPN/在线状态 ──
async def get_vpn_sessions(self, username: Optional[str] = None) -> List[VpnSession]:
"""查询VPN在线会话(仅aTrust支持)"""
return []
async def get_online_status(self, username: str) -> bool:
"""查询用户是否在线"""
return False
4.3 统一门面服务
class ExternalSystemService:
"""外部系统统一门面 — 上层业务只调用此类"""
def __init__(self, adapters: Dict[str, ExternalSystemAdapter], cache: CacheService):
self._adapters = adapters # {"lianruan": LianRuanAdapter, ...}
self._cache = cache
async def find_user_terminal(self, username: str) -> Optional[TerminalInfo]:
"""查找用户终端 — 优先联软,降级aTrust,最后eHR
做什么:按映射优先级依次查询,任一系统返回即停止
为什么:联软strusername精确匹配最可靠,aTrust次之
"""
# 1. 联软(主源,strusername精确匹配)
result = await self._query_with_cache("lianruan", "get_terminal_by_user", username)
if result:
return result
# 2. aTrust(VPN源,bindUserList匹配)
result = await self._query_with_cache("atrust", "get_terminal_by_user", username)
if result:
return result
# 3. eHR(静态辅助,无终端数据)
return None
async def get_terminal_security(self, terminal_id: str) -> Optional[SecurityStatus]:
"""获取终端安全状态 — 仅火绒"""
return await self._query_with_cache("huorong", "get_security_status", terminal_id)
async def isolate_terminal(self, terminal_id: str, reason: str, operator: str) -> bool:
"""隔离终端 — 仅火绒,需operator记录审计日志"""
logger.warning(f"终端隔离操作: terminal={terminal_id}, operator={operator}, reason={reason}")
return await self._adapters["huorong"].isolate_terminal(terminal_id, reason)
五、缓存策略
| 数据类型 | 缓存TTL | 刷新策略 | 说明 |
|---|---|---|---|
| 终端映射(员工→终端) | 30分钟 | 定时刷新+访问时检查 | 映射关系不常变 |
| 终端详情(硬件/软件) | 60分钟 | 懒加载 | 硬件配置极少变 |
| 安全状态(漏洞/病毒) | 5分钟 | 短TTL+事件驱动 | 安全状态需近实时 |
| VPN在线状态 | 1分钟 | 短TTL | 在线状态变化快 |
| eHR员工信息 | 24小时 | 每日凌晨全量同步 | 静态数据 |
缓存key格式:ext:{system}:{method}:{param_hash}
六、降级策略
| 故障场景 | 处理方式 | 用户影响 |
|---|---|---|
| 单个系统不可用 | 跳过该系统,尝试下一优先级 | 部分数据缺失,不阻断 |
| 所有外部系统不可用 | 返回缓存数据(如有)+ 明确标注"数据可能过时" | 信息可能过时 |
| 缓存+外部系统均不可用 | 返回空结果+告警通知坐席 | 无法获取外部数据 |
| 火绒隔离操作失败 | 重试1次 → 失败则记录待执行队列 → 告警坐席 | 安全操作不静默失败 |
七、配置管理
class ExternalSystemConfig(BaseModel):
"""外部系统连接配置 — 从环境变量或配置中心读取"""
# 联软
lianruan_base_url: str = "http://192.168.x.x:30098"
lianruan_api_account: Optional[str] = None
lianruan_api_password: Optional[str] = None
# 火绒
huorong_base_url: str = "http://huorong.oa.servyou-it.com:8080"
huorong_access_key_id: Optional[str] = None
huorong_access_key_secret: Optional[str] = None
# aTrust
atrust_base_url: str = "https://atrust.servyou-it.com:4433"
atrust_api_id: Optional[str] = None
atrust_api_secret: Optional[str] = None
atrust_directory_domain: Optional[str] = None
# eHR
ehr_base_url: Optional[str] = None
ehr_client_id: Optional[str] = None
ehr_client_secret: Optional[str] = None
# 全局
cache_enabled: bool = True
mock_mode: bool = False # True时所有请求走MockAdapter
八、目录结构
backend/app/services/external/
├── __init__.py # 模块导出
├── base.py # 抽象基类 ExternalSystemAdapter + 数据模型
├── config.py # 配置管理 ExternalSystemConfig
├── cache.py # 缓存装饰器和策略
├── mock.py # MockAdapter(开发期使用)
├── lianruan_adapter.py # 联软适配器
├── huorong_adapter.py # 火绒适配器
├── atrust_adapter.py # aTrust适配器
├── ehr_adapter.py # eHR适配器
└── service.py # ExternalSystemService 统一门面
九、实施路径
| 阶段 | 内容 | 依赖 |
|---|---|---|
| Step 1 | base.py + config.py + mock.py + service.py + cache.py | 无,立即可做 |
| Step 2 | huorong_adapter.py | 凭证现在可拿 |
| Step 3 | lianruan_adapter.py | 凭证明天可拿 |
| Step 4 | atrust_adapter.py | 凭证约一周 |
| Step 5 | ehr_adapter.py | 待对接HR团队 |
十、与项目阶段的对应关系
| 项目阶段 | Adapter用途 | 对接系统 |
|---|---|---|
| 阶段一(1C) | 不使用 — MVP只跑会话管理 | 无 |
| 阶段二(2B) | 联软终端查询 + 火绒安全状态 | 联软+火绒 |
| 阶段二(2C) | 火绒漏洞/病毒/隔离 | 火绒 |
| 阶段三(3B) | aTrust VPN数据 + AI混合排查 | aTrust |
| 阶段三(3C) | eHR员工信息 + 标注体系 | eHR |