chore: initial baseline with P0-safety .gitignore

This commit is contained in:
Simon
2026-06-14 16:49:18 +08:00
commit 63262292d7
510 changed files with 146008 additions and 0 deletions
@@ -0,0 +1,15 @@
# 联软LV7000 API集成模块
"""
提供联软LV7000终端安全管理系统的API客户端。
认证方式:三层认证(IP白名单 + 账号密码 + Token
- 第一层:IP白名单(在联软后台配置WhiteListServerIp
- 第二层:账号密码(ApiAccount + ApiPassword
- 第三层:一次性Token(先调getToken获取,30分钟有效)
核心P0接口:
- queryDevByParams:按条件查询终端(含strusername员工账号映射)
- getDevAllInfo:终端详细信息(硬件+软件+资产+网络)
- getUserInfoByAccount:按账号查用户信息
- getAllOrgInfo:全量组织架构同步
"""
+604
View File
@@ -0,0 +1,604 @@
# 联软LV7000 API客户端
"""
联软LV7000终端安全管理系统 API 客户端。
认证流程:
1. 第一层:IP白名单(在联软后台配置,调用时自动生效)
2. 第二层:账号密码(ApiAccount + ApiPassword
3. 第三层:Token(先调getToken获取,30分钟有效,自动缓存+刷新)
接口调用方式:
- GET请求:参数通过query string传递
- POST请求:参数通过form-data传递
- 统一携带 token + apiAccount + apiPassword + validatekey
使用示例:
client = LianruanClient(base_url, api_account, api_password, validate_key)
terminals = await client.query_dev_by_params(strusername="songxian")
detail = await client.get_dev_all_info(strdevname="IT-SONGXIAN")
"""
import time
import logging
from typing import Optional
import httpx
from app.integrations.lianruan.exceptions import (
LianruanApiError,
LianruanAuthError,
LianruanConnectionError,
)
from app.integrations.lianruan.models import (
TerminalBasicInfo,
TerminalAllInfo,
UserInfo,
OrgDeptInfo,
OnlineStatus,
TerminalSoftwareInfo,
)
logger = logging.getLogger(__name__)
class LianruanClient:
"""联软LV7000 API客户端。
Attributes:
base_url: 联软API地址,如 http://192.168.x.x:30098
api_account: API账号
api_password: API密码
validate_key: 验证密钥
_token: 缓存的Token
_token_expire: Token过期时间戳
"""
def __init__(
self,
base_url: str,
api_account: str,
api_password: str,
validate_key: str = "",
timeout: float = 30.0,
):
self.base_url = base_url.rstrip("/")
self.api_account = api_account
self.api_password = api_password
self.validate_key = validate_key
self.timeout = timeout
# Token缓存(30分钟有效,提前5分钟刷新)
self._token: str = ""
self._token_expire: float = 0.0
# httpx异步客户端(连接池复用)
self._client: Optional[httpx.AsyncClient] = None
async def _get_client(self) -> httpx.AsyncClient:
"""获取或创建httpx异步客户端(懒初始化+连接池复用)"""
if self._client is None or self._client.is_closed:
self._client = httpx.AsyncClient(
timeout=self.timeout,
verify=False, # 内网自签证书
)
return self._client
async def close(self) -> None:
"""关闭httpx客户端,释放连接池"""
if self._client and not self._client.is_closed:
await self._client.aclose()
# ==========================================================================
# Token管理
# ==========================================================================
async def _ensure_token(self) -> str:
"""确保Token有效,过期则自动刷新。
联软Token默认30分钟有效,提前5分钟刷新。
Returns:
str: 有效的Token字符串
"""
now = time.time()
# Token还有5分钟以上有效期,直接复用
if self._token and now < self._token_expire - 300:
return self._token
# 重新获取Token
logger.info("联软Token过期或为空,正在刷新...")
try:
client = await self._get_client()
url = f"{self.base_url}/token"
params = {
"act": "getToken",
"apiAccount": self.api_account,
"apiPassword": self.api_password,
}
if self.validate_key:
params["validatekey"] = self.validate_key
resp = await client.get(url, params=params)
resp.raise_for_status()
data = resp.json()
if data.get("status") != "SUCCESS":
raise LianruanAuthError(
f"获取Token失败: {data.get('msg', '未知错误')}",
detail=str(data),
)
self._token = data.get("data", data.get("rows", ""))
if not self._token:
# 有些版本返回格式不同
self._token = str(data.get("token", ""))
# 30分钟有效期
self._token_expire = now + 1800
logger.info("联软Token刷新成功,有效期至 %s",
time.strftime("%H:%M:%S", time.localtime(self._token_expire)))
return self._token
except httpx.ConnectError as e:
raise LianruanConnectionError(
f"无法连接联软服务器 {self.base_url}: {e}",
detail=str(e),
)
except httpx.TimeoutException as e:
raise LianruanConnectionError(
f"连接联软服务器超时: {e}",
detail=str(e),
)
# ==========================================================================
# 通用请求方法
# ==========================================================================
async def _request(
self,
path: str,
act: str,
params: Optional[dict] = None,
method: str = "GET",
) -> dict:
"""发送请求到联软API。
自动携带认证参数(token + apiAccount + apiPassword)。
Args:
path: API路径,如 /terminal 或 /querydeptuser
act: 操作类型,如 queryDevByParams
params: 额外业务参数
method: 请求方法(GET/POST
Returns:
dict: 联软API返回的JSON数据
Raises:
LianruanAuthError: 认证失败
LianruanApiError: 业务错误
LianruanConnectionError: 网络错误
"""
token = await self._ensure_token()
client = await self._get_client()
# 构建完整参数:认证参数 + 业务参数
full_params = {
"act": act,
"apiAccount": self.api_account,
"apiPassword": self.api_password,
"token": token,
}
if self.validate_key:
full_params["validatekey"] = self.validate_key
if params:
full_params.update(params)
url = f"{self.base_url}{path}"
try:
if method.upper() == "POST":
resp = await client.post(url, data=full_params)
else:
resp = await client.get(url, params=full_params)
resp.raise_for_status()
data = resp.json()
except httpx.ConnectError as e:
raise LianruanConnectionError(
f"无法连接联软服务器: {e}",
detail=str(e),
)
except httpx.TimeoutException as e:
raise LianruanConnectionError(
f"请求联软超时: {e}",
detail=str(e),
)
except httpx.HTTPStatusError as e:
raise LianruanApiError(
f"联软HTTP错误 {e.response.status_code}",
status=str(e.response.status_code),
detail=str(e),
)
# 检查联软业务状态码
status = data.get("status", "")
if status == "INVALID":
# Token可能过期,清除缓存重试一次
self._token = ""
self._token_expire = 0
raise LianruanAuthError(
f"联软认证失败(IP不在白名单或Token无效): {data.get('msg', '')}",
detail=str(data),
)
elif status == "ERROR":
raise LianruanApiError(
f"联软API错误: {data.get('msg', '')}",
status=status,
detail=str(data),
)
elif status == "Exceed":
raise LianruanApiError(
f"联软数据量超限: {data.get('msg', '')}",
status=status,
detail=str(data),
)
elif status != "SUCCESS":
raise LianruanApiError(
f"联软未知状态: {status} - {data.get('msg', '')}",
status=status,
detail=str(data),
)
return data
# ==========================================================================
# P0接口 — 终端设备查询
# ==========================================================================
async def query_dev_by_params(
self,
strusername: str = "",
strdevname: str = "",
strdevip: str = "",
strmac: str = "",
page: int = 1,
per_page: int = 20,
) -> dict:
"""查询终端设备(核心映射接口)。
⭐ strusername 参数可直接按员工账号查终端,这是联软最大的优势!
Args:
strusername: 员工账号(映射金钥匙)
strdevname: 计算机名
strdevip: IP地址
strmac: MAC地址
page: 页码(从1开始)
per_page: 每页条数
Returns:
dict: {"items": [TerminalBasicInfo], "total": int}
"""
params: dict = {}
if strusername:
params["strusername"] = strusername
if strdevname:
params["strdevname"] = strdevname
if strdevip:
params["strdevip"] = strdevip
if strmac:
params["strmac"] = strmac
# 联软分页参数
params["page"] = str(page)
params["rows"] = str(per_page)
data = await self._request("/terminal", "queryDevByParams", params)
rows = data.get("rows", [])
total = data.get("total", len(rows))
items = [TerminalBasicInfo(**row) for row in rows]
return {"items": items, "total": total}
async def get_dev_all_info(
self,
strdevname: str = "",
strdevip: str = "",
) -> TerminalAllInfo:
"""查询终端详细信息(极详细硬件+软件+资产+网络)。
比火绒_info2更丰富,包含逻辑磁盘使用率、显示器信息、内存条详情。
Args:
strdevname: 计算机名(二选一)
strdevip: IP地址(二选一)
Returns:
TerminalAllInfo: 终端详细信息
"""
params: dict = {}
if strdevname:
params["strdevname"] = strdevname
if strdevip:
params["strdevip"] = strdevip
data = await self._request(
"/devallinfoshowwithpaging", "getDevAllInfo", params
)
# 返回格式:data.equipment + data.equipmentdetail
equipment = data.get("equipment", data.get("rows", [{}]))
if isinstance(equipment, list) and equipment:
equipment = equipment[0]
equipment_detail = data.get("equipmentdetail", {})
if isinstance(equipment_detail, list) and equipment_detail:
equipment_detail = equipment_detail[0]
dev_detail = equipment_detail.get("devdetail", equipment_detail)
# 解析硬件详情
result = TerminalAllInfo(
strdevname=equipment.get("strdevname", ""),
strip1=equipment.get("strip1", ""),
strmac=equipment.get("strmac", ""),
strdeptname=equipment.get("strdeptname", ""),
strusername=equipment.get("strusername", ""),
struserdes=equipment.get("struserdes", ""),
stros=equipment.get("stros", ""),
strdomain=equipment.get("strdomain", ""),
istatus=dev_detail.get("istatus", equipment.get("istatus", "")),
strverofuaagent=dev_detail.get("strverofuaagent", ""),
devassetno=dev_detail.get("devassetno", ""),
devgroup=dev_detail.get("devgroup", ""),
)
# 解析硬件列表
self._parse_hardware_list(dev_detail, "CPUInformation", result.cpu)
self._parse_hardware_list(dev_detail, "MemoryInformation", result.memory)
self._parse_hardware_list(dev_detail, "HardDiskInformation", result.hard_disk)
self._parse_hardware_list(dev_detail, "GraphicsCardInformation", result.graphics_card)
self._parse_hardware_list(dev_detail, "MainboardInformation", result.mainboard)
# 解析逻辑磁盘(含使用率)
for ld in dev_detail.get("LogicalDiskInformation", []):
result.logical_disk.append(LogicalDiskInfo(
name=ld.get("strlogicaldiskname", ""),
file_system=ld.get("strfilesystem", ""),
total_size=ld.get("strtotalsize", ""),
free_space=ld.get("strfreespace", ""),
usage_percent=ld.get("strusagepercent", ""),
))
# 解析网卡
for nc in dev_detail.get("NetworkCardInformation", []):
result.network_card.append(NetworkCardInfo(
name=nc.get("strnetcardname", ""),
is_wireless=nc.get("iswireless", ""),
vendor=nc.get("strnetcardvendor", ""),
mac=nc.get("strnetcardmac", ""),
))
# 解析显示器
for d in dev_detail.get("DisplayInformation", []):
result.display.append(DisplayInfo(
vendor=d.get("strdisplayvendor", ""),
model=d.get("strdisplaymodel", ""),
serial=d.get("strdisplayserial", ""),
size=d.get("strdisplaysize", ""),
))
return result
def _parse_hardware_list(
self, dev_detail: dict, key: str, target_list: list
) -> None:
"""解析硬件信息列表(CPU/内存/硬盘等)"""
from app.integrations.lianruan.models import HardwareInfo
for item in dev_detail.get(key, []):
target_list.append(HardwareInfo(
name=item.get("strcpuname", item.get("strmemname", item.get("strdiskname", ""))),
model=item.get("strcpumodel", item.get("strmemmodel", item.get("strdiskmodel", ""))),
vendor=item.get("strcpuvendor", item.get("strmemvendor", item.get("strdiskvendor", ""))),
capacity=item.get("strcpufrequency", item.get("strmemcapacity", item.get("strdiskcapacity", ""))),
serial=item.get("strcpuserial", item.get("strmemserial", item.get("strdiskserial", ""))),
))
# ==========================================================================
# P0接口 — 组织架构/用户
# ==========================================================================
async def get_user_info_by_account(self, useraccount: str) -> Optional[UserInfo]:
"""按账号查询用户信息。
Args:
useraccount: 用户账号
Returns:
UserInfo或None
"""
data = await self._request(
"/querydeptuser",
"getUserInfoByAccount",
{"useraccount": useraccount},
)
rows = data.get("rows", data.get("row", []))
if rows:
row = rows[0] if isinstance(rows, list) else rows
return UserInfo(**row)
return None
async def get_all_org_info(self) -> list[OrgDeptInfo]:
"""获取全量组织架构(部门+用户)。
用于定时同步,构建组织架构映射。
Returns:
list[OrgDeptInfo]: 部门列表,每个部门含用户列表
"""
data = await self._request("/querydeptuser", "getAllOrgInfo")
rows = data.get("rows", [])
result = []
for dept_data in rows:
users = []
for u in dept_data.get("users", []):
users.append(UserInfo(**u))
result.append(OrgDeptInfo(
deptid=dept_data.get("deptid", ""),
deptname=dept_data.get("deptname", ""),
parentid=dept_data.get("parentid", ""),
users=users,
))
return result
# ==========================================================================
# P1接口 — 准入控制
# ==========================================================================
async def exist_online_user(
self, username: str, strdevip: str = ""
) -> OnlineStatus:
"""查询终端用户是否在线。
可精确判断某员工在某IP是否当前在线。
Args:
username: 用户名
strdevip: IP地址(可选)
Returns:
OnlineStatus: 在线状态
"""
params = {"username": username}
if strdevip:
params["strdevip"] = strdevip
data = await self._request(
"/access/onlineUser", "existOnlineUser", params
)
is_online = data.get("data", "0") == "1"
return OnlineStatus(
username=username,
ip=strdevip,
is_online=is_online,
)
# ==========================================================================
# P1接口 — 终端操作
# ==========================================================================
async def notice_agent_msg(
self, strdevip: str, message: str
) -> bool:
"""向终端推送弹窗消息。
Args:
strdevip: 终端IP
message: 消息内容
Returns:
bool: 是否成功
"""
data = await self._request(
"/terminal",
"noticeAgentMsg",
{"strdevip": strdevip, "msg": message},
)
return data.get("status") == "SUCCESS"
async def remote_wake_up(
self, strdevip: str, strmac: str
) -> bool:
"""远程唤醒终端。
Args:
strdevip: 终端IP
strmac: 终端MAC地址
Returns:
bool: 是否成功
"""
data = await self._request(
"/terminal",
"remoteWakeUp",
{"strdevip": strdevip, "strmac": strmac},
)
return data.get("status") == "SUCCESS"
async def query_software_by_dev(
self, strdevname: str = "", strdevip: str = ""
) -> Optional[TerminalSoftwareInfo]:
"""查询终端安装软件。
Args:
strdevname: 计算机名
strdevip: IP地址
Returns:
TerminalSoftwareInfo或None
"""
params: dict = {}
if strdevname:
params["strdevname"] = strdevname
if strdevip:
params["strdevip"] = strdevip
data = await self._request("/software", "querysoftwarebydev", params)
rows = data.get("rows", [])
if not rows:
return None
row = rows[0] if isinstance(rows, list) else rows
softwares = []
for s in row.get("softwares", []):
softwares.append(SoftwareInfo(
name=s.get("strsoftware", ""),
version=s.get("strversion", ""),
vendor=s.get("strvendor", ""),
install_date=s.get("installdate", ""),
))
return TerminalSoftwareInfo(
strdevname=row.get("strdevname", ""),
strdevip=row.get("strdevip", ""),
strmac=row.get("strmac", ""),
strusername=row.get("strusername", ""),
softwares=softwares,
)
# ==========================================================================
# 测试连接
# ==========================================================================
async def test_connection(self) -> dict:
"""测试联软API连接。
使用getToken接口验证:
1. 网络连通性
2. IP白名单
3. 账号密码正确性
4. Token获取成功
Returns:
dict: {"success": bool, "message": str}
"""
try:
token = await self._ensure_token()
if token:
return {
"success": True,
"message": "联软API连接成功,Token获取正常",
}
else:
return {
"success": False,
"message": "Token获取失败,返回为空",
}
except LianruanAuthError as e:
return {"success": False, "message": e.message}
except LianruanConnectionError as e:
return {"success": False, "message": e.message}
except Exception as e:
return {"success": False, "message": f"未知错误: {str(e)}"}
@@ -0,0 +1,98 @@
# 联软LV7000配置管理
"""
从system_configs表读取联软API配置,构建LianruanClient实例。
联软配置键(前缀 integration_lianruan_):
- integration_lianruan_base_url: 联软API地址(如 http://192.168.x.x:30098
- integration_lianruan_api_account: API账号
- integration_lianruan_api_password: API密码
- integration_lianruan_validate_key: 验证密钥(可选)
配置方式:管理后台 → 系统集成 → 联软LV7000 → 填入账号密码
"""
import logging
from sqlalchemy.ext.asyncio import AsyncSession
from app.integrations.lianruan.client import LianruanClient
from app.integrations.lianruan.exceptions import LianruanConfigError
from app.models.system_config import SystemConfig
logger = logging.getLogger(__name__)
# 联软配置键前缀(与 admin_service INTEGRATION_DEFINITIONS 中的 key_prefix 一致)
_PREFIX = "integration_lianruan_"
async def _get_lianruan_config_value(db: AsyncSession, key_suffix: str) -> str:
"""读取单个联软配置值。
Args:
db: 数据库会话
key_suffix: 配置键后缀(如 base_url / api_account
Returns:
str: 配置值,不存在返回空字符串
"""
full_key = f"{_PREFIX}{key_suffix}"
from sqlalchemy import select
result = await db.execute(select(SystemConfig).where(SystemConfig.key == full_key))
config_row = result.scalar_one_or_none()
return config_row.value if config_row else ""
async def get_lianruan_config(db: AsyncSession) -> dict:
"""从system_configs表读取联软配置。
Args:
db: 数据库会话
Returns:
dict: 包含 base_url / api_account / api_password / validate_key
Raises:
LianruanConfigError: 配置缺失
"""
base_url = await _get_lianruan_config_value(db, "base_url")
api_account = await _get_lianruan_config_value(db, "api_account")
api_password = await _get_lianruan_config_value(db, "api_password")
validate_key = await _get_lianruan_config_value(db, "validate_key")
if not base_url:
raise LianruanConfigError("联软API未配置:缺少Base URL")
if not api_account:
raise LianruanConfigError("联软API未配置:缺少API账号")
if not api_password:
raise LianruanConfigError("联软API未配置:缺少API密码")
return {
"base_url": base_url,
"api_account": api_account,
"api_password": api_password,
"validate_key": validate_key,
}
async def get_lianruan_client(db: AsyncSession) -> LianruanClient:
"""构建联软API客户端实例。
从system_configs表读取配置,创建LianruanClient。
Args:
db: 数据库会话
Returns:
LianruanClient: 已配置的联软客户端
Raises:
LianruanConfigError: 配置缺失
"""
cfg = await get_lianruan_config(db)
return LianruanClient(
base_url=cfg["base_url"],
api_account=cfg["api_account"],
api_password=cfg["api_password"],
validate_key=cfg.get("validate_key", ""),
)
@@ -0,0 +1,61 @@
# 联软LV7000异常体系
"""
定义联软API集成的异常类层级。
层级:
LianruanError — 基类(所有联软异常)
├── LianruanConfigError — 配置缺失(未填写账号/密码/BaseURL)
├── LianruanAuthError — 认证失败(IP不在白名单/账号密码错误/Token过期)
├── LianruanConnectionError — 网络连接失败(超时/拒绝连接)
└── LianruanApiError — API业务错误(参数错误/数据超限/其他)
"""
class LianruanError(Exception):
"""联软异常基类"""
def __init__(self, message: str, detail: str = ""):
self.message = message
self.detail = detail
super().__init__(message)
class LianruanConfigError(LianruanError):
"""配置缺失异常。
场景:未配置联软 BaseURL / ApiAccount / ApiPassword
"""
pass
class LianruanAuthError(LianruanError):
"""认证失败异常。
场景:
- IP不在白名单(status=INVALID
- 账号密码错误
- Token过期(需重新获取)
"""
pass
class LianruanConnectionError(LianruanError):
"""网络连接失败异常。
场景:超时/拒绝连接/DNS解析失败
"""
pass
class LianruanApiError(LianruanError):
"""API业务错误异常。
场景:
- 参数错误(status=ERROR
- 数据量超限(status=Exceed
- 其他业务异常
"""
def __init__(self, message: str, status: str = "", detail: str = ""):
self.status = status # 联软返回的status字段(ERROR/Exceed等)
super().__init__(message, detail)
+193
View File
@@ -0,0 +1,193 @@
# 联软LV7000数据模型
"""
定义联软API返回数据的Pydantic模型。
核心模型:
- TerminalBasicInfo:终端基本信息(queryDevByParams返回)
- TerminalAllInfo:终端详细信息(getDevAllInfo返回,极详细)
- UserInfo:用户信息(getUserInfoByAccount返回)
- OrgInfo:组织架构信息(getAllOrgInfo返回)
- OnlineStatus:终端在线状态(existOnlineUser返回)
"""
from typing import Optional
from pydantic import BaseModel, Field
# ==========================================================================
# 终端基本信息(queryDevByParams返回)
# ==========================================================================
class TerminalBasicInfo(BaseModel):
"""终端基本信息 — 最核心的映射数据源。
⭐ strusername + struserdes 字段直接提供员工账号→终端映射!
这是联软相比火绒最大的优势。
"""
# 终端标识
strdevname: str = Field(default="", description="计算机名")
strdevip: str = Field(default="", description="IP地址")
strmac: str = Field(default="", description="MAC地址")
# ⭐ 员工映射字段(核心价值)
strusername: str = Field(default="", description="使用该终端的用户账号(映射金钥匙)")
struserdes: str = Field(default="", description="用户姓名/描述")
# 组织信息
strdeptname: str = Field(default="", description="所属部门名")
# 状态
istatus: str = Field(default="", description="终端状态(1=在线/0=离线)")
# 网络
strswitchname: str = Field(default="", description="接入交换机名")
strifname: str = Field(default="", description="交换机接口名")
# 联系方式
strmail: str = Field(default="", description="用户邮箱")
strphone: str = Field(default="", description="用户电话")
# 其他
strdomain: str = Field(default="", description="Windows域")
strdevtype: str = Field(default="", description="设备类型")
# ==========================================================================
# 终端详细信息(getDevAllInfo返回)
# ==========================================================================
class HardwareInfo(BaseModel):
"""硬件组件信息"""
name: str = Field(default="", description="名称")
model: str = Field(default="", description="型号")
vendor: str = Field(default="", description="厂商")
capacity: str = Field(default="", description="容量")
serial: str = Field(default="", description="序列号")
class LogicalDiskInfo(BaseModel):
"""逻辑磁盘信息(含使用率,判断磁盘满)"""
name: str = Field(default="", description="卷标")
file_system: str = Field(default="", description="文件系统")
total_size: str = Field(default="", description="总量")
free_space: str = Field(default="", description="可用空间")
usage_percent: str = Field(default="", description="使用率")
class NetworkCardInfo(BaseModel):
"""网卡信息"""
name: str = Field(default="", description="名称")
is_wireless: str = Field(default="", description="是否无线")
vendor: str = Field(default="", description="厂商")
mac: str = Field(default="", description="MAC地址")
class DisplayInfo(BaseModel):
"""显示器信息(多屏配置排查)"""
vendor: str = Field(default="", description="厂商")
model: str = Field(default="", description="型号")
serial: str = Field(default="", description="序列号")
size: str = Field(default="", description="尺寸")
class TerminalAllInfo(BaseModel):
"""终端详细信息 — 极其详细,比火绒_info2更丰富。
包含:设备基础+硬件+软件+资产+网络配置。
特别是逻辑磁盘使用率和显示器信息,是火绒没有的。
"""
# 设备基础
strdevname: str = Field(default="", description="计算机名")
strip1: str = Field(default="", description="IP地址")
strmac: str = Field(default="", description="MAC地址")
strnatip: str = Field(default="", description="NAT IP")
macverdor: str = Field(default="", description="MAC厂商")
strdevtype: str = Field(default="", description="设备类型")
# 组织+用户
strdeptname: str = Field(default="", description="所属部门")
strusername: str = Field(default="", description="用户账号⭐")
struserdes: str = Field(default="", description="用户姓名⭐")
# 时间
dtdevuptime: str = Field(default="", description="最近上线时间")
dtdevdowntime: str = Field(default="", description="最近下线时间")
dtdevfirstfoundtime: str = Field(default="", description="首次发现时间")
# 系统
stros: str = Field(default="", description="操作系统")
strdomain: str = Field(default="", description="Windows域")
strserialnumber: str = Field(default="", description="序列号")
strmainboardtype: str = Field(default="", description="主板型号")
# 客户端详情
strverofuaagent: str = Field(default="", description="安全助手版本")
istatus: str = Field(default="", description="在线状态")
devassetno: str = Field(default="", description="设备资产号")
devgroup: str = Field(default="", description="设备所属设备组")
# 硬件详情(列表)
mainboard: list[HardwareInfo] = Field(default_factory=list, description="主板信息")
cpu: list[HardwareInfo] = Field(default_factory=list, description="CPU信息")
memory: list[HardwareInfo] = Field(default_factory=list, description="内存信息")
hard_disk: list[HardwareInfo] = Field(default_factory=list, description="硬盘信息")
logical_disk: list[LogicalDiskInfo] = Field(default_factory=list, description="逻辑磁盘")
graphics_card: list[HardwareInfo] = Field(default_factory=list, description="显卡信息")
network_card: list[NetworkCardInfo] = Field(default_factory=list, description="网卡信息")
display: list[DisplayInfo] = Field(default_factory=list, description="显示器信息")
# ==========================================================================
# 用户信息(getUserInfoByAccount返回)
# ==========================================================================
class UserInfo(BaseModel):
"""用户信息"""
deptid: str = Field(default="", description="部门ID")
userid: str = Field(default="", description="用户ID")
useraccount: str = Field(default="", description="用户账号")
username: str = Field(default="", description="用户姓名")
# ==========================================================================
# 组织架构信息(getAllOrgInfo返回)
# ==========================================================================
class OrgDeptInfo(BaseModel):
"""部门信息"""
deptid: str = Field(default="", description="部门ID")
deptname: str = Field(default="", description="部门名称")
parentid: str = Field(default="", description="父部门ID")
users: list[UserInfo] = Field(default_factory=list, description="部门下用户列表")
# ==========================================================================
# 终端在线状态(existOnlineUser返回)
# ==========================================================================
class OnlineStatus(BaseModel):
"""终端在线状态"""
username: str = Field(default="", description="用户名")
ip: str = Field(default="", description="IP地址")
is_online: bool = Field(default=False, description="是否在线")
# ==========================================================================
# 软件信息(querysoftwarebydev返回)
# ==========================================================================
class SoftwareInfo(BaseModel):
"""软件安装信息"""
name: str = Field(default="", description="软件名称")
version: str = Field(default="", description="版本")
vendor: str = Field(default="", description="厂商")
install_date: str = Field(default="", description="安装日期")
class TerminalSoftwareInfo(BaseModel):
"""终端安装软件信息"""
strdevname: str = Field(default="", description="计算机名")
strdevip: str = Field(default="", description="IP地址")
strmac: str = Field(default="", description="MAC地址")
strusername: str = Field(default="", description="用户账号")
softwares: list[SoftwareInfo] = Field(default_factory=list, description="软件列表")