605 lines
20 KiB
Python
605 lines
20 KiB
Python
|
|
# 联软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)}"}
|