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

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,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,
}