Files
happy-life-star/backend-single/deploy.py
T
peanut 06b2e16813 重构:统一 Python 部署脚本并修复编码问题
- 新增 deploy.py 统一部署脚本(调用各子目录 .py 脚本)
- 保留 deploy.sh 统一部署脚本(调用各子目录 .sh 脚本)
- 删除旧的 deploy-all.sh / deploy-domain.sh / deploy-to-prod.sh
- 修复 Windows GBK 编码导致的 UnicodeDecodeError/UnicodeEncodeError
- 修复 nginx 远程目录自动创建
- 移除 backend-single/deploy.py 和 web/deploy.py 中的 emoji 字符

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 18:09:34 +08:00

249 lines
7.2 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
情绪博物馆后端服务部署脚本
部署到远程服务器 101.200.208.45
使用系统自带的ssh/scp命令,无需额外依赖
"""
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')
# 配置变量
APP_NAME = "emotion-museum-single"
JAR_NAME = "backend-single-1.0.0.jar"
REMOTE_JAR_NAME = "emotion-single-1.0.0.jar"
SPRING_PROFILE = "test"
# 本地路径
SCRIPT_DIR = Path(__file__).parent.absolute()
JAR_PATH = SCRIPT_DIR / "target" / JAR_NAME
# 远程服务器配置
REMOTE_HOST = "101.200.208.45"
REMOTE_USER = "root"
REMOTE_DIR = "/data/programs/emotion-museum"
REMOTE_LOG_DIR = "/data/logs/emotion-museum"
class Colors:
"""终端颜色"""
GREEN = '\033[32m'
RED = '\033[31m'
YELLOW = '\033[33m'
RESET = '\033[0m'
def log_info(msg):
"""打印信息日志"""
print(f"{Colors.GREEN}[INFO]{Colors.RESET} {msg}")
def log_error(msg):
"""打印错误日志"""
print(f"{Colors.RED}[ERROR]{Colors.RESET} {msg}")
def log_warn(msg):
"""打印警告日志"""
print(f"{Colors.YELLOW}[WARN]{Colors.RESET} {msg}")
def run_command(cmd, cwd=None, shell=True, capture=True):
"""执行本地命令"""
try:
if capture:
result = subprocess.run(cmd, cwd=cwd, shell=shell, capture_output=True, text=True, encoding='utf-8')
return result.returncode == 0, result.stdout, result.stderr
else:
result = subprocess.run(cmd, cwd=cwd, shell=shell)
return result.returncode == 0, "", ""
except Exception as e:
return False, "", str(e)
def exec_ssh_cmd(cmd):
"""通过SSH执行远程命令"""
ssh_cmd = f'ssh {REMOTE_USER}@{REMOTE_HOST} "{cmd}"'
return run_command(ssh_cmd)
def scp_upload(local_path, remote_path):
"""通过SCP上传文件"""
scp_cmd = f'scp "{local_path}" {REMOTE_USER}@{REMOTE_HOST}:{remote_path}'
success, stdout, stderr = run_command(scp_cmd)
if not success:
log_error(f"SCP上传失败: {stderr}")
return success
def build_project():
"""构建项目"""
log_info("开始构建项目...")
success, _, _ = run_command("mvn --version")
if not success:
log_error("未找到Maven命令,请确保已安装Maven")
sys.exit(1)
log_info("执行: mvn clean package -DskipTests")
os.chdir(SCRIPT_DIR)
success, _, _ = run_command("mvn clean package -DskipTests", capture=False)
if not success:
log_error("项目构建失败")
sys.exit(1)
if not JAR_PATH.exists():
log_error(f"项目构建失败,未找到JAR文件: {JAR_PATH}")
sys.exit(1)
file_size = JAR_PATH.stat().st_size / (1024 * 1024)
log_info(f"[OK] 项目构建成功: {JAR_PATH}")
log_info(f"文件大小: {file_size:.2f} MB")
def check_jar():
"""检查JAR文件是否存在"""
if not JAR_PATH.exists():
log_error(f"JAR文件不存在: {JAR_PATH}")
log_info("请先执行打包命令: mvn clean package")
sys.exit(1)
log_info(f"JAR文件检查通过: {JAR_PATH}")
def deploy(upload_script=None):
"""
部署到远程服务器
Args:
upload_script: 可选,指定要上传的额外文件路径(如 deploy-server.sh
"""
log_info(f"开始部署到 {REMOTE_HOST}...")
build_project()
check_jar()
# 创建远程目录
log_info("创建远程目录...")
exec_ssh_cmd(f"mkdir -p {REMOTE_DIR}")
exec_ssh_cmd(f"mkdir -p {REMOTE_LOG_DIR}")
# 上传JAR文件
log_info("上传JAR文件到远程服务器...")
log_info(f"本地文件: {JAR_PATH}")
log_info(f"远程路径: {REMOTE_USER}@{REMOTE_HOST}:{REMOTE_DIR}/{REMOTE_JAR_NAME}")
if not scp_upload(JAR_PATH, f"{REMOTE_DIR}/{REMOTE_JAR_NAME}"):
log_error("上传JAR文件失败")
sys.exit(1)
log_info("[OK] JAR文件上传成功")
# 验证远程文件
log_info("验证远程文件...")
success, output, _ = exec_ssh_cmd(f"ls -lh {REMOTE_DIR}/{REMOTE_JAR_NAME}")
if not success:
log_error("远程文件验证失败")
sys.exit(1)
log_info(output)
# 上传额外文件
if upload_script:
script_path = SCRIPT_DIR / upload_script
if script_path.exists():
log_info(f"上传文件到远程服务器: {upload_script}")
if not scp_upload(script_path, f"{REMOTE_DIR}/{upload_script}"):
log_error(f"上传文件失败: {upload_script}")
sys.exit(1)
log_info(f"[OK] 文件上传成功: {upload_script}")
if upload_script.endswith('.sh'):
exec_ssh_cmd(f"chmod +x {REMOTE_DIR}/{upload_script}")
else:
log_error(f"指定的文件不存在: {script_path}")
sys.exit(1)
# 执行远程部署
log_info("在远程服务器上执行部署...")
success, output, error = exec_ssh_cmd(f"cd {REMOTE_DIR} && ./deploy-server.sh {SPRING_PROFILE}")
if output:
print(output)
if error:
print(error)
if not success:
log_error("远程部署脚本执行失败")
sys.exit(1)
log_info("[OK] 部署完成!")
show_status()
def show_status():
"""显示远程服务状态"""
log_info("=== 服务信息 ===")
log_info(f"服务器地址: {REMOTE_HOST}")
log_info(f"部署目录: {REMOTE_DIR}")
log_info(f"日志目录: {REMOTE_LOG_DIR}")
log_info(f"Spring Profile: {SPRING_PROFILE}")
log_info("检查服务状态...")
success, output, _ = exec_ssh_cmd(f"ps aux | grep {REMOTE_JAR_NAME} | grep -v grep")
if output:
log_info(f"服务运行中:\n{output}")
else:
log_info("服务未运行")
def print_usage():
"""打印使用说明"""
print("""
用法: python deploy.py [命令] [参数]
命令:
deploy - 部署到远程服务器(默认)
deploy [文件名] - 部署并上传指定文件(如 deploy-server.sh
build - 仅构建项目
status - 查看远程服务状态
示例:
python deploy.py # 部署到远程服务器
python deploy.py deploy-server.sh # 同时上传部署脚本
python deploy.py build # 仅构建项目
python deploy.py status # 查看服务状态
""")
def main():
"""主函数"""
if len(sys.argv) < 2:
deploy()
return
command = sys.argv[1]
if command == "build":
build_project()
elif command == "status":
show_status()
elif command == "help" or command == "-h" or command == "--help":
print_usage()
elif command.endswith('.sh'):
# 如果第一个参数是文件名,则上传该文件
deploy(command)
else:
# 其他情况视为部署命令
upload_script = sys.argv[2] if len(sys.argv) > 2 else None
deploy(upload_script)
if __name__ == "__main__":
main()