Files
wecom_it_smart_desk/docs/ExternalSystemAdapter设计文档.md

306 lines
12 KiB
Markdown
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.
# ExternalSystemAdapter 抽象层设计文档
> 版本:V1.0 | 日期:2026-06-11 | 作者:IT智能服务台项目组
---
## 一、设计目标
为联软、火绒、aTrust、eHR 四个外部系统提供**统一适配层**,实现:
1. **接口统一**:上层业务代码只依赖抽象接口,不感知底层系统差异
2. **可替换性**:Mock数据开发 → 真实API无缝切换,只需改配置
3. **缓存透明**:外部数据自动缓存+定时刷新,业务层无感
4. **降级安全**:外部系统不可用时自动降级,不阻断主流程
5. **横向扩展**:新增系统只需实现一个 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
```python
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抽象基类
```python
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交叉匹配
aTrustqueryAll(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 统一门面服务
```python
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. aTrustVPN源,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次 → 失败则记录待执行队列 → 告警坐席 | 安全操作不静默失败 |
---
## 七、配置管理
```python
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 |