#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 前端应用部署脚本 - 将构建好的文件上传到服务器 使用方法: python deploy.py """ import io import os import sys import subprocess from pathlib import Path # 强制 stdout/stderr 使用 UTF-8 编码,避免 Windows GBK 编码错误 if hasattr(sys.stdout, 'buffer'): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') # 服务器配置 SERVER_IP = "101.200.208.45" USERNAME = "root" REMOTE_PATH = "/data/www/emotion-museum" # 本地路径 SCRIPT_DIR = Path(__file__).parent.absolute() DIST_DIR = SCRIPT_DIR / "dist" class Colors: """终端颜色""" GREEN = '\033[32m' RED = '\033[31m' RESET = '\033[0m' def log_info(msg): """打印信息日志""" print(f"{Colors.GREEN}[OK]{Colors.RESET} {msg}") def log_error(msg): """打印错误日志""" print(f"{Colors.RED}[ERR]{Colors.RESET} {msg}") def log_step(msg): """打印步骤日志""" print(f"[STEP] {msg}") def run_command(cmd, cwd=None, shell=True, capture=True, timeout=None): """执行本地命令""" try: if capture: result = subprocess.run( cmd, cwd=cwd, shell=shell, capture_output=True, text=True, timeout=timeout ) return result.returncode == 0, result.stdout, result.stderr else: result = subprocess.run(cmd, cwd=cwd, shell=shell, timeout=timeout) return result.returncode == 0, "", "" except subprocess.TimeoutExpired: return False, "", "命令执行超时" except Exception as e: return False, "", str(e) def scp_upload(local_path, remote_path, recursive=False): """通过SCP上传文件或目录""" r_flag = "-r" if recursive else "" scp_cmd = f'scp -o ConnectTimeout=10 -o BatchMode=yes {r_flag} "{local_path}" {USERNAME}@{SERVER_IP}:{remote_path}' try: result = subprocess.run( scp_cmd, shell=True, capture_output=True, text=True, timeout=120 ) if result.returncode != 0: log_error(f"SCP上传失败: {result.stderr}") return False return True except subprocess.TimeoutExpired: log_error("SCP上传超时") return False except Exception as e: log_error(f"SCP上传异常: {e}") return False def check_npm(): """检查npm是否安装""" success, _, _ = run_command("npm --version") if not success: log_error("错误: 未找到npm命令,请先安装Node.js") sys.exit(1) def build_project(): """构建项目""" log_step("开始构建前端项目...") os.chdir(SCRIPT_DIR) success, _, _ = run_command("npm run build", capture=False) if not success: log_error("前端项目构建失败,请检查代码") sys.exit(1) log_info("前端项目构建成功") def verify_dist(): """验证dist目录是否存在""" if not DIST_DIR.exists(): log_error("错误: 构建后dist目录仍不存在,请检查构建配置") sys.exit(1) index_file = DIST_DIR / "index.html" assets_dir = DIST_DIR / "assets" if not index_file.exists(): log_error("错误: dist/index.html 不存在") sys.exit(1) if not assets_dir.exists(): log_error("错误: dist/assets 目录不存在") sys.exit(1) def upload_files(): """上传文件到服务器""" print(f"正在上传文件到服务器 {SERVER_IP}...") # 上传 index.html index_file = DIST_DIR / "index.html" if not scp_upload(index_file, f"{REMOTE_PATH}/"): return False # 上传 assets 目录 assets_dir = DIST_DIR / "assets" if not scp_upload(assets_dir, f"{REMOTE_PATH}/", recursive=True): return False # 上传测试文件(如果存在) test_file = DIST_DIR / "test-login-redirect.html" if test_file.exists(): # 上传所有 test-*.html 文件 for f in DIST_DIR.glob("test-*.html"): scp_upload(f, f"{REMOTE_PATH}/") return True def deploy(): """执行部署""" print("开始部署前端应用到服务器...") # 检查npm check_npm() # 构建项目 build_project() # 验证构建结果 verify_dist() # 上传文件 if upload_files(): log_info("部署完成!") print(f"访问地址: http://{SERVER_IP}/emotion-museum/") else: log_error("部署失败,请检查:") print("1. 服务器IP地址是否正确") print("2. SSH密钥是否配置正确") print("3. 服务器目录权限是否正确") sys.exit(1) def main(): """主函数""" deploy() if __name__ == "__main__": main()