211 lines
6.6 KiB
Python
211 lines
6.6 KiB
Python
"""
|
||
Linux 系统适配模块
|
||
处理 Linux 特有的路径、权限、环境变量等问题
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import logging
|
||
from pathlib import Path
|
||
from typing import Optional
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class LinuxAdapter:
|
||
"""Linux 系统适配器"""
|
||
|
||
@staticmethod
|
||
def is_linux() -> bool:
|
||
"""检查是否运行在 Linux 系统上"""
|
||
return sys.platform.startswith('linux')
|
||
|
||
@staticmethod
|
||
def normalize_path(path: str) -> Path:
|
||
"""
|
||
规范化路径 - 适配不同操作系统
|
||
|
||
Args:
|
||
path: 路径字符串(可能混合了不同分隔符)
|
||
|
||
Returns:
|
||
规范化后的 Path 对象
|
||
"""
|
||
# 替换反斜杠为正斜杠
|
||
path = path.replace('\\', '/')
|
||
# 创建 Path 对象,会根据系统自动转换
|
||
return Path(path).resolve()
|
||
|
||
@staticmethod
|
||
def ensure_directory_writable(dir_path: Path) -> bool:
|
||
"""
|
||
确保目录可写
|
||
|
||
Args:
|
||
dir_path: 目录路径
|
||
|
||
Returns:
|
||
是否成功
|
||
"""
|
||
try:
|
||
dir_path = Path(dir_path)
|
||
dir_path.mkdir(parents=True, exist_ok=True)
|
||
|
||
# 检查写入权限
|
||
test_file = dir_path / '.test_write'
|
||
test_file.touch()
|
||
test_file.unlink()
|
||
|
||
logger.info(f"✓ 目录可写: {dir_path}")
|
||
return True
|
||
except PermissionError:
|
||
logger.error(f"✗ 没有写入权限: {dir_path}")
|
||
logger.error(f" 建议: sudo chmod 755 {dir_path}")
|
||
return False
|
||
except Exception as e:
|
||
logger.error(f"✗ 目录检查失败: {dir_path} - {e}")
|
||
return False
|
||
|
||
@staticmethod
|
||
def get_recommended_upload_dir() -> Path:
|
||
"""
|
||
获取 Linux 上推荐的上传目录
|
||
|
||
Returns:
|
||
推荐的上传目录路径
|
||
"""
|
||
# 优先级:
|
||
# 1. 环境变量指定的目录
|
||
# 2. 项目相对路径
|
||
# 3. /tmp (临时目录)
|
||
|
||
if upload_dir := os.getenv('UPLOAD_DIR'):
|
||
return Path(upload_dir)
|
||
|
||
project_upload = Path(__file__).parent.parent.parent / 'uploads'
|
||
if project_upload.exists() and os.access(project_upload, os.W_OK):
|
||
return project_upload
|
||
|
||
logger.warning("使用系统临时目录进行上传存储")
|
||
return Path('/tmp/lazy_fjh_uploads')
|
||
|
||
@staticmethod
|
||
def setup_signal_handlers():
|
||
"""
|
||
设置 Linux 信号处理器
|
||
确保优雅关闭
|
||
"""
|
||
import signal
|
||
|
||
def signal_handler(sig, frame):
|
||
logger.info(f"收到信号 {sig},开始优雅关闭...")
|
||
sys.exit(0)
|
||
|
||
if LinuxAdapter.is_linux():
|
||
signal.signal(signal.SIGTERM, signal_handler)
|
||
signal.signal(signal.SIGINT, signal_handler)
|
||
logger.info("✓ Linux 信号处理器已注册")
|
||
|
||
@staticmethod
|
||
def get_process_info() -> dict:
|
||
"""
|
||
获取当前进程信息
|
||
|
||
Returns:
|
||
进程信息字典
|
||
"""
|
||
import psutil
|
||
|
||
process = psutil.Process(os.getpid())
|
||
|
||
return {
|
||
'pid': os.getpid(),
|
||
'user': os.getlogin() if LinuxAdapter.is_linux() else 'unknown',
|
||
'memory_mb': process.memory_info().rss / 1024 / 1024,
|
||
'cpu_percent': process.cpu_percent(interval=1),
|
||
'num_threads': process.num_threads()
|
||
}
|
||
|
||
@staticmethod
|
||
def check_system_resources() -> dict:
|
||
"""
|
||
检查系统资源
|
||
|
||
Returns:
|
||
系统资源信息
|
||
"""
|
||
import psutil
|
||
|
||
return {
|
||
'cpu_count': psutil.cpu_count(),
|
||
'total_memory_gb': psutil.virtual_memory().total / (1024**3),
|
||
'available_memory_gb': psutil.virtual_memory().available / (1024**3),
|
||
'disk_usage_percent': psutil.disk_usage('/').percent
|
||
}
|
||
|
||
@staticmethod
|
||
def optimize_for_linux():
|
||
"""
|
||
针对 Linux 系统进行优化
|
||
"""
|
||
if not LinuxAdapter.is_linux():
|
||
return
|
||
|
||
logger.info("应用 Linux 系统优化...")
|
||
|
||
# 1. 增加文件描述符限制
|
||
try:
|
||
import resource
|
||
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
||
resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))
|
||
logger.info(f"✓ 设置文件描述符限制: {hard}")
|
||
except Exception as e:
|
||
logger.warning(f"⚠ 无法设置文件描述符: {e}")
|
||
|
||
# 2. 可选内存限制(默认跳过,避免在 WSL/容器中因 RLIMIT_AS 过低触发 MemoryError)
|
||
mem_limit_env = os.getenv('LINUX_RLIMIT_AS_MB') or os.getenv('LINUX_MEMORY_LIMIT_MB')
|
||
if mem_limit_env:
|
||
try:
|
||
import resource
|
||
limit_mb = int(mem_limit_env)
|
||
limit_bytes = limit_mb * 1024**2
|
||
resource.setrlimit(resource.RLIMIT_AS, (limit_bytes, limit_bytes))
|
||
logger.info(f"✓ 设置虚拟内存限制: {limit_mb}MB")
|
||
except Exception as e:
|
||
logger.warning(f"⚠ 无法设置虚拟内存限制: {e}")
|
||
else:
|
||
logger.info("跳过虚拟内存限制 (未设置 LINUX_RLIMIT_AS_MB / LINUX_MEMORY_LIMIT_MB)")
|
||
|
||
# 3. 注册信号处理
|
||
LinuxAdapter.setup_signal_handlers()
|
||
|
||
logger.info("Linux 系统优化完成")
|
||
|
||
|
||
def init_linux_environment():
|
||
"""
|
||
初始化 Linux 环境
|
||
在应用启动时调用
|
||
"""
|
||
if not LinuxAdapter.is_linux():
|
||
logger.info("非 Linux 系统,跳过 Linux 特定初始化")
|
||
return
|
||
|
||
logger.info("=" * 60)
|
||
logger.info("初始化 Linux 环境...")
|
||
logger.info("=" * 60)
|
||
|
||
# 应用优化
|
||
LinuxAdapter.optimize_for_linux()
|
||
|
||
# 检查系统资源
|
||
resources = LinuxAdapter.check_system_resources()
|
||
logger.info(f"系统资源: {resources}")
|
||
|
||
# 检查上传目录
|
||
upload_dir = LinuxAdapter.get_recommended_upload_dir()
|
||
if not LinuxAdapter.ensure_directory_writable(upload_dir):
|
||
logger.warning(f"上传目录 {upload_dir} 可能不可写")
|
||
|
||
logger.info("Linux 环境初始化完成")
|