调整部分前端布局和后端增删改逻辑

This commit is contained in:
焦龙言
2026-09-20 10:38:10 +08:00
parent acddc8a237
commit 3edba99d2d
227 changed files with 122871 additions and 839 deletions
@@ -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,
}