""" 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 环境初始化完成")