Files
happy-life-star/backend-single/deploy.py
T

414 lines
12 KiB
Python
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 -*-
"""
情绪博物馆后端服务部署脚本
支持本地部署和远程部署到服务器
使用系统自带的ssh/scp命令,无需额外依赖
"""
import os
import sys
import subprocess
import time
from pathlib import Path
# 配置变量
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
LOG_DIR = SCRIPT_DIR / "logs"
PID_FILE = Path(f"/tmp/{APP_NAME}.pid")
# Java配置
JAVA_OPTS = "-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError"
# 远程服务器配置
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
)
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("开始构建项目...")
# 检查Maven
success, _, _ = run_command("mvn --version")
if not success:
log_error("未找到Maven命令,请确保已安装Maven")
sys.exit(1)
# 执行Maven构建
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)
# 检查JAR文件
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"✅ 项目构建成功: {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_to_remote(upload_script=None):
"""
远程部署到服务器
Args:
upload_script: 可选,指定要上传的额外文件路径(如 deploy-server.sh
"""
log_info(f"开始远程部署到 {REMOTE_HOST}...")
# 构建项目
build_project()
# 检查JAR文件
check_jar()
# 1. 创建远程目录
log_info("创建远程目录...")
exec_ssh_cmd(f"mkdir -p {REMOTE_DIR}")
exec_ssh_cmd(f"mkdir -p {REMOTE_LOG_DIR}")
# 2. 上传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("✅ JAR文件上传成功")
# 3. 验证远程文件
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)
# 4. 如果指定了额外文件,则上传
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"✅ 文件上传成功: {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)
else:
log_info("跳过部署脚本上传(服务器已存在)")
# 5. 在远程服务器上执行部署
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("✅ 远程部署完成!")
show_remote_status()
def show_remote_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 local_deploy():
"""本地部署"""
log_info(f"开始本地部署 {APP_NAME} 服务...")
# 构建项目
build_project()
# 检查JAR文件
check_jar()
# 创建日志目录
LOG_DIR.mkdir(parents=True, exist_ok=True)
# 停止旧服务
stop_local_service()
# 启动新服务
start_local_service()
# 等待启动
if wait_for_startup():
log_info("✅ 本地部署成功!")
show_local_status()
else:
log_error("本地部署失败!")
sys.exit(1)
def stop_local_service():
"""停止本地服务"""
if PID_FILE.exists():
pid = PID_FILE.read_text().strip()
success, _, _ = run_command(f"ps -p {pid}")
if success:
log_info(f"停止旧服务 (PID: {pid})")
run_command(f"kill {pid}")
# 等待服务停止
for _ in range(30):
success, _, _ = run_command(f"ps -p {pid}")
if not success:
log_info("服务已停止")
break
time.sleep(1)
else:
log_warn(f"强制停止服务 (PID: {pid})")
run_command(f"kill -9 {pid}")
else:
log_warn("PID文件存在但进程不存在,清理PID文件")
PID_FILE.unlink()
else:
log_info("没有找到PID文件,服务可能未运行")
def start_local_service():
"""启动本地服务"""
log_info("启动本地服务...")
startup_log = LOG_DIR / "startup.log"
app_log = LOG_DIR / "application.log"
cmd = (
f"nohup java {JAVA_OPTS} "
f"-Dspring.profiles.active={SPRING_PROFILE} "
f"-Dlogging.file.path={LOG_DIR} "
f"-Dlogging.file.name={app_log} "
f"-jar {JAR_PATH} "
f"> {startup_log} 2>&1 &"
)
# 使用subprocess启动后台进程
subprocess.Popen(
cmd,
shell=True,
cwd=SCRIPT_DIR,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
# 获取实际的Java进程PID
time.sleep(2)
success, output, _ = run_command(f"pgrep -f {JAR_NAME}")
if success and output:
pid = output.strip().split('\n')[0]
PID_FILE.write_text(pid)
log_info(f"服务启动中,PID: {pid}")
else:
log_warn("无法获取服务PID")
log_info(f"启动日志: {startup_log}")
log_info(f"应用日志: {app_log}")
def wait_for_startup():
"""等待服务启动"""
log_info("等待服务启动...")
for _ in range(60):
# 检查端口是否监听
success, output, _ = run_command("netstat -tlnp 2>/dev/null | grep ':19089.*LISTEN'")
if success and output:
log_info("服务启动成功!")
return True
time.sleep(2)
log_error(f"服务启动超时,请检查日志: {LOG_DIR}/startup.log")
return False
def show_local_status():
"""显示本地服务状态"""
log_info("=== 本地服务信息 ===")
log_info(f"应用名称: {APP_NAME}")
log_info(f"JAR文件: {JAR_PATH}")
log_info(f"日志目录: {LOG_DIR}")
log_info(f"PID文件: {PID_FILE}")
log_info(f"Java参数: {JAVA_OPTS}")
log_info(f"Spring Profile: {SPRING_PROFILE}")
if PID_FILE.exists():
pid = PID_FILE.read_text().strip()
success, _, _ = run_command(f"ps -p {pid}")
if success:
log_info(f"服务状态: 运行中 (PID: {pid})")
else:
log_info("服务状态: 未运行")
else:
log_info("服务状态: 未运行")
def print_usage():
"""打印使用说明"""
print("""
用法: python deploy.py [命令] [参数]
命令:
deploy - 本地部署服务(默认)
remote - 远程部署到服务器(仅上传JAR)
remote [文件名] - 远程部署并上传指定文件(如 deploy-server.sh
build - 构建项目
start - 启动本地服务
stop - 停止本地服务
restart - 重启本地服务
status - 查看本地服务状态
remote-status - 查看远程服务状态
示例:
python deploy.py remote # 仅上传JAR并部署
python deploy.py remote deploy-server.sh # 同时上传部署脚本
""")
def main():
"""主函数"""
command = sys.argv[1] if len(sys.argv) > 1 else "deploy"
if command == "deploy":
local_deploy()
elif command == "remote":
# 检查是否有额外的文件参数
upload_script = sys.argv[2] if len(sys.argv) > 2 else None
deploy_to_remote(upload_script)
elif command == "build":
build_project()
elif command == "start":
build_project()
check_jar()
LOG_DIR.mkdir(parents=True, exist_ok=True)
start_local_service()
wait_for_startup()
elif command == "stop":
stop_local_service()
elif command == "restart":
stop_local_service()
time.sleep(2)
build_project()
check_jar()
LOG_DIR.mkdir(parents=True, exist_ok=True)
start_local_service()
wait_for_startup()
elif command == "status":
show_local_status()
elif command == "remote-status":
show_remote_status()
else:
print_usage()
sys.exit(1)
if __name__ == "__main__":
main()