bug修复
This commit is contained in:
+41
-212
@@ -2,14 +2,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
情绪博物馆后端服务部署脚本
|
||||
支持本地部署和远程部署到服务器
|
||||
部署到远程服务器 101.200.208.45
|
||||
使用系统自带的ssh/scp命令,无需额外依赖
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# 配置变量
|
||||
@@ -21,11 +20,6 @@ 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"
|
||||
@@ -61,13 +55,7 @@ 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
|
||||
)
|
||||
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)
|
||||
@@ -95,23 +83,19 @@ 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)
|
||||
@@ -130,27 +114,24 @@ def check_jar():
|
||||
log_info(f"JAR文件检查通过: {JAR_PATH}")
|
||||
|
||||
|
||||
def deploy_to_remote(upload_script=None):
|
||||
def deploy(upload_script=None):
|
||||
"""
|
||||
远程部署到服务器
|
||||
部署到远程服务器
|
||||
|
||||
Args:
|
||||
upload_script: 可选,指定要上传的额外文件路径(如 deploy-server.sh)
|
||||
"""
|
||||
log_info(f"开始远程部署到 {REMOTE_HOST}...")
|
||||
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文件
|
||||
# 上传JAR文件
|
||||
log_info("上传JAR文件到远程服务器...")
|
||||
log_info(f"本地文件: {JAR_PATH}")
|
||||
log_info(f"远程路径: {REMOTE_USER}@{REMOTE_HOST}:{REMOTE_DIR}/{REMOTE_JAR_NAME}")
|
||||
@@ -160,7 +141,7 @@ def deploy_to_remote(upload_script=None):
|
||||
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:
|
||||
@@ -168,26 +149,22 @@ def deploy_to_remote(upload_script=None):
|
||||
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}")
|
||||
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:
|
||||
@@ -199,155 +176,24 @@ def deploy_to_remote(upload_script=None):
|
||||
log_error("远程部署脚本执行失败")
|
||||
sys.exit(1)
|
||||
|
||||
log_info("✅ 远程部署完成!")
|
||||
show_remote_status()
|
||||
log_info("✅ 部署完成!")
|
||||
show_status()
|
||||
|
||||
|
||||
def show_remote_status():
|
||||
def show_status():
|
||||
"""显示远程服务状态"""
|
||||
log_info("=== 远程服务信息 ===")
|
||||
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("检查远程服务状态...")
|
||||
log_info("检查服务状态...")
|
||||
success, output, _ = exec_ssh_cmd(f"ps aux | grep {REMOTE_JAR_NAME} | grep -v grep")
|
||||
if output:
|
||||
log_info(f"远程服务运行中:\n{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("服务状态: 未运行")
|
||||
log_info("服务未运行")
|
||||
|
||||
|
||||
def print_usage():
|
||||
@@ -356,57 +202,40 @@ def print_usage():
|
||||
用法: python deploy.py [命令] [参数]
|
||||
|
||||
命令:
|
||||
deploy - 本地部署服务(默认)
|
||||
remote - 远程部署到服务器(仅上传JAR)
|
||||
remote [文件名] - 远程部署并上传指定文件(如 deploy-server.sh)
|
||||
build - 构建项目
|
||||
start - 启动本地服务
|
||||
stop - 停止本地服务
|
||||
restart - 重启本地服务
|
||||
status - 查看本地服务状态
|
||||
remote-status - 查看远程服务状态
|
||||
deploy - 部署到远程服务器(默认)
|
||||
deploy [文件名] - 部署并上传指定文件(如 deploy-server.sh)
|
||||
build - 仅构建项目
|
||||
status - 查看远程服务状态
|
||||
|
||||
示例:
|
||||
python deploy.py remote # 仅上传JAR并部署
|
||||
python deploy.py remote deploy-server.sh # 同时上传部署脚本
|
||||
python deploy.py # 部署到远程服务器
|
||||
python deploy.py deploy-server.sh # 同时上传部署脚本
|
||||
python deploy.py build # 仅构建项目
|
||||
python deploy.py status # 查看服务状态
|
||||
""")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
command = sys.argv[1] if len(sys.argv) > 1 else "deploy"
|
||||
if len(sys.argv) < 2:
|
||||
deploy()
|
||||
return
|
||||
|
||||
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":
|
||||
command = sys.argv[1]
|
||||
|
||||
if 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:
|
||||
show_status()
|
||||
elif command == "help" or command == "-h" or command == "--help":
|
||||
print_usage()
|
||||
sys.exit(1)
|
||||
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__":
|
||||
|
||||
+63
-263
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 情绪博物馆后端服务部署脚本
|
||||
# 支持本地部署和远程部署到服务器 101.200.208.45
|
||||
# 部署到远程服务器 101.200.208.45
|
||||
|
||||
set -e
|
||||
|
||||
@@ -9,9 +9,6 @@ set -e
|
||||
APP_NAME="emotion-museum-single"
|
||||
JAR_NAME="backend-single-1.0.0.jar"
|
||||
JAR_PATH="./target/${JAR_NAME}"
|
||||
LOG_DIR="./logs/"
|
||||
PID_FILE="/tmp/${APP_NAME}.pid"
|
||||
JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOG_DIR}/heapdump.hprof"
|
||||
|
||||
# 远程服务器配置
|
||||
REMOTE_HOST="101.200.208.45"
|
||||
@@ -25,9 +22,8 @@ SPRING_PROFILE="test"
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
NC='\033[0m'
|
||||
|
||||
# 日志函数
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
@@ -40,27 +36,23 @@ log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 检查并构建项目
|
||||
# 构建项目
|
||||
build_project() {
|
||||
log_info "开始构建项目..."
|
||||
|
||||
# 检查 Maven 是否安装
|
||||
if ! command -v mvn > /dev/null 2>&1; then
|
||||
log_error "未找到Maven命令,请确保已安装Maven"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 执行 Maven 构建
|
||||
log_info "执行: mvn clean package -DskipTests"
|
||||
if ! mvn clean package -DskipTests; then
|
||||
log_error "项目构建失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 JAR 文件是否生成
|
||||
if [ ! -f "$JAR_PATH" ]; then
|
||||
log_error "项目构建失败,未找到JAR文件: $JAR_PATH"
|
||||
log_error "请检查 Maven 构建输出"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -68,313 +60,121 @@ build_project() {
|
||||
log_info "文件大小: $(ls -lh $JAR_PATH | awk '{print $5}')"
|
||||
}
|
||||
|
||||
# 检查 jar 文件是否存在
|
||||
# 检查JAR文件
|
||||
check_jar() {
|
||||
if [ ! -f "$JAR_PATH" ]; then
|
||||
log_error "JAR 文件不存在: $JAR_PATH"
|
||||
log_error "JAR文件不存在: $JAR_PATH"
|
||||
log_info "请先执行打包命令: mvn clean package"
|
||||
exit 1
|
||||
fi
|
||||
log_info "JAR 文件检查通过: $JAR_PATH"
|
||||
log_info "JAR文件检查通过: $JAR_PATH"
|
||||
}
|
||||
|
||||
# 创建日志目录
|
||||
create_log_dir() {
|
||||
if [ ! -d "$LOG_DIR" ]; then
|
||||
log_info "创建日志目录: $LOG_DIR"
|
||||
mkdir -p "$LOG_DIR"
|
||||
fi
|
||||
# 显示服务状态
|
||||
show_status() {
|
||||
log_info "=== 服务信息 ==="
|
||||
log_info "服务器地址: $REMOTE_HOST"
|
||||
log_info "部署目录: $REMOTE_DIR"
|
||||
log_info "日志目录: $REMOTE_LOG_DIR"
|
||||
log_info "Spring Profile: $SPRING_PROFILE"
|
||||
|
||||
log_info "检查服务状态..."
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "ps aux | grep $REMOTE_JAR_NAME | grep -v grep" || log_info "服务未运行"
|
||||
}
|
||||
|
||||
# 本地部署 - 停止旧服务
|
||||
stop_local_service() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
log_info "停止旧服务 (PID: $PID)"
|
||||
kill "$PID"
|
||||
|
||||
# 等待服务停止
|
||||
for i in {1..30}; do
|
||||
if ! ps -p "$PID" > /dev/null 2>&1; then
|
||||
log_info "服务已停止"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# 强制停止
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
log_warn "强制停止服务 (PID: $PID)"
|
||||
kill -9 "$PID"
|
||||
fi
|
||||
else
|
||||
log_warn "PID 文件存在但进程不存在,清理 PID 文件"
|
||||
fi
|
||||
rm -f "$PID_FILE"
|
||||
else
|
||||
log_info "没有找到 PID 文件,服务可能未运行"
|
||||
fi
|
||||
}
|
||||
# 部署到远程服务器
|
||||
# 参数: $1 - 可选,指定要上传的额外文件
|
||||
deploy() {
|
||||
UPLOAD_SCRIPT="$1"
|
||||
|
||||
log_info "开始部署到 $REMOTE_HOST..."
|
||||
|
||||
# 本地部署 - 启动新服务
|
||||
start_local_service() {
|
||||
log_info "启动本地服务..."
|
||||
|
||||
# 启动命令
|
||||
nohup java $JAVA_OPTS \
|
||||
-Dspring.profiles.active=$SPRING_PROFILE \
|
||||
-Dlogging.file.path=$LOG_DIR \
|
||||
-Dlogging.file.name=$LOG_DIR/application.log \
|
||||
-jar "$JAR_PATH" \
|
||||
> "$LOG_DIR/startup.log" 2>&1 &
|
||||
|
||||
# 保存 PID
|
||||
echo $! > "$PID_FILE"
|
||||
|
||||
log_info "服务启动中,PID: $(cat $PID_FILE)"
|
||||
log_info "启动日志: $LOG_DIR/startup.log"
|
||||
log_info "应用日志: $LOG_DIR/application.log"
|
||||
}
|
||||
|
||||
# 远程部署 - 上传文件到服务器
|
||||
# 参数: $1 - 可选,指定要上传的额外文件(如 deploy-server.sh)
|
||||
deploy_to_remote() {
|
||||
UPLOAD_SCRIPT="$2"
|
||||
|
||||
log_info "开始远程部署到 $REMOTE_HOST..."
|
||||
|
||||
# 检查并构建项目
|
||||
build_project
|
||||
|
||||
# 检查 jar 文件
|
||||
check_jar
|
||||
|
||||
# 创建远程目录
|
||||
log_info "创建远程目录..."
|
||||
if ! ssh $REMOTE_USER@$REMOTE_HOST "mkdir -p $REMOTE_DIR"; then
|
||||
log_error "创建远程目录失败: $REMOTE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
if ! ssh $REMOTE_USER@$REMOTE_HOST "mkdir -p $REMOTE_LOG_DIR"; then
|
||||
log_error "创建远程日志目录失败: $REMOTE_LOG_DIR"
|
||||
exit 1
|
||||
fi
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "mkdir -p $REMOTE_DIR" || { log_error "创建远程目录失败"; exit 1; }
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "mkdir -p $REMOTE_LOG_DIR" || { log_error "创建远程日志目录失败"; exit 1; }
|
||||
|
||||
# 上传 jar 文件并重命名
|
||||
log_info "上传 JAR 文件到远程服务器..."
|
||||
# 上传JAR文件
|
||||
log_info "上传JAR文件到远程服务器..."
|
||||
log_info "本地文件: $JAR_PATH"
|
||||
log_info "远程路径: $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/$REMOTE_JAR_NAME"
|
||||
|
||||
if ! scp "$JAR_PATH" $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/$REMOTE_JAR_NAME; then
|
||||
log_error "上传 JAR 文件失败"
|
||||
log_error "请检查:"
|
||||
log_error " 1. SSH 连接是否正常: ssh $REMOTE_USER@$REMOTE_HOST 'ls -la $REMOTE_DIR'"
|
||||
log_error " 2. 本地文件是否存在: ls -la $JAR_PATH"
|
||||
log_error " 3. 远程目录权限: ssh $REMOTE_USER@$REMOTE_HOST 'ls -la $REMOTE_DIR'"
|
||||
log_error "上传JAR文件失败"
|
||||
exit 1
|
||||
fi
|
||||
log_info "✅ JAR 文件上传成功"
|
||||
log_info "✅ JAR文件上传成功"
|
||||
|
||||
# 验证远程文件
|
||||
log_info "验证远程文件..."
|
||||
if ! ssh $REMOTE_USER@$REMOTE_HOST "ls -lh $REMOTE_DIR/$REMOTE_JAR_NAME"; then
|
||||
log_error "远程文件验证失败"
|
||||
exit 1
|
||||
fi
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "ls -lh $REMOTE_DIR/$REMOTE_JAR_NAME" || { log_error "远程文件验证失败"; exit 1; }
|
||||
|
||||
# 如果指定了额外文件,则上传
|
||||
# 上传额外文件
|
||||
if [ -n "$UPLOAD_SCRIPT" ] && [ -f "$UPLOAD_SCRIPT" ]; then
|
||||
log_info "上传指定文件到远程服务器: $UPLOAD_SCRIPT"
|
||||
log_info "上传文件到远程服务器: $UPLOAD_SCRIPT"
|
||||
if ! scp "$UPLOAD_SCRIPT" $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/; then
|
||||
log_error "上传文件失败: $UPLOAD_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
log_info "✅ 文件上传成功: $UPLOAD_SCRIPT"
|
||||
|
||||
# 如果是脚本文件,设置执行权限
|
||||
REMOTE_FILENAME=$(basename "$UPLOAD_SCRIPT")
|
||||
if [[ "$REMOTE_FILENAME" == *.sh ]]; then
|
||||
log_info "设置远程脚本权限..."
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "chmod +x $REMOTE_DIR/$REMOTE_FILENAME"
|
||||
fi
|
||||
else
|
||||
log_info "跳过部署脚本上传(服务器已存在)"
|
||||
fi
|
||||
|
||||
# 在远程服务器上执行部署
|
||||
# 执行远程部署
|
||||
log_info "在远程服务器上执行部署..."
|
||||
if ! ssh $REMOTE_USER@$REMOTE_HOST "cd $REMOTE_DIR && ./deploy-server.sh $SPRING_PROFILE"; then
|
||||
log_error "远程部署脚本执行失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "✅ 远程部署完成!"
|
||||
show_remote_info
|
||||
log_info "✅ 部署完成!"
|
||||
show_status
|
||||
}
|
||||
|
||||
# 本地部署 - 检查服务状态
|
||||
check_local_status() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
log_info "服务运行中 (PID: $PID)"
|
||||
return 0
|
||||
else
|
||||
log_error "PID 文件存在但进程不存在"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_error "PID 文件不存在,服务未运行"
|
||||
return 1
|
||||
fi
|
||||
# 打印使用说明
|
||||
print_usage() {
|
||||
echo "用法: $0 [命令] [参数]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " (无参数) - 部署到远程服务器(默认)"
|
||||
echo " [文件名] - 部署并上传指定文件(如 deploy-server.sh)"
|
||||
echo " build - 仅构建项目"
|
||||
echo " status - 查看远程服务状态"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 # 部署到远程服务器"
|
||||
echo " $0 deploy-server.sh # 同时上传部署脚本"
|
||||
echo " $0 build # 仅构建项目"
|
||||
echo " $0 status # 查看服务状态"
|
||||
}
|
||||
|
||||
# 本地部署 - 等待服务启动
|
||||
wait_for_local_startup() {
|
||||
log_info "等待服务启动..."
|
||||
for i in {1..60}; do
|
||||
if check_local_status > /dev/null 2>&1; then
|
||||
# 检查端口是否监听(使用 19089 端口)
|
||||
if netstat -tlnp 2>/dev/null | grep -q ":19089.*LISTEN"; then
|
||||
log_info "服务启动成功!"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
log_error "服务启动超时,请检查日志: $LOG_DIR/startup.log"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 本地部署 - 显示服务信息
|
||||
show_local_info() {
|
||||
log_info "=== 本地服务信息 ==="
|
||||
log_info "应用名称: $APP_NAME"
|
||||
log_info "JAR 文件: $JAR_PATH"
|
||||
log_info "日志目录: $LOG_DIR"
|
||||
log_info "PID 文件: $PID_FILE"
|
||||
log_info "Java 参数: $JAVA_OPTS"
|
||||
log_info "Spring Profile: $SPRING_PROFILE"
|
||||
|
||||
if check_local_status > /dev/null 2>&1; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
log_info "服务状态: 运行中 (PID: $PID)"
|
||||
|
||||
# 显示内存使用情况
|
||||
if command -v jstat > /dev/null 2>&1; then
|
||||
log_info "内存使用情况:"
|
||||
jstat -gc "$PID" | head -2
|
||||
fi
|
||||
else
|
||||
log_info "服务状态: 未运行"
|
||||
fi
|
||||
}
|
||||
|
||||
# 远程部署 - 显示服务信息
|
||||
show_remote_info() {
|
||||
log_info "=== 远程服务信息 ==="
|
||||
log_info "服务器地址: $REMOTE_HOST"
|
||||
log_info "部署目录: $REMOTE_DIR"
|
||||
log_info "日志目录: $REMOTE_LOG_DIR"
|
||||
log_info "Spring Profile: $SPRING_PROFILE"
|
||||
|
||||
# 检查远程服务状态
|
||||
log_info "检查远程服务状态..."
|
||||
ssh $REMOTE_USER@$REMOTE_HOST "ps aux | grep $REMOTE_JAR_NAME | grep -v grep" || log_info "远程服务未运行"
|
||||
}
|
||||
|
||||
# 本地部署 - 主函数
|
||||
local_deploy() {
|
||||
log_info "开始本地部署 $APP_NAME 服务..."
|
||||
|
||||
# 检查并构建项目
|
||||
build_project
|
||||
|
||||
# 检查 jar 文件
|
||||
check_jar
|
||||
|
||||
# 创建日志目录
|
||||
create_log_dir
|
||||
|
||||
# 停止旧服务
|
||||
stop_local_service
|
||||
|
||||
# 启动新服务
|
||||
start_local_service
|
||||
|
||||
# 等待启动
|
||||
if wait_for_local_startup; then
|
||||
log_info "本地部署成功!"
|
||||
show_local_info
|
||||
else
|
||||
log_error "本地部署失败!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 处理命令行参数
|
||||
case "${1:-deploy}" in
|
||||
"deploy")
|
||||
local_deploy
|
||||
;;
|
||||
"remote")
|
||||
deploy_to_remote "$@"
|
||||
# 主逻辑
|
||||
case "${1:-}" in
|
||||
"")
|
||||
deploy
|
||||
;;
|
||||
"build")
|
||||
build_project
|
||||
;;
|
||||
"start")
|
||||
# 检查并构建项目
|
||||
build_project
|
||||
check_jar
|
||||
create_log_dir
|
||||
start_local_service
|
||||
wait_for_local_startup
|
||||
;;
|
||||
"stop")
|
||||
stop_local_service
|
||||
;;
|
||||
"restart")
|
||||
stop_local_service
|
||||
sleep 2
|
||||
# 检查并构建项目
|
||||
build_project
|
||||
check_jar
|
||||
create_log_dir
|
||||
start_local_service
|
||||
wait_for_local_startup
|
||||
;;
|
||||
"status")
|
||||
show_local_info
|
||||
show_status
|
||||
;;
|
||||
"remote-status")
|
||||
show_remote_info
|
||||
;;
|
||||
"logs")
|
||||
if [ -f "$LOG_DIR/application.log" ]; then
|
||||
tail -f "$LOG_DIR/application.log"
|
||||
else
|
||||
log_error "日志文件不存在: $LOG_DIR/application.log"
|
||||
fi
|
||||
"help"|"-h"|"--help")
|
||||
print_usage
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {deploy|remote [文件名]|build|start|stop|restart|status|remote-status|logs}"
|
||||
echo " deploy - 本地部署服务(默认)"
|
||||
echo " remote - 远程部署到服务器(仅上传JAR)"
|
||||
echo " remote [文件名] - 远程部署并上传指定文件(如 deploy-server.sh)"
|
||||
echo " build - 构建项目"
|
||||
echo " start - 启动本地服务"
|
||||
echo " stop - 停止本地服务"
|
||||
echo " restart - 重启本地服务"
|
||||
echo " status - 查看本地服务状态"
|
||||
echo " remote-status - 查看远程服务状态"
|
||||
echo " logs - 查看本地实时日志"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 remote # 仅上传JAR并部署"
|
||||
echo " $0 remote deploy-server.sh # 同时上传部署脚本"
|
||||
exit 1
|
||||
# 如果参数是文件,则上传该文件
|
||||
if [ -f "$1" ]; then
|
||||
deploy "$1"
|
||||
else
|
||||
deploy "$1"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
esac
|
||||
|
||||
@@ -97,4 +97,15 @@ public class UserProfileController {
|
||||
List<UserProfileResponse> list = userProfileService.getProfileList(request);
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 迁移用户档案中的生命事件数据到t_life_event表
|
||||
* 将childhood、peak、valley数据迁移到生命事件表
|
||||
* 注意:此接口为一次性数据迁移接口,迁移完成后可删除
|
||||
*/
|
||||
@PostMapping("/migrateLifeEvents")
|
||||
public Result<Integer> migrateLifeEvents() {
|
||||
int count = userProfileService.migrateLifeEventsFromProfiles();
|
||||
return Result.success(count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,4 +72,12 @@ public interface UserProfileService extends IService<UserProfile> {
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean deleteProfile(String id);
|
||||
|
||||
/**
|
||||
* 迁移用户档案中的生命事件数据到t_life_event表
|
||||
* 将childhood、peak、valley数据迁移到生命事件表
|
||||
*
|
||||
* @return 迁移的记录数
|
||||
*/
|
||||
int migrateLifeEventsFromProfiles();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_DATE_TIME;
|
||||
private static final DateTimeFormatter DATE_ONLY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
@Override
|
||||
public PageResult<LifeEventResponse> getPageByCurrentUser(LifeEventPageRequest request) {
|
||||
@@ -130,16 +131,8 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
|
||||
event.setEmotionType(request.getEmotionType());
|
||||
event.setTags(request.getTags());
|
||||
|
||||
// 解析事件日期
|
||||
if (StringUtils.hasText(request.getEventDate())) {
|
||||
try {
|
||||
event.setEventDate(LocalDateTime.parse(request.getEventDate(), ISO_FORMATTER));
|
||||
} catch (Exception e) {
|
||||
event.setEventDate(LocalDateTime.now());
|
||||
}
|
||||
} else {
|
||||
event.setEventDate(LocalDateTime.now());
|
||||
}
|
||||
// 解析事件日期,支持多种格式
|
||||
event.setEventDate(parseEventDate(request.getEventDate()));
|
||||
|
||||
// 情绪评分
|
||||
if (request.getEmotionScore() != null) {
|
||||
@@ -183,10 +176,7 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
|
||||
event.setTags(request.getTags());
|
||||
}
|
||||
if (StringUtils.hasText(request.getEventDate())) {
|
||||
try {
|
||||
event.setEventDate(LocalDateTime.parse(request.getEventDate(), ISO_FORMATTER));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
event.setEventDate(parseEventDate(request.getEventDate()));
|
||||
}
|
||||
if (request.getEmotionScore() != null) {
|
||||
event.setEmotionScore(BigDecimal.valueOf(request.getEmotionScore()));
|
||||
@@ -234,4 +224,38 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析事件日期,支持多种格式
|
||||
* 支持格式:yyyy-MM-dd、yyyy-MM-ddTHH:mm:ss、ISO_DATE_TIME
|
||||
*
|
||||
* @param dateStr 日期字符串
|
||||
* @return 解析后的LocalDateTime,解析失败返回当前时间
|
||||
*/
|
||||
private LocalDateTime parseEventDate(String dateStr) {
|
||||
if (!StringUtils.hasText(dateStr)) {
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
|
||||
// 尝试ISO格式 (yyyy-MM-ddTHH:mm:ss.SSSZ)
|
||||
try {
|
||||
return LocalDateTime.parse(dateStr, ISO_FORMATTER);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// 尝试日期时间格式 (yyyy-MM-dd HH:mm:ss)
|
||||
try {
|
||||
return LocalDateTime.parse(dateStr, DATE_TIME_FORMATTER);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// 尝试纯日期格式 (yyyy-MM-dd),时间设为当天开始
|
||||
try {
|
||||
return java.time.LocalDate.parse(dateStr, DATE_ONLY_FORMATTER).atStartOfDay();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
// 所有格式都失败,返回当前时间
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,24 @@ import com.emotion.dto.request.userprofile.UserProfileCreateRequest;
|
||||
import com.emotion.dto.request.userprofile.UserProfilePageRequest;
|
||||
import com.emotion.dto.request.userprofile.UserProfileUpdateRequest;
|
||||
import com.emotion.dto.response.userprofile.UserProfileResponse;
|
||||
import com.emotion.entity.LifeEvent;
|
||||
import com.emotion.entity.User;
|
||||
import com.emotion.entity.UserProfile;
|
||||
import com.emotion.mapper.UserProfileMapper;
|
||||
import com.emotion.service.LifeEventService;
|
||||
import com.emotion.service.UserProfileService;
|
||||
import com.emotion.service.UserService;
|
||||
import com.emotion.util.UserContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -39,6 +45,10 @@ public class UserProfileServiceImpl extends ServiceImpl<UserProfileMapper, UserP
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private LifeEventService lifeEventService;
|
||||
|
||||
@Override
|
||||
public UserProfileResponse createProfile(UserProfileCreateRequest request) {
|
||||
log.info("Creating user profile: {}", request);
|
||||
@@ -207,6 +217,126 @@ public class UserProfileServiceImpl extends ServiceImpl<UserProfileMapper, UserP
|
||||
return removeById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int migrateLifeEventsFromProfiles() {
|
||||
log.info("开始迁移用户档案中的生命事件数据到t_life_event表");
|
||||
|
||||
// 查询所有有生命事件数据的用户档案
|
||||
LambdaQueryWrapper<UserProfile> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.and(w -> w
|
||||
.isNotNull(UserProfile::getChildhoodContent).ne(UserProfile::getChildhoodContent, "")
|
||||
.or().isNotNull(UserProfile::getPeakContent).ne(UserProfile::getPeakContent, "")
|
||||
.or().isNotNull(UserProfile::getValleyContent).ne(UserProfile::getValleyContent, "")
|
||||
);
|
||||
|
||||
List<UserProfile> profiles = list(queryWrapper);
|
||||
log.info("找到 {} 个有生命事件数据的用户档案", profiles.size());
|
||||
|
||||
int migratedCount = 0;
|
||||
|
||||
for (UserProfile profile : profiles) {
|
||||
String userId = profile.getUserId();
|
||||
List<LifeEvent> eventsToSave = new ArrayList<>();
|
||||
|
||||
// 迁移童年记忆
|
||||
if (StringUtils.hasText(profile.getChildhoodContent())) {
|
||||
if (!isEventExists(userId, "childhood")) {
|
||||
LifeEvent childhoodEvent = createLifeEventFromProfile(
|
||||
userId,
|
||||
"童年记忆",
|
||||
profile.getChildhoodContent(),
|
||||
profile.getChildhoodDate(),
|
||||
"childhood"
|
||||
);
|
||||
eventsToSave.add(childhoodEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移高光时刻 (peak -> joy)
|
||||
if (StringUtils.hasText(profile.getPeakContent())) {
|
||||
if (!isEventExists(userId, "joy")) {
|
||||
LifeEvent peakEvent = createLifeEventFromProfile(
|
||||
userId,
|
||||
"光芒闪耀的时刻",
|
||||
profile.getPeakContent(),
|
||||
profile.getPeakDate(),
|
||||
"joy"
|
||||
);
|
||||
eventsToSave.add(peakEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// 迁移低谷时期 (valley -> low)
|
||||
if (StringUtils.hasText(profile.getValleyContent())) {
|
||||
if (!isEventExists(userId, "low")) {
|
||||
LifeEvent valleyEvent = createLifeEventFromProfile(
|
||||
userId,
|
||||
"在暗夜中潜行",
|
||||
profile.getValleyContent(),
|
||||
profile.getValleyDate(),
|
||||
"low"
|
||||
);
|
||||
eventsToSave.add(valleyEvent);
|
||||
}
|
||||
}
|
||||
|
||||
// 批量保存
|
||||
if (!eventsToSave.isEmpty()) {
|
||||
lifeEventService.saveBatch(eventsToSave);
|
||||
migratedCount += eventsToSave.size();
|
||||
log.info("用户 {} 迁移了 {} 条生命事件", userId, eventsToSave.size());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("生命事件数据迁移完成,共迁移 {} 条记录", migratedCount);
|
||||
return migratedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已存在指定标签的生命事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param tag 标签
|
||||
* @return 是否存在
|
||||
*/
|
||||
private boolean isEventExists(String userId, String tag) {
|
||||
LambdaQueryWrapper<LifeEvent> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(LifeEvent::getUserId, userId)
|
||||
.eq(LifeEvent::getIsDeleted, 0)
|
||||
.like(LifeEvent::getTags, tag);
|
||||
return lifeEventService.count(wrapper) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从用户档案数据创建生命事件
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param title 事件标题
|
||||
* @param content 事件内容
|
||||
* @param eventDate 事件日期
|
||||
* @param tag 事件标签
|
||||
* @return 生命事件实体
|
||||
*/
|
||||
private LifeEvent createLifeEventFromProfile(String userId, String title, String content,
|
||||
LocalDate eventDate, String tag) {
|
||||
LifeEvent event = new LifeEvent();
|
||||
event.setUserId(userId);
|
||||
event.setTitle(title);
|
||||
event.setContent(content);
|
||||
event.setEventType("milestone");
|
||||
event.setTags(List.of(tag));
|
||||
|
||||
// 设置事件日期,如果没有则使用当前时间
|
||||
if (eventDate != null) {
|
||||
event.setEventDate(eventDate.atStartOfDay());
|
||||
} else {
|
||||
event.setEventDate(java.time.LocalDateTime.now());
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
private UserProfileResponse convertToResponse(UserProfile userProfile) {
|
||||
if (userProfile == null) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user