diff --git a/backend/Jenkins-Pipeline配置.md b/backend/Jenkins-Pipeline配置.md new file mode 100644 index 0000000..62d7513 --- /dev/null +++ b/backend/Jenkins-Pipeline配置.md @@ -0,0 +1,315 @@ +# 情感博物馆 Jenkins Pipeline 配置指南 + +## 概述 + +本文档描述了优化后的Jenkins CI/CD流水线配置,采用分离式部署架构: +- **构建阶段**: 在Jenkins服务器上编译打包所有微服务 +- **部署阶段**: 将jar包传输到远程应用服务器并进行容器化部署 + +## 部署脚本架构 + +### 1. 脚本分类 + +#### 🔨 构建脚本 +- **`build-all.sh`**: 专门用于Jenkins构建阶段,编译所有微服务jar包 +- **特点**: 仅在Jenkins服务器执行,不涉及远程操作 + +#### 🚀 部署脚本 +- **`deploy-remote.sh`**: 专门用于远程部署,传输jar包并部署到应用服务器 +- **特点**: 假设jar包已构建完成,专注于远程部署 + +#### 🔄 综合脚本 +- **`deploy-all.sh`**: 支持多种模式的综合脚本 +- **模式**: + - `full`: 完整模式 (构建+部署) + - `build`: 仅构建模式 + - `deploy`: 仅部署模式 + +### 2. 服务列表 + +所有脚本支持以下10个微服务: + +| 服务名称 | 端口 | 描述 | +|---------|------|------| +| emotion-gateway | 19000 | API网关服务 | +| emotion-user | 19001 | 用户管理服务 | +| emotion-ai | 19002 | AI聊天服务 | +| emotion-record | 19003 | 记录管理服务 | +| emotion-growth | 19004 | 成长跟踪服务 | +| emotion-explore | 19005 | 探索服务 | +| emotion-reward | 19006 | 奖励服务 | +| emotion-websocket | 19007 | WebSocket服务 | +| emotion-auth | 19008 | 认证服务 | +| emotion-stats | 19009 | 统计服务 | + +## Jenkins Pipeline 配置 + +### 1. 推荐的Pipeline配置 + +#### 方案一:分离式Pipeline (推荐) + +```groovy +pipeline { + agent any + + environment { + DEPLOY_ENV = 'test' + PROJECT_NAME = 'emotion-museum' + REMOTE_HOST = 'root@47.111.10.27' + } + + stages { + stage('Checkout') { + steps { + git branch: 'master', url: 'your-git-repo-url' + } + } + + stage('Build') { + steps { + dir('backend') { + sh './build-all.sh' + } + } + post { + always { + // 归档构建产物 + archiveArtifacts artifacts: 'backend/*/target/*.jar', fingerprint: true + } + } + } + + stage('Deploy to Remote') { + steps { + dir('backend') { + sh './deploy-remote.sh' + } + } + } + + stage('Health Check') { + steps { + script { + // 等待服务启动 + sleep 30 + + // 检查关键服务 + def services = ['19000', '19001', '19002', '19008'] + services.each { port -> + sh "curl -f http://47.111.10.27:${port}/actuator/health || exit 1" + } + } + } + } + } + + post { + always { + // 发送部署结果通知 + emailext ( + subject: "部署结果: ${env.JOB_NAME} - ${env.BUILD_NUMBER}", + body: """ + 部署状态: ${currentBuild.result} + 构建链接: ${env.BUILD_URL} + 部署环境: ${env.DEPLOY_ENV} + """, + to: "your-email@example.com" + ) + } + success { + echo '🎉 部署成功!' + } + failure { + echo '❌ 部署失败,请检查日志' + } + } +} +``` + +#### 方案二:单一Pipeline + +```groovy +pipeline { + agent any + + environment { + DEPLOY_ENV = 'test' + DEPLOY_MODE = 'full' // full, build, deploy + PROJECT_NAME = 'emotion-museum' + } + + stages { + stage('Checkout') { + steps { + git branch: 'master', url: 'your-git-repo-url' + } + } + + stage('Deploy') { + steps { + dir('backend') { + sh './deploy-all.sh' + } + } + } + } +} +``` + +### 2. 环境变量配置 + +在Jenkins中配置以下环境变量: + +#### 必需变量 +```bash +# 部署配置 +DEPLOY_ENV=test # 部署环境 +DEPLOY_HOST=root@47.111.10.27 # 目标服务器 +PROJECT_NAME=emotion-museum # 项目名称 + +# 远程路径配置 +REMOTE_BUILD_DIR=/data/builds # jar包存储目录 +REMOTE_DOCKER_DIR=/data/docker # Docker配置目录 +``` + +#### 可选变量 +```bash +# 部署模式 (仅deploy-all.sh使用) +DEPLOY_MODE=full # full, build, deploy + +# 数据库配置 +MYSQL_HOST=47.111.10.27 +MYSQL_PASSWORD=EmotionMuseum2025*# + +# Nacos配置 +NACOS_SERVER_ADDR=47.111.10.27:8848 +NACOS_PASSWORD=Peanut2817*# +``` + +### 3. 多环境部署配置 + +#### 测试环境 +```groovy +environment { + DEPLOY_ENV = 'test' + DEPLOY_HOST = 'root@47.111.10.27' +} +``` + +#### 生产环境 +```groovy +environment { + DEPLOY_ENV = 'prod' + DEPLOY_HOST = 'root@production-server' +} +``` + +## 使用方法 + +### 1. 本地测试 + +#### 仅构建 +```bash +cd backend +./build-all.sh +``` + +#### 仅部署 (需要先有jar包) +```bash +cd backend +./deploy-remote.sh +``` + +#### 完整部署 +```bash +cd backend +./deploy-all.sh +# 或指定模式 +DEPLOY_MODE=full ./deploy-all.sh +``` + +### 2. Jenkins执行 + +#### 分离式执行 +```bash +# 构建阶段 +./build-all.sh + +# 部署阶段 +./deploy-remote.sh +``` + +#### 综合执行 +```bash +# 根据DEPLOY_MODE环境变量决定执行模式 +./deploy-all.sh +``` + +## 优势特点 + +### 1. 🔄 分离式架构 +- **构建与部署分离**: Jenkins服务器专注构建,应用服务器专注运行 +- **资源优化**: 避免在应用服务器上安装构建工具 +- **安全性**: 减少应用服务器的攻击面 + +### 2. 🛡️ 容错机制 +- **单服务失败不影响其他服务**: 继续部署其他服务 +- **详细错误报告**: 每个服务的部署状态和错误信息 +- **回滚支持**: 保留旧版本容器,支持快速回滚 + +### 3. 📊 监控与报告 +- **实时日志**: 详细的部署过程日志 +- **健康检查**: 自动验证服务启动状态 +- **部署报告**: 完整的部署统计和结果报告 + +### 4. 🔧 灵活配置 +- **多模式支持**: 支持仅构建、仅部署、完整部署 +- **环境变量**: 支持Jenkins环境变量覆盖 +- **多环境**: 支持test/prod等多环境部署 + +## 故障排查 + +### 1. 构建失败 +```bash +# 检查Java和Maven版本 +java -version +mvn -version + +# 查看详细构建日志 +./build-all.sh +``` + +### 2. 部署失败 +```bash +# 检查SSH连接 +ssh 'root@47.111.10.27' "echo 'test'" + +# 检查远程Docker环境 +ssh 'root@47.111.10.27' "docker --version" + +# 查看容器日志 +ssh 'root@47.111.10.27' "docker logs " +``` + +### 3. 服务启动失败 +```bash +# 检查端口占用 +ssh 'root@47.111.10.27' "netstat -tlnp | grep " + +# 检查服务健康状态 +curl -f http://47.111.10.27:/actuator/health +``` + +## 联系支持 + +如遇到问题,请: +1. 查看Jenkins构建日志 +2. 检查脚本执行日志 +3. 验证环境变量配置 +4. 联系开发团队并提供完整日志 + +--- + +**文档版本**: v2.0 +**更新时间**: 2025-07-18 +**维护团队**: 情感博物馆开发团队 diff --git a/backend/build-all.sh b/backend/build-all.sh new file mode 100755 index 0000000..1d981c2 --- /dev/null +++ b/backend/build-all.sh @@ -0,0 +1,256 @@ +#!/bin/bash + +# 情感博物馆 - Jenkins构建脚本 +# 作者: emotion-museum +# 日期: 2025-07-18 +# 用途: 在Jenkins服务器上构建所有微服务jar包 + +set -e + +# 配置变量 +PROFILE="${DEPLOY_ENV:-test}" +PROJECT_NAME="${PROJECT_NAME:-emotion-museum}" + +# Jenkins构建信息 +BUILD_NUMBER="${BUILD_NUMBER:-manual}" +JOB_NAME="${JOB_NAME:-local-build}" +BUILD_URL="${BUILD_URL:-}" + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 日志函数 +log_info() { + echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +# 服务列表 +SERVICES=( + "emotion-gateway:19000" + "emotion-user:19001" + "emotion-ai:19002" + "emotion-record:19003" + "emotion-growth:19004" + "emotion-explore:19005" + "emotion-reward:19006" + "emotion-websocket:19007" + "emotion-auth:19008" + "emotion-stats:19009" +) + +# 检查构建环境 +check_build_environment() { + log_info "检查构建环境..." + + # 检查Java版本 + if command -v java &> /dev/null; then + local java_version=$(java -version 2>&1 | head -1 | cut -d'"' -f2) + log_info "Java版本: $java_version" + else + log_error "Java未安装" + exit 1 + fi + + # 检查Maven版本 + if command -v mvn &> /dev/null; then + local maven_version=$(mvn -version | head -1 | awk '{print $3}') + log_info "Maven版本: $maven_version" + else + log_error "Maven未安装" + exit 1 + fi + + # 检查是否在Jenkins环境中 + if [ -n "$JENKINS_HOME" ] || [ -n "$BUILD_NUMBER" ]; then + log_info "检测到Jenkins环境" + log_info "构建编号: $BUILD_NUMBER" + log_info "任务名称: $JOB_NAME" + else + log_info "本地构建环境" + fi + + log_success "构建环境检查通过" +} + +# 清理旧的构建产物 +clean_old_artifacts() { + log_info "清理旧的构建产物..." + + # 清理父项目 + mvn clean -q + + # 清理各个子模块 + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + if [ -d "$service_name" ]; then + log_info "清理模块: $service_name" + cd $service_name + mvn clean -q + cd .. + fi + done + + log_success "构建产物清理完成" +} + +# 构建所有服务 +build_all_services() { + log_info "开始构建所有微服务..." + + # 先构建父项目 + log_info "构建父项目..." + if mvn install -DskipTests -q; then + log_success "父项目构建成功" + else + log_error "父项目构建失败" + exit 1 + fi + + # 构建各个微服务 + local build_success=0 + local build_failed=0 + + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + log_info "构建服务: $service_name" + + if [ ! -d "$service_name" ]; then + log_warning "服务目录不存在: $service_name" + continue + fi + + cd $service_name + if mvn package -DskipTests -P${PROFILE} -q; then + # 检查jar包是否生成 + if [ -f "target/${service_name}-1.0.0.jar" ]; then + local jar_size=$(du -h "target/${service_name}-1.0.0.jar" | cut -f1) + log_success "✅ $service_name 构建成功 (大小: $jar_size)" + build_success=$((build_success + 1)) + else + log_error "❌ $service_name jar包未生成" + build_failed=$((build_failed + 1)) + fi + else + log_error "❌ $service_name 构建失败" + build_failed=$((build_failed + 1)) + fi + cd .. + done + + log_info "构建统计: 成功 $build_success, 失败 $build_failed" + + if [ $build_failed -eq 0 ]; then + log_success "所有服务构建成功" + return 0 + else + log_error "部分服务构建失败" + return 1 + fi +} + +# 生成构建报告 +generate_build_report() { + local total_time=$1 + + echo "" + echo "========================================" + echo " 构建完成报告" + echo "========================================" + echo "项目名称: $PROJECT_NAME" + echo "构建环境: $PROFILE" + echo "构建时间: $(date '+%Y-%m-%d %H:%M:%S')" + echo "总耗时: ${total_time}s" + if [ "$BUILD_NUMBER" != "manual" ]; then + echo "Jenkins构建: #$BUILD_NUMBER" + echo "Jenkins任务: $JOB_NAME" + [ -n "$BUILD_URL" ] && echo "构建链接: $BUILD_URL" + fi + echo "========================================" + + echo "" + echo "📦 构建产物详情:" + printf "%-20s %-10s %-10s %s\n" "服务名称" "状态" "大小" "路径" + echo "----------------------------------------" + + local total_size=0 + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + jar_file="${service_name}/target/${service_name}-1.0.0.jar" + + if [ -f "$jar_file" ]; then + jar_size=$(du -h "$jar_file" | cut -f1) + jar_bytes=$(du -b "$jar_file" | cut -f1) + total_size=$((total_size + jar_bytes)) + printf "%-20s ${GREEN}%-10s${NC} %-10s %s\n" "$service_name" "✅ 成功" "$jar_size" "$jar_file" + else + printf "%-20s ${RED}%-10s${NC} %-10s %s\n" "$service_name" "❌ 失败" "N/A" "未生成" + fi + done + + echo "" + echo "📊 构建统计:" + echo " 总产物大小: $(echo $total_size | awk '{printf "%.1fMB", $1/1024/1024}')" + echo " 构建工作空间: $(pwd)" + echo "" + echo "========================================" + echo "🎉 构建任务完成!" +} + +# 主函数 +main() { + local start_time=$(date +%s) + + log_info "🔨 开始Jenkins构建任务..." + log_info "构建环境: $PROFILE" + log_info "项目名称: $PROJECT_NAME" + + # 检查构建环境 + check_build_environment + + # 清理旧产物 + clean_old_artifacts + + # 构建所有服务 + if build_all_services; then + log_success "所有服务构建成功" + build_result=0 + else + log_error "部分服务构建失败" + build_result=1 + fi + + # 计算总耗时 + local end_time=$(date +%s) + local total_time=$((end_time - start_time)) + + # 生成构建报告 + generate_build_report $total_time + + # 返回构建结果 + if [ $build_result -eq 0 ]; then + log_success "🎉 Jenkins构建任务完成!" + exit 0 + else + log_error "⚠️ 构建任务部分失败,请检查错误日志" + exit 1 + fi +} + +# 执行主函数 +main "$@" diff --git a/backend/deploy-all.sh b/backend/deploy-all.sh index 3deac18..387ce72 100755 --- a/backend/deploy-all.sh +++ b/backend/deploy-all.sh @@ -20,6 +20,9 @@ BUILD_NUMBER="${BUILD_NUMBER:-manual}" JOB_NAME="${JOB_NAME:-local-deploy}" BUILD_URL="${BUILD_URL:-}" +# 部署模式配置 +DEPLOY_MODE="${DEPLOY_MODE:-full}" # full: 完整部署, build: 仅构建, deploy: 仅部署 + # 颜色输出 RED='\033[0;31m' GREEN='\033[0;32m' @@ -89,21 +92,42 @@ create_remote_directories() { log_success "远程目录创建完成" } -# 构建所有服务 +# 构建所有服务 (Jenkins阶段) build_all_services() { - log_info "开始构建所有微服务..." - + log_info "开始在Jenkins服务器上构建所有微服务..." + + # 检查是否在Jenkins环境中 + if [ -n "$JENKINS_HOME" ] || [ -n "$BUILD_NUMBER" ]; then + log_info "检测到Jenkins环境,执行完整构建流程" + else + log_info "本地环境,执行构建流程" + fi + # 先构建父项目 log_info "构建父项目..." - mvn clean install -DskipTests -q - + if mvn clean install -DskipTests -q; then + log_success "父项目构建成功" + else + log_error "父项目构建失败" + exit 1 + fi + + # 构建各个微服务 for service_info in "${SERVICES[@]}"; do service_name=$(echo $service_info | cut -d':' -f1) log_info "构建服务: $service_name" - + cd $service_name - if mvn clean package -DskipTests -Ptest -q; then - log_success "服务 $service_name 构建成功" + if mvn clean package -DskipTests -P${PROFILE} -q; then + # 检查jar包是否生成 + if [ -f "target/${service_name}-1.0.0.jar" ]; then + local jar_size=$(du -h "target/${service_name}-1.0.0.jar" | cut -f1) + log_success "服务 $service_name 构建成功 (大小: $jar_size)" + else + log_error "服务 $service_name jar包未生成" + cd .. + exit 1 + fi else log_error "服务 $service_name 构建失败" cd .. @@ -111,39 +135,91 @@ build_all_services() { fi cd .. done - - log_success "所有服务构建完成" + + log_success "所有服务在Jenkins服务器构建完成" } -# 部署单个服务 +# 部署所有服务到远程服务器 +deploy_all_services_to_remote() { + log_info "开始逐个部署服务到远程服务器..." + + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + service_port=$(echo $service_info | cut -d':' -f2) + + echo "" + log_info "[$((SUCCESSFUL_DEPLOYMENTS + FAILED_DEPLOYMENTS + 1))/$TOTAL_SERVICES] 部署服务: $service_name" + + if deploy_service $service_name $service_port; then + SUCCESSFUL_DEPLOYMENTS=$((SUCCESSFUL_DEPLOYMENTS + 1)) + log_success "✅ 服务 $service_name 部署成功" + else + FAILED_DEPLOYMENTS=$((FAILED_DEPLOYMENTS + 1)) + log_error "❌ 服务 $service_name 部署失败,继续部署其他服务..." + fi + done +} + +# 传输jar包到远程服务器 +transfer_jar_to_remote() { + local service_name=$1 + + log_info "传输jar包到远程服务器: $service_name" + + # 检查本地jar包是否存在 + local jar_file="${service_name}/target/${service_name}-1.0.0.jar" + if [ ! -f "$jar_file" ]; then + log_error "本地JAR包不存在: $jar_file" + return 1 + fi + + # 显示jar包信息 + local jar_size=$(du -h "$jar_file" | cut -f1) + log_info "准备传输jar包: $jar_file (大小: $jar_size)" + + # 删除远程旧jar包 + log_info "清理远程旧jar包: $service_name" + ssh 'root@47.111.10.27' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar" + + # 上传新jar包 + log_info "上传jar包到远程服务器..." + if scp "$jar_file" 'root@47.111.10.27':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then + log_success "jar包传输成功: $service_name" + + # 验证远程jar包 + local remote_size=$(ssh 'root@47.111.10.27' "du -h $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar | cut -f1") + log_info "远程jar包大小: $remote_size" + return 0 + else + log_error "jar包传输失败: $service_name" + return 1 + fi +} + +# 部署单个服务 (远程服务器阶段) deploy_service() { local service_name=$1 local service_port=$2 local start_time=$(date +%s) - log_info "开始部署服务: $service_name" + log_info "开始部署服务到远程服务器: $service_name" DEPLOYMENT_STATUS[$service_name]="DEPLOYING" - # 检查jar包是否存在 - local jar_file="${service_name}/target/${service_name}-1.0.0.jar" - if [ ! -f "$jar_file" ]; then - local error_msg="JAR包不存在: $jar_file" - log_error "$error_msg" + # 先传输jar包 + if ! transfer_jar_to_remote $service_name; then + local error_msg="jar包传输失败" DEPLOYMENT_STATUS[$service_name]="FAILED" DEPLOYMENT_ERRORS[$service_name]="$error_msg" return 1 fi - # 删除远程旧jar包 - log_info "删除远程旧jar包: $service_name" - ssh 'root@47.111.10.27' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar" - - # 上传新jar包 - log_info "上传jar包: $service_name" - if scp "$jar_file" 'root@47.111.10.27':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then - log_success "jar包上传成功: $service_name" - else - log_error "jar包上传失败: $service_name" + # 验证远程jar包存在 + log_info "验证远程jar包: $service_name" + if ! ssh 'root@47.111.10.27' "test -f $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar"; then + local error_msg="远程jar包不存在,请先执行构建和传输" + log_error "$error_msg" + DEPLOYMENT_STATUS[$service_name]="FAILED" + DEPLOYMENT_ERRORS[$service_name]="$error_msg" return 1 fi @@ -385,8 +461,86 @@ main() { log_info "🚀 开始全服务容器化部署..." log_info "目标服务器: $REMOTE_HOST" log_info "部署环境: $PROFILE" + log_info "部署模式: $DEPLOY_MODE" log_info "服务总数: $TOTAL_SERVICES" + # 根据部署模式执行不同的流程 + case $DEPLOY_MODE in + "build") + log_info "🔨 执行构建模式 - 仅在Jenkins服务器构建jar包" + execute_build_only + ;; + "deploy") + log_info "🚀 执行部署模式 - 仅部署到远程服务器" + execute_deploy_only + ;; + "full"|*) + log_info "🔄 执行完整模式 - 构建+部署" + execute_full_deployment + ;; + esac +} + +# 仅构建模式 +execute_build_only() { + local start_time=$(date +%s) + + log_info "开始构建所有服务..." + + # 构建服务 + if ! build_all_services; then + log_error "服务构建失败" + exit 1 + fi + + # 显示构建结果 + log_info "📦 构建产物信息:" + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + jar_file="${service_name}/target/${service_name}-1.0.0.jar" + if [ -f "$jar_file" ]; then + jar_size=$(du -h "$jar_file" | cut -f1) + log_success "✅ $service_name: $jar_size" + else + log_error "❌ $service_name: jar包未生成" + fi + done + + local end_time=$(date +%s) + local total_time=$((end_time - start_time)) + log_success "🎉 构建完成!总耗时: ${total_time}s" +} + +# 仅部署模式 +execute_deploy_only() { + local start_time=$(date +%s) + + log_info "开始部署到远程服务器..." + + # 检查连接 + if ! check_remote_connection; then + log_error "远程服务器连接失败,部署终止" + exit 1 + fi + + # 创建目录和网络 + create_remote_directories + create_docker_network + + # 部署所有服务 + deploy_all_services_to_remote + + # 健康检查和报告 + health_check + local end_time=$(date +%s) + local total_time=$((end_time - start_time)) + show_deployment_report $total_time +} + +# 完整部署模式 +execute_full_deployment() { + local start_time=$(date +%s) + # 检查连接 if ! check_remote_connection; then log_error "远程服务器连接失败,部署终止" @@ -405,23 +559,8 @@ main() { exit 1 fi - # 部署所有服务 - 单个失败不影响其他服务 - log_info "开始逐个部署服务..." - for service_info in "${SERVICES[@]}"; do - service_name=$(echo $service_info | cut -d':' -f1) - service_port=$(echo $service_info | cut -d':' -f2) - - echo "" - log_info "[$((SUCCESSFUL_DEPLOYMENTS + FAILED_DEPLOYMENTS + 1))/$TOTAL_SERVICES] 部署服务: $service_name" - - if deploy_service $service_name $service_port; then - SUCCESSFUL_DEPLOYMENTS=$((SUCCESSFUL_DEPLOYMENTS + 1)) - log_success "✅ 服务 $service_name 部署成功" - else - FAILED_DEPLOYMENTS=$((FAILED_DEPLOYMENTS + 1)) - log_error "❌ 服务 $service_name 部署失败,继续部署其他服务..." - fi - done + # 部署所有服务 + deploy_all_services_to_remote # 健康检查 log_info "执行服务健康检查..." diff --git a/backend/deploy-remote.sh b/backend/deploy-remote.sh new file mode 100755 index 0000000..fef06eb --- /dev/null +++ b/backend/deploy-remote.sh @@ -0,0 +1,358 @@ +#!/bin/bash + +# 情感博物馆 - 远程部署脚本 +# 作者: emotion-museum +# 日期: 2025-07-18 +# 用途: 将构建好的jar包部署到远程服务器 + +set -e + +# 配置变量 +REMOTE_HOST="'root@47.111.10.27'" +REMOTE_BUILD_DIR="/data/builds" +REMOTE_DOCKER_COMPOSE_DIR="/data/docker" +PROFILE="${DEPLOY_ENV:-test}" +PROJECT_NAME="${PROJECT_NAME:-emotion-museum}" + +# Jenkins构建信息 +BUILD_NUMBER="${BUILD_NUMBER:-manual}" +JOB_NAME="${JOB_NAME:-local-deploy}" +BUILD_URL="${BUILD_URL:-}" + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 日志函数 +log_info() { + echo -e "${BLUE}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $1" +} + +# 服务列表 +SERVICES=( + "emotion-gateway:19000" + "emotion-user:19001" + "emotion-ai:19002" + "emotion-record:19003" + "emotion-growth:19004" + "emotion-explore:19005" + "emotion-reward:19006" + "emotion-websocket:19007" + "emotion-auth:19008" + "emotion-stats:19009" +) + +# 部署状态跟踪 +declare -A DEPLOYMENT_STATUS +declare -A DEPLOYMENT_ERRORS +declare -A DEPLOYMENT_TIMES +TOTAL_SERVICES=${#SERVICES[@]} +SUCCESSFUL_DEPLOYMENTS=0 +FAILED_DEPLOYMENTS=0 + +# 检查远程服务器连接 +check_remote_connection() { + log_info "检查远程服务器连接..." + if ssh -o ConnectTimeout=10 'root@47.111.10.27' "echo 'Connection successful'" > /dev/null 2>&1; then + log_success "远程服务器连接正常" + else + log_error "无法连接到远程服务器 'root@47.111.10.27'" + exit 1 + fi +} + +# 检查本地jar包 +check_local_jars() { + log_info "检查本地jar包..." + + local missing_jars=0 + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + jar_file="${service_name}/target/${service_name}-1.0.0.jar" + + if [ -f "$jar_file" ]; then + jar_size=$(du -h "$jar_file" | cut -f1) + log_info "✅ $service_name: $jar_size" + else + log_error "❌ $service_name: jar包不存在" + missing_jars=$((missing_jars + 1)) + fi + done + + if [ $missing_jars -gt 0 ]; then + log_error "发现 $missing_jars 个缺失的jar包,请先执行构建" + exit 1 + fi + + log_success "所有jar包检查通过" +} + +# 传输所有jar包到远程服务器 +transfer_all_jars() { + log_info "开始传输所有jar包到远程服务器..." + + # 创建远程目录 + ssh 'root@47.111.10.27' "mkdir -p $REMOTE_BUILD_DIR" + + local transfer_success=0 + local transfer_failed=0 + + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + jar_file="${service_name}/target/${service_name}-1.0.0.jar" + + if [ ! -f "$jar_file" ]; then + log_warning "跳过不存在的jar包: $service_name" + continue + fi + + log_info "传输jar包: $service_name" + + # 删除远程旧jar包 + ssh 'root@47.111.10.27' "rm -f $REMOTE_BUILD_DIR/${service_name}-*.jar" + + # 上传新jar包 + if scp "$jar_file" 'root@47.111.10.27':$REMOTE_BUILD_DIR/${service_name}-1.0.0.jar; then + # 验证远程jar包 + remote_size=$(ssh 'root@47.111.10.27' "du -h $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar | cut -f1") + log_success "✅ $service_name 传输成功 (远程大小: $remote_size)" + transfer_success=$((transfer_success + 1)) + else + log_error "❌ $service_name 传输失败" + transfer_failed=$((transfer_failed + 1)) + fi + done + + log_info "传输统计: 成功 $transfer_success, 失败 $transfer_failed" + + if [ $transfer_failed -eq 0 ]; then + log_success "所有jar包传输成功" + return 0 + else + log_error "部分jar包传输失败" + return 1 + fi +} + +# 创建远程目录 +create_remote_directories() { + log_info "创建远程目录结构..." + ssh 'root@47.111.10.27' " + mkdir -p $REMOTE_BUILD_DIR + mkdir -p $REMOTE_DOCKER_COMPOSE_DIR + mkdir -p /data/logs/emotion-museum + mkdir -p /data/config/emotion-museum + " + log_success "远程目录创建完成" +} + +# 创建Docker网络 +create_docker_network() { + log_info "创建Docker网络..." + ssh 'root@47.111.10.27' " + docker network create emotion-network 2>/dev/null || true + " + log_success "Docker网络创建完成" +} + +# 部署单个服务到远程 +deploy_service_to_remote() { + local service_name=$1 + local service_port=$2 + local start_time=$(date +%s) + + log_info "部署服务到远程: $service_name" + DEPLOYMENT_STATUS[$service_name]="DEPLOYING" + + # 验证远程jar包存在 + if ! ssh 'root@47.111.10.27' "test -f $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar"; then + local error_msg="远程jar包不存在" + log_error "$error_msg" + DEPLOYMENT_STATUS[$service_name]="FAILED" + DEPLOYMENT_ERRORS[$service_name]="$error_msg" + return 1 + fi + + # 创建Dockerfile + create_dockerfile $service_name $service_port + + # 停止并删除旧容器 + log_info "停止旧容器: $service_name" + ssh 'root@47.111.10.27' " + docker stop ${service_name} 2>/dev/null || true + docker rm ${service_name} 2>/dev/null || true + docker rmi ${PROJECT_NAME}/${service_name}:latest 2>/dev/null || true + " + + # 构建Docker镜像 + log_info "构建Docker镜像: $service_name" + ssh 'root@47.111.10.27' " + cd $REMOTE_DOCKER_COMPOSE_DIR + docker build -t ${PROJECT_NAME}/${service_name}:latest -f Dockerfile.${service_name} . + " + + # 启动新容器 + log_info "启动新容器: $service_name" + ssh 'root@47.111.10.27' " + docker run -d \\ + --name ${service_name} \\ + --network emotion-network \\ + -p ${service_port}:${service_port} \\ + -v /data/logs/emotion-museum:/app/logs \\ + -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ + -e MYSQL_HOST=47.111.10.27 \\ + -e MYSQL_PORT=3306 \\ + -e MYSQL_DATABASE=emotion_museum \\ + -e MYSQL_USERNAME=root \\ + -e MYSQL_PASSWORD='EmotionMuseum2025*#' \\ + -e REDIS_HOST=47.111.10.27 \\ + -e REDIS_PORT=6379 \\ + -e REDIS_PASSWORD= \\ + -e REDIS_DATABASE=0 \\ + -e NACOS_SERVER_ADDR=47.111.10.27:8848 \\ + -e NACOS_USERNAME=nacos \\ + -e NACOS_PASSWORD='Peanut2817*#' \\ + --restart unless-stopped \\ + ${PROJECT_NAME}/${service_name}:latest + " + + # 等待服务启动 + log_info "等待服务启动: $service_name" + sleep 10 + + # 检查容器状态 + if ssh 'root@47.111.10.27' "docker ps | grep ${service_name}" > /dev/null 2>&1; then + log_success "服务 $service_name 启动成功" + + # 显示容器日志 + log_info "显示服务日志 最后10行: $service_name" + ssh 'root@47.111.10.27' "docker logs --tail 10 ${service_name}" 2>/dev/null || true + + # 记录成功状态 + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + DEPLOYMENT_STATUS[$service_name]="SUCCESS" + DEPLOYMENT_TIMES[$service_name]="${duration}s" + return 0 + else + local error_msg="服务启动失败" + log_error "服务 $service_name 启动失败" + local error_logs=$(ssh 'root@47.111.10.27' "docker logs ${service_name}" 2>&1 || echo "无法获取日志") + echo "$error_logs" + + # 记录失败状态 + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + DEPLOYMENT_STATUS[$service_name]="FAILED" + DEPLOYMENT_ERRORS[$service_name]="$error_msg: $error_logs" + DEPLOYMENT_TIMES[$service_name]="${duration}s" + return 1 + fi +} + +# 创建Dockerfile +create_dockerfile() { + local service_name=$1 + local service_port=$2 + + log_info "创建Dockerfile: $service_name" + + ssh 'root@47.111.10.27' "cat > $REMOTE_DOCKER_COMPOSE_DIR/Dockerfile.${service_name} << 'EOF' +FROM openjdk:17-jre-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +COPY $REMOTE_BUILD_DIR/${service_name}-1.0.0.jar app.jar + +RUN mkdir -p /app/logs + +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone + +EXPOSE ${service_port} + +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \\ + CMD curl -f http://localhost:${service_port}/actuator/health || exit 1 + +ENTRYPOINT [\"java\", \"-Djava.security.egd=file:/dev/./urandom\", \"-Xms512m\", \"-Xmx1024m\", \"-jar\", \"app.jar\"] +EOF" +} + +# 主函数 +main() { + local start_time=$(date +%s) + + log_info "🚀 开始远程部署任务..." + log_info "目标服务器: $REMOTE_HOST" + log_info "部署环境: $PROFILE" + log_info "服务总数: $TOTAL_SERVICES" + + # 检查远程连接 + check_remote_connection + + # 检查本地jar包 + check_local_jars + + # 创建远程目录和网络 + create_remote_directories + create_docker_network + + # 传输所有jar包 + if ! transfer_all_jars; then + log_error "jar包传输失败,部署终止" + exit 1 + fi + + # 部署所有服务 + log_info "开始逐个部署服务..." + for service_info in "${SERVICES[@]}"; do + service_name=$(echo $service_info | cut -d':' -f1) + service_port=$(echo $service_info | cut -d':' -f2) + + echo "" + log_info "[$((SUCCESSFUL_DEPLOYMENTS + FAILED_DEPLOYMENTS + 1))/$TOTAL_SERVICES] 部署服务: $service_name" + + if deploy_service_to_remote $service_name $service_port; then + SUCCESSFUL_DEPLOYMENTS=$((SUCCESSFUL_DEPLOYMENTS + 1)) + log_success "✅ 服务 $service_name 部署成功" + else + FAILED_DEPLOYMENTS=$((FAILED_DEPLOYMENTS + 1)) + log_error "❌ 服务 $service_name 部署失败,继续部署其他服务..." + fi + done + + # 计算总耗时 + local end_time=$(date +%s) + local total_time=$((end_time - start_time)) + + log_info "部署统计: 成功 $SUCCESSFUL_DEPLOYMENTS, 失败 $FAILED_DEPLOYMENTS" + + # 根据部署结果设置退出码 + if [ $FAILED_DEPLOYMENTS -eq 0 ]; then + log_success "🎉 远程部署任务完成!总耗时: ${total_time}s" + exit 0 + else + log_warning "⚠️ 部分服务部署失败,总耗时: ${total_time}s" + exit 1 + fi +} + +# 执行主函数 +main "$@"