初始提交
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from .service import OPERATIONS_RUNTIME, OperationsRuntimeService
|
||||
|
||||
__all__ = ['OPERATIONS_RUNTIME', 'OperationsRuntimeService']
|
||||
@@ -0,0 +1,65 @@
|
||||
from importlib import import_module
|
||||
from typing import Any
|
||||
|
||||
|
||||
class OperationsInfrastructureGateway:
|
||||
"""
|
||||
运维基础设施网关。
|
||||
|
||||
该对象负责延迟加载 Redis、调度器、服务器监控与系统工具依赖,
|
||||
供运维运行时 facade 和其协作对象统一复用。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_redis_util() -> Any:
|
||||
"""
|
||||
获取 Redis 工具类。
|
||||
|
||||
:return: Redis 工具类
|
||||
"""
|
||||
return import_module('config.get_redis').RedisUtil
|
||||
|
||||
@staticmethod
|
||||
def get_redis_error_class() -> type[Exception]:
|
||||
"""
|
||||
获取 Redis 异常类型。
|
||||
|
||||
:return: Redis 异常类型
|
||||
"""
|
||||
return import_module('redis.exceptions').RedisError
|
||||
|
||||
@staticmethod
|
||||
def get_scheduler_util() -> Any:
|
||||
"""
|
||||
获取调度器工具类。
|
||||
|
||||
:return: 调度器工具类
|
||||
"""
|
||||
return import_module('config.get_scheduler').SchedulerUtil
|
||||
|
||||
@staticmethod
|
||||
def get_server_service() -> Any:
|
||||
"""
|
||||
获取服务器监控服务类。
|
||||
|
||||
:return: 服务器监控服务类
|
||||
"""
|
||||
return import_module('module_admin.service.server_service').ServerService
|
||||
|
||||
@staticmethod
|
||||
def get_psutil_module() -> Any:
|
||||
"""
|
||||
获取 `psutil` 模块。
|
||||
|
||||
:return: `psutil` 模块
|
||||
"""
|
||||
return import_module('psutil')
|
||||
|
||||
@staticmethod
|
||||
def get_bytes2human() -> Any:
|
||||
"""
|
||||
获取字节数转可读文本函数。
|
||||
|
||||
:return: `bytes2human` 函数
|
||||
"""
|
||||
return import_module('utils.common_util').bytes2human
|
||||
@@ -0,0 +1,132 @@
|
||||
import socket
|
||||
from typing import Any
|
||||
|
||||
from cli.exit_codes import REDIS_ERROR, RUNTIME_ERROR, SCHEDULER_ERROR
|
||||
|
||||
from .gateway import OperationsInfrastructureGateway
|
||||
from .support import (
|
||||
OperationsDependencyInspector,
|
||||
OperationsServerInfoSupport,
|
||||
)
|
||||
|
||||
|
||||
class OperationsRuntimeService:
|
||||
"""
|
||||
运维运行时服务。
|
||||
|
||||
该服务作为运维运行时 facade,对外统一暴露依赖版本检查、Redis 探活、
|
||||
调度同步以及服务器运行时信息采集入口。
|
||||
|
||||
:param infrastructure_gateway: 运维基础设施网关
|
||||
:param dependency_inspector: 运维依赖检查器
|
||||
:param server_info_support: 服务器信息支持对象
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
infrastructure_gateway: OperationsInfrastructureGateway | None = None,
|
||||
dependency_inspector: OperationsDependencyInspector | None = None,
|
||||
server_info_support: OperationsServerInfoSupport | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
初始化运维运行时服务。
|
||||
|
||||
:param infrastructure_gateway: 运维基础设施网关
|
||||
:param dependency_inspector: 运维依赖检查器
|
||||
:param server_info_support: 服务器信息支持对象
|
||||
:return: None
|
||||
"""
|
||||
self.infrastructure_gateway = infrastructure_gateway or OperationsInfrastructureGateway()
|
||||
self.dependency_inspector = dependency_inspector or OperationsDependencyInspector()
|
||||
self.server_info_support = server_info_support or OperationsServerInfoSupport(self.infrastructure_gateway)
|
||||
|
||||
def get_dependency_versions(self, *, include_dev: bool = False) -> dict[str, Any]:
|
||||
"""
|
||||
读取 CLI 和后端运行所依赖的核心 Python 包版本。
|
||||
|
||||
:param include_dev: 是否附带开发阶段依赖
|
||||
:return: 依赖检查结果
|
||||
"""
|
||||
return self.dependency_inspector.inspect(include_dev=include_dev)
|
||||
|
||||
async def ping_redis(self) -> dict[str, Any]:
|
||||
"""
|
||||
检查 Redis 连通性。
|
||||
|
||||
:return: Redis 检查结果
|
||||
"""
|
||||
redis_util = self.infrastructure_gateway.get_redis_util()
|
||||
redis_error = self.infrastructure_gateway.get_redis_error_class()
|
||||
redis = await redis_util.create_redis_pool(log_enabled=False)
|
||||
try:
|
||||
await redis.ping()
|
||||
return {'ok': True, 'message': 'Redis连接成功'}
|
||||
except redis_error as exc:
|
||||
return {'ok': False, 'message': 'Redis连接失败', 'error': str(exc), 'exit_code': REDIS_ERROR}
|
||||
finally:
|
||||
await redis.close()
|
||||
|
||||
async def sync_jobs(self) -> dict[str, Any]:
|
||||
"""
|
||||
同步调度任务配置。
|
||||
|
||||
:return: 任务同步执行结果
|
||||
"""
|
||||
redis = None
|
||||
scheduler_util = None
|
||||
try:
|
||||
redis_util = self.infrastructure_gateway.get_redis_util()
|
||||
scheduler_util = self.infrastructure_gateway.get_scheduler_util()
|
||||
redis = await redis_util.create_redis_pool(log_enabled=False)
|
||||
await scheduler_util.init_system_scheduler(redis)
|
||||
await scheduler_util.request_scheduler_sync()
|
||||
return {
|
||||
'ok': True,
|
||||
'operation': 'sync',
|
||||
'operationLabel': '同步调度配置',
|
||||
'schedulerSyncRequested': True,
|
||||
'message': '调度配置同步请求已发送',
|
||||
'isLeader': scheduler_util._is_leader,
|
||||
}
|
||||
except Exception as exc:
|
||||
return {
|
||||
'ok': False,
|
||||
'operation': 'sync',
|
||||
'operationLabel': '同步调度配置',
|
||||
'schedulerSyncRequested': False,
|
||||
'message': '调度配置同步失败',
|
||||
'error': str(exc),
|
||||
'exit_code': SCHEDULER_ERROR,
|
||||
}
|
||||
finally:
|
||||
if scheduler_util is not None:
|
||||
try:
|
||||
await scheduler_util.close_system_scheduler()
|
||||
except Exception:
|
||||
pass
|
||||
if redis is not None:
|
||||
try:
|
||||
await redis.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def get_server_info(self) -> dict[str, Any]:
|
||||
"""
|
||||
获取服务器运行信息。
|
||||
|
||||
:return: 服务器运行信息字典
|
||||
"""
|
||||
try:
|
||||
server_service = self.infrastructure_gateway.get_server_service()
|
||||
return {'ok': True, 'server': (await server_service.get_server_monitor_info()).model_dump(by_alias=True)}
|
||||
except socket.gaierror:
|
||||
try:
|
||||
return {'ok': True, 'server': self.server_info_support.build_server_info_fallback()}
|
||||
except Exception as exc:
|
||||
return {'ok': False, 'message': '读取服务器运行信息失败', 'error': str(exc), 'exit_code': RUNTIME_ERROR}
|
||||
except Exception as exc:
|
||||
return {'ok': False, 'message': '读取服务器运行信息失败', 'error': str(exc), 'exit_code': RUNTIME_ERROR}
|
||||
|
||||
|
||||
OPERATIONS_RUNTIME = OperationsRuntimeService()
|
||||
@@ -0,0 +1,241 @@
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from importlib import metadata
|
||||
from typing import Any
|
||||
|
||||
from cli.exit_codes import RUNTIME_ERROR
|
||||
|
||||
from .gateway import OperationsInfrastructureGateway
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OperationsDependencySpec:
|
||||
"""
|
||||
运维依赖检查项定义。
|
||||
|
||||
:param package_name: CLI 展示用包名
|
||||
:param distribution: Python 分发名称
|
||||
:param required: 是否为必需依赖
|
||||
:param version: 显式版本,存在时跳过环境读取
|
||||
"""
|
||||
|
||||
package_name: str
|
||||
distribution: str
|
||||
required: bool
|
||||
version: str | None = None
|
||||
|
||||
|
||||
class OperationsDependencyInspector:
|
||||
"""
|
||||
运维依赖检查器。
|
||||
|
||||
该对象负责核心依赖声明、版本探测与依赖检查结果构建,
|
||||
使运行时 facade 不再直接维护细碎依赖定义。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def read_package_version(distribution_name: str) -> str | None:
|
||||
"""
|
||||
读取指定 Python 包的已安装版本号。
|
||||
|
||||
:param distribution_name: Python 包分发名称
|
||||
:return: 已安装版本号,未安装时返回 `None`
|
||||
"""
|
||||
try:
|
||||
return metadata.version(distribution_name)
|
||||
except metadata.PackageNotFoundError:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def build_dependency_specs(*, include_dev: bool = False) -> list[OperationsDependencySpec]:
|
||||
"""
|
||||
构建当前依赖检查项列表。
|
||||
|
||||
:param include_dev: 是否附带开发阶段依赖
|
||||
:return: 依赖检查项列表
|
||||
"""
|
||||
dependency_specs = [
|
||||
OperationsDependencySpec('python', '', True, platform.python_version()),
|
||||
OperationsDependencySpec('fastapi', 'fastapi', True),
|
||||
OperationsDependencySpec('uvicorn', 'uvicorn', True),
|
||||
OperationsDependencySpec('sqlalchemy', 'SQLAlchemy', True),
|
||||
OperationsDependencySpec('alembic', 'alembic', True),
|
||||
OperationsDependencySpec('redis', 'redis', True),
|
||||
OperationsDependencySpec('pydantic', 'pydantic', True),
|
||||
OperationsDependencySpec('pydantic-settings', 'pydantic-settings', True),
|
||||
OperationsDependencySpec('typer', 'typer', True),
|
||||
]
|
||||
if include_dev:
|
||||
dependency_specs.extend(
|
||||
[
|
||||
OperationsDependencySpec('pytest', 'pytest', False),
|
||||
OperationsDependencySpec('ruff', 'ruff', False),
|
||||
]
|
||||
)
|
||||
return dependency_specs
|
||||
|
||||
def inspect(self, *, include_dev: bool = False) -> dict[str, Any]:
|
||||
"""
|
||||
读取 CLI 和后端运行所依赖的核心 Python 包版本。
|
||||
|
||||
:param include_dev: 是否附带开发阶段依赖
|
||||
:return: 依赖检查结果
|
||||
"""
|
||||
packages: dict[str, dict[str, Any]] = {}
|
||||
missing_required: list[str] = []
|
||||
for dependency_spec in self.build_dependency_specs(include_dev=include_dev):
|
||||
version = dependency_spec.version or self.read_package_version(dependency_spec.distribution)
|
||||
installed = bool(version)
|
||||
package_payload = {
|
||||
'installed': installed,
|
||||
'version': version or '',
|
||||
'required': dependency_spec.required,
|
||||
}
|
||||
if dependency_spec.distribution:
|
||||
package_payload['distribution'] = dependency_spec.distribution
|
||||
packages[dependency_spec.package_name] = package_payload
|
||||
if dependency_spec.required and not installed:
|
||||
missing_required.append(dependency_spec.package_name)
|
||||
|
||||
if missing_required:
|
||||
return {
|
||||
'ok': False,
|
||||
'message': '存在缺失的核心运行依赖',
|
||||
'missingRequired': missing_required,
|
||||
'includeDev': include_dev,
|
||||
'packages': packages,
|
||||
'exit_code': RUNTIME_ERROR,
|
||||
}
|
||||
|
||||
return {
|
||||
'ok': True,
|
||||
'message': '核心运行依赖已安装',
|
||||
'missingRequired': [],
|
||||
'includeDev': include_dev,
|
||||
'packages': packages,
|
||||
}
|
||||
|
||||
|
||||
class OperationsServerInfoSupport:
|
||||
"""
|
||||
运维服务器信息支持对象。
|
||||
|
||||
该对象负责 CLI 兜底服务器信息构建与 IP 解析逻辑,
|
||||
避免主运行时服务继续承载大量平台采集细节。
|
||||
|
||||
:param infrastructure_gateway: 运维基础设施网关
|
||||
"""
|
||||
|
||||
def __init__(self, infrastructure_gateway: OperationsInfrastructureGateway) -> None:
|
||||
"""
|
||||
初始化服务器信息支持对象。
|
||||
|
||||
:param infrastructure_gateway: 运维基础设施网关
|
||||
:return: None
|
||||
"""
|
||||
self.infrastructure_gateway = infrastructure_gateway
|
||||
|
||||
def resolve_server_ip(self, hostname: str) -> str:
|
||||
"""
|
||||
解析服务器 IP,优先使用主机名解析,失败时回退到网卡地址。
|
||||
|
||||
:param hostname: 当前主机名
|
||||
:return: 可用的 IPv4 地址
|
||||
"""
|
||||
psutil = self.infrastructure_gateway.get_psutil_module()
|
||||
try:
|
||||
resolved_ip = socket.gethostbyname(hostname)
|
||||
if resolved_ip:
|
||||
return resolved_ip
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
for interface_addresses in psutil.net_if_addrs().values():
|
||||
for address_info in interface_addresses:
|
||||
if (
|
||||
address_info.family == socket.AF_INET
|
||||
and address_info.address
|
||||
and not address_info.address.startswith('127.')
|
||||
):
|
||||
return address_info.address
|
||||
|
||||
return '127.0.0.1'
|
||||
|
||||
def build_server_info_fallback(self) -> dict[str, Any]:
|
||||
"""
|
||||
构建 CLI 使用的服务器运行信息兜底数据。
|
||||
|
||||
:return: 与原服务输出结构兼容的服务器运行信息字典
|
||||
"""
|
||||
psutil = self.infrastructure_gateway.get_psutil_module()
|
||||
bytes2human = self.infrastructure_gateway.get_bytes2human()
|
||||
cpu_num = psutil.cpu_count(logical=True)
|
||||
cpu_usage_percent = psutil.cpu_times_percent()
|
||||
memory_info = psutil.virtual_memory()
|
||||
|
||||
hostname = socket.gethostname()
|
||||
current_process = psutil.Process(os.getpid())
|
||||
start_time_stamp = current_process.create_time()
|
||||
current_time_stamp = time.time()
|
||||
difference = current_time_stamp - start_time_stamp
|
||||
days = int(difference // (24 * 60 * 60))
|
||||
hours = int((difference % (24 * 60 * 60)) // (60 * 60))
|
||||
minutes = int((difference % (60 * 60)) // 60)
|
||||
process_memory_info = current_process.memory_info()
|
||||
|
||||
sys_files: list[dict[str, Any]] = []
|
||||
for partition in psutil.disk_partitions():
|
||||
try:
|
||||
disk_usage = psutil.disk_usage(partition.mountpoint)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
mountpoint = partition.mountpoint.replace('\\', '')
|
||||
sys_files.append(
|
||||
{
|
||||
'dirName': partition.device,
|
||||
'sysTypeName': partition.fstype,
|
||||
'typeName': f'本地固定磁盘({mountpoint})',
|
||||
'total': bytes2human(disk_usage.total),
|
||||
'used': bytes2human(disk_usage.used),
|
||||
'free': bytes2human(disk_usage.free),
|
||||
'usage': f'{disk_usage.percent}%',
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
'cpu': {
|
||||
'cpuNum': cpu_num,
|
||||
'used': cpu_usage_percent.user,
|
||||
'sys': cpu_usage_percent.system,
|
||||
'free': cpu_usage_percent.idle,
|
||||
},
|
||||
'mem': {
|
||||
'total': bytes2human(memory_info.total),
|
||||
'used': bytes2human(memory_info.used),
|
||||
'free': bytes2human(memory_info.free),
|
||||
'usage': memory_info.percent,
|
||||
},
|
||||
'sys': {
|
||||
'computerIp': self.resolve_server_ip(hostname),
|
||||
'computerName': platform.node(),
|
||||
'osArch': platform.machine(),
|
||||
'osName': platform.platform(),
|
||||
'userDir': os.getcwd(),
|
||||
},
|
||||
'py': {
|
||||
'name': current_process.name(),
|
||||
'version': platform.python_version(),
|
||||
'startTime': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time_stamp)),
|
||||
'runTime': f'{days}天{hours}小时{minutes}分钟',
|
||||
'home': current_process.exe(),
|
||||
'total': bytes2human(memory_info.available),
|
||||
'used': bytes2human(process_memory_info.rss),
|
||||
'free': bytes2human(memory_info.available - process_memory_info.rss),
|
||||
'usage': round((process_memory_info.rss / memory_info.available) * 100, 2),
|
||||
},
|
||||
'sysFiles': sys_files,
|
||||
}
|
||||
Reference in New Issue
Block a user