手把手教你全流程项目部署:从 Jenkins 到 Nginx 的项目实战手册

在企业级项目中,多服务器协同部署是常态 ------Nginx、Tomcat、Redis、MySQL 各司其职,通过网络通信构建完整服务链路。但对于学习者而言,如何在一台虚拟机内复现这一流程,用 Jenkins 实现后端自动化部署、用 Nginx 搞定前端静态资源与反向代理?本文将手把手带你打通从环境配置到前后端联调的全流程,让复杂部署变得可操作、可复现。

一、 项目部署思路

在企业正常的项目部署中,可能会涉及多台服务器共同使用,如图中的Nginx、Tomcat、Redis、MySQL都会有对应的单独的服务器,让各个服务器进行互相通信即可完成部署

  • 其中的微信小程序必须租腾讯的服务器才能完成部署,我们涉及不到
  • 在学习阶段,我们所有的服务都使用同一个虚拟机来完成项目的部署

二. 准备Jenkins环境

2.1 基础环境要求

软件环境:

软件 版本 安装方式
docker Docker version 20.10.11 shell
docker-compose 1.29.1 shell
Java JDK 11.0.19 shell
Maven 3.6.1 shell
Git 1.8.3.1 shell
Redis 7.2.4 docker
MySQL 8.0.29 docker
Nginx 1.25.5 docker

附:安装git、jdk、maven的shell脚本

bash 复制代码
#!/bin/bash
# 安装 Git
echo "开始安装 Git..."
yum install git -y
echo "Git安装完成"
echo `git --version`
​
# 下载并安装 JDK 11
echo "开始下载并安装 JDK 11..."
#JDK_URL="https://download.oracle.com/otn/java/jdk/11.0.21%2B9/8819d0447e4d41b3bd1d9e1007728d17/jdk-11.0.21_linux-x64_bin.tar.gz"  # 替换为实际的JDK下载地址
#wget $JDK_URL
# 本地将JDK上传到Linux服务器
tar -xzf jdk-11.0.21_linux-x64_bin.tar.gz
mv jdk-11.0.21 /usr/local/src/
chmod +x /usr/local/src/jdk-11.0.21/bin/*
# 设置环境变量
echo "export JAVA_HOME=/usr/local/src/jdk-11.0.21" >> /etc/profile
echo "export PATH=$PATH:$JAVA_HOME/bin" >> /etc/profile
source /etc/profile
echo `java -version`
echo "JDK 11安装完成"
​
# # 下载并安装 Maven
echo "开始下载并安装 Maven..."
MAVEN_URL="https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz"  # 替换为实际的Maven下载地址
cd /usr/local/src
wget $MAVEN_URL
tar -xzf apache-maven-3.8.8-bin.tar.gz
mv apache-maven-3.8.8 /usr/local/src/
echo "Maven安装完成"
​
# 设置环境变量
echo "export MAVEN_HOME=/usr/local/src/apache-maven-3.8.8" >> /etc/profile
echo "export PATH=$PATH:$MAVEN_HOME/bin" >> /etc/profile
source /etc/profile
echo `mvn -version`
​
echo "JDK 11和Maven安装完成"

2.2 安装Jenkins

参考文档mfg6vszyp7.feishu.cn/wiki/XiHswm...

目前在提供的虚拟机中已经使用docker安装了jenkins,启动命令:docker start jenkins

访问地址:http://192.168.100.168:8000/

登录后的效果:

三. 部署后端项目

3.1 多环境说明

在项目开发部署的过程中,一般都会有三套项目环境

  • Development :开发环境
  • Production :生产环境
  • Test :测试环境

例如:开发环境的mysql连接的是本地,生产环境需要连接线上的mysql环境

3.1.1 汇总配置文件

操作步骤:

  1. application.ymlapplication-druid.yml所有内容合并成一个新的配置文件:application-dev.yml
  2. 将汇总后的配置文件中spring.profiles.active=druid这个配置项删除掉

最终的application-dev.yaml内容如下:

yaml 复制代码
# 项目相关配置
ruoyi:
  # 名称
  name: RuoYi
  # 版本
  version: 3.8.8
  # 版权年份
  copyrightYear: 2024
  # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: D:/ruoyi/uploadPath
  # 获取ip地址开关
  addressEnabled: false
  # 验证码类型 math 数字计算 char 字符验证
  captchaType: math
​
# 开发环境配置
server:
  # 服务器的HTTP端口,默认为8080
  port: 8080
  servlet:
    # 应用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
    # 连接数满后的排队数,默认为100
    accept-count: 1000
    threads:
      # tomcat最大线程数,默认为200
      max: 800
      # Tomcat启动初始化的线程数,默认值10
      min-spare: 100
​
# 日志配置
logging:
  level:
    com.zzyl: debug
    org.springframework: warn
​
# 用户配置
user:
  password:
    # 密码最大错误次数
    maxRetryCount: 5
    # 密码锁定时间(默认10分钟)
    lockTime: 10
​
# Spring配置
spring:
  # 资源信息
  messages:
    # 国际化资源文件路径
    basename: i18n/messages
  # 文件上传
  servlet:
    multipart:
      # 单个文件大小
      max-file-size: 10MB
      # 设置总上传的文件大小
      max-request-size: 20MB
  # 服务模块
  devtools:
    restart:
      # 热部署开关
      enabled: true
  # redis 配置
  redis:
    # 地址
    host: 192.168.100.168
    # 端口,默认为6379
    port: 6379
    # 数据库索引
    database: 0
    # 密码
    password: 123456
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 主库数据源
      master:
        url: jdbc:mysql://192.168.100.168:3306/zzyl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: heima123
      # 从库数据源
      slave:
        # 从数据源开关/默认关闭
        enabled: false
        url:
        username:
        password:
      # 初始连接数
      initialSize: 5
      # 最小连接池数量
      minIdle: 10
      # 最大连接池数量
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置连接超时时间
      connectTimeout: 30000
      # 配置网络超时时间
      socketTimeout: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # 配置检测连接是否有效
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # 设置白名单,不填则允许所有访问
        allow:
        url-pattern: /druid/*
        # 控制台管理用户名和密码
        login-username: ruoyi
        login-password: 123456
      filter:
        stat:
          enabled: true
          # 慢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
​
# token配置
token:
  # 令牌自定义标识
  header: Authorization
  # 令牌密钥
  secret: abcdefghijklmnopqrstuvwxyz
  # 令牌有效期(默认30分钟)
  expireTime: 30
​
# MyBatisPlus配置
mybatis-plus:
  # 搜索指定包别名
  typeAliasesPackage: com.zzyl.**.domain
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # 全局配置
  global-config:
    db-config:
      id-type: auto   #id生成策略为自增
  configuration:
    map-underscore-to-camel-case: true    #字段与属性,自动转换为驼峰命名
​
# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  supportMethodsArguments: true
  params: count=countSql
​
# Swagger配置
swagger:
  # 是否开启swagger
  enabled: true
  # 请求前缀
  pathMapping: /dev-api
​
# 防止XSS攻击
xss:
  # 过滤开关
  enabled: true
  # 排除链接(多个用逗号分隔)
  excludes: /system/notice
  # 匹配链接
  urlPatterns: /system/*,/monitor/*,/tool/*
aliyun:
  oss:
    endpoint: https://oss-cn-beijing.aliyuncs.com
    bucketName: itheima-liuyp
baidu:
  accessKey: ALTAKEtNuKDmzxnkR7umWk1U4y
  secretKey: 2e475c56f878413c81e85aac94cb8fb6
  qianfanModel: ERNIE-4.0-8K-Preview

3.1.2 准备多环境配置文件

将刚刚的application-dev.yml再复制2份,分别命名为:application-test.ymlapplication-prod.yml

最终有3个环境的配置文件,分别是:

  • application-dev.yml 开发环境
  • application-test.yml 测试环境
  • application-prod.yml 正式环境(为了能演示出不同环境配置生效了,我们把prod环境的配置文件中端口改为9000

再创建一个application.yml作为默认配置文件,在配置文件中设置激活哪套环境:

yaml 复制代码
spring:
  profiles:
    active: prod  #prod是环境标识,表示要激活配置文件application-prod.yml

3.1.2 测试

直接在idea里运行启动类RuoYiApplication,查看能否正常启动,且日志中显示端口为9000

3.2 准备项目代码

3.2.1 Jenkins流水线介绍

Jenkins 流水线配置是 Jenkins 中用于定义和执行自动化构建、测试和部署过程的一种方式

在声明式流水线中,整个流水线过程被定义在一个 pipeline 块中,该块包含了流水线执行所需的所有指令和阶段。

声明式流水线的Jenkinsfile文件(Groovy脚本)基本结构如下:

typescript 复制代码
pipeline {  
    agent any // 指定流水线运行的节点,any 表示任何可用的节点  
    stages {  
        stage('Stage Name1') { // 定义阶段  
            steps {  
                // 定义在该阶段执行的步骤  
                echo 'Hello, World!'  
            }  
        },
        stage('Stage Name2') { // 定义阶段  
            steps {
                // 定义在该阶段执行的步骤  
                echo 'Hello, World!'  
            }
        }
    }
}

主要指令和阶段:

  • agent:指定流水线或特定阶段在哪个节点上执行。
  • stages:包含流水线中的所有阶段(stage),阶段是流水线的主要分组单元。
  • stage:定义一个阶段,阶段内可以包含一个或多个步骤。
  • steps:定义在某个阶段内执行的步骤,步骤是构建过程中的具体操作。
  • post:定义在所有阶段完成后执行的操作,可以基于不同的条件(如成功、失败、总是)来执行。
  • environment:定义流水线中的环境变量。
  • options:定义全局选项和配置,如超时设置、并行执行等。
  • parameters:定义流水线的参数,用于接收用户输入。
  • triggers:定义触发流水线执行的条件或事件,如定时触发、代码推送触发等。

3.2.2 准备Jenkinsfile 文件

新建 Jenkinsfile 文件并上传到代码仓库中, 在Jenkins中完成流水线配置

将 Jenkinsfile 文件上传到Git做为共享配置使用,存储到**项目根目录**,命名必须是:Jenkinsfile

文件内容如下:

如果gitee仓库非开源,则需要在脚本中准备gitee的账号密码。

  • stage('拉取Git代码')checkout中,找到userRemoteConfigs,添加credentialsId: 'Gitee_ID'
  • 在后续Jenkins中新增任务时,要为gitee指定账号密码,并设置id为Gitee_ID
bash 复制代码
pipeline {
    agent any
    options {
        timestamps()
    }
    tools {
        maven 'maven'
        jdk 'jdk11'
    }
    stages {
        stage('清除工作空间') {
            steps {
                cleanWs()
            }
        }
        stage('拉取Git代码') {
            steps {
                echo "正在拉取代码..."
                echo "当前分支:${GIT_TAG},当前服务:${services}"
                checkout([$class: 'GitSCM',
                          branches: [[name: GIT_TAG]],
                          doGenerateSubmoduleConfigurations: false,
                          extensions: [],
                          submoduleCfg: [],
                          userRemoteConfigs: [[ url: GIT_URL]]
                ])
                sh "pwd"
            }
        }
        stage('重新Maven打包') {
            steps {
                script {
                    echo "正在执行maven打包...."
                    sh "mvn clean install -DskipTests"
                }
            }
        }
        stage('存在容器处理'){
          steps {
             script {
                for (ws in services.tokenize(",")) {
                   def containerExists = sh(script: "docker ps -a --filter 'name=${ws}' --format '{{.Names}}' | grep -w '${ws}'", returnStatus: true) == 0
                   if (containerExists) {
                      echo "容器${ws}存在"
                      sh "docker rm -f ${ws}"
                      echo "容器${ws}已删除"
                   } else {
                      echo "容器${ws}不存在"
                   }
                }
             }
          }
       }
        stage('重新构建镜像') {
            steps {
                echo "当前打镜像tag:${DOCKER_TAG}"
                script {
                    for (ds in services.tokenize(",")) {
                        def imageExists = sh(script: "docker images ${ds}:${DOCKER_TAG} --format '{{.ID}}' | grep -q .", returnStatus: true) == 0
                        if (imageExists) {
                            echo "镜像${ds}:${DOCKER_TAG}存在"
                            def imageId = sh(script: "docker images ${ds}:${DOCKER_TAG} --format '{{.ID}}'", returnStdout: true).trim()
                            echo "获取镜像ID:${imageId}"
                            sh "docker rmi -f ${imageId}"
                            echo "镜像${ds}:${DOCKER_TAG}已删除"
                        } else {
                            echo "镜像${ds}:${DOCKER_TAG}不存在"
                        }
                        sh "pwd"
                        echo "进入target目录执行镜像打包......"
                        sh "cd ./${ds}/target/ && docker build -t ${ds}:${DOCKER_TAG} -f ../Dockerfile ."
                    }
                }
            }
        }
        stage('部署服务'){
            steps {
                script {
                    for (ws in services.tokenize(",")) {
                        sh "pwd"
                        sh "cd `pwd`"
                        echo "部署升级:${ws}服务"
                        sh "chmod +x ./${ws}/deploy.sh && sh ./${ws}/deploy.sh ${ws} ${DOCKER_TAG}"
                    }
                }
            }
        }
​
    }
    post {
        always {
            echo '任务构建完毕'
        }
    }
}

3.2.3 项目中准备文件

在部署的项目模块(zzyl-admin)目录下 新增两个文件:

其中Dockerfile内容:

bash 复制代码
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# OSS配置
ENV OSS_ACCESS_KEY_ID LTAI5tN2ueyxxWtbEvJD949T   
ENV OSS_ACCESS_KEY_SECRET dbbXOf1Mj8YwtevxvO1pOMmTflfzpA 
# 拷贝jar包
COPY zzyl-admin.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]

deploy.sh文件内容:

bash 复制代码
#!/bin/bash
# 容器名称
container_name=$1
# 镜像名称
image_name=$1
# 镜像tag
image_tag=$2
​
# 判断容器是否存在
if docker ps -a | grep $container_name | awk '{print $1}'; then
  echo "容器 $container_name 存在"
  if docker ps | grep $container_name | awk '{print $1}';then
     echo "关闭正在运行的容器 $container_name"
     docker stop `docker ps | grep $container_name | awk '{print $1}'`
  else
    echo "容器 $container_name 都已关闭"
  fi
  # 删除容器
  echo "删除容器 $container_name"
  docker rm `docker ps -a | grep $container_name | awk '{print $1}'`
else
  echo "容器 $container_name 不存在"
fi
​
# 启动容器
echo "启动容器 $container_name"
if [ $container_name = "zzyl-admin" ]; then
    docker run -d --restart=always --name $container_name -v /usr/local/zzyl-admin/logs:/home/ruoyi/logs -p 9000:9000 $image_name:$image_tag
fi

最终的代码结构

3.2.4 代码合并

务必要将代码提交并合并到master分支名,然后推送到远程仓库

3.4 准备Jenkins任务

3.4.1 新建任务

3.4.2 输入任务描述

3.4.3 参数化配置

准备参数services

定义服务列表:services,多个服务建议用 "," 隔开

准备参数GIT_URL

定义参数:GIT_URL

准备参数GIT_TAG

定义代码分支参数:GIT_TAG

准备参数Docker_TAG

设置Docker打包镜像版本 Docker_TAG:

3.4.4 流水线的配置

从代码仓库中读取Jenkinsfile文件

点击应用和保存,基础设置完成,最终效果可以查看首页:

点击 zzyl-admin 进入任务详情查看:

3.5 启动部署任务

3.5.1 部署项目

打开jenkins,找到刚才创建的项目,选择参数化构建,选择需要配置,如下图,点击Build开始构建

3.5.2 查看日志

3.5.3 查看docker容器

当项目部署成功之后,可以在docker中查看是否启动了容器:

3.6 前后端测试

我们可以在本地的前端代码中,修改访问后端的地址,来验证服务是否可以正常访问。

注意:

  • 因为后端项目我们使用Jenkins部署到了服务器上,并且设置了生产环境激活application-prod.yml文件,其中配置的端口是9000
  • 所以前端向后端发请求时,配置的后端地址中,ip是服务器ip,端口是9000

如果前端项目启动后,可以正常访问,并且可以获取数据,则表示成功

四. 部署前端项目

前端项目我们可以采用nginx进行部署,需要使用nginx中的两个特性,分别是静态服务器反向代理

  • 首先我们把目前的vue项目进行打包,打包成静态资源
  • 使用docker创建nginx容器
  • 部署静态资源(前端打包之后的静态资源)
  • 配置反向代理服务访问

4.1 多环境打包

在前端项目中也有多环境配置,如下面的三个文件,不同的文件对应了不同的环境配置项

  • development 开发环境
  • production 正式环境
  • staging 预发布环境

指定环境打包,打包命令:npm run build:prod (选择了正式环境)

  • 打包成功之后,会在项目的根目录下生成一个文件夹 dist
  • dist目录中存储的就是打包之后的静态文件,会压缩,存储较小,部署使用这里面内容

4.2 创建nginx容器

  1. 准备目录,创建容器需要做数据卷挂载

    bash 复制代码
    mkdir -p /usr/local/zzyl-vue/html
    mkdir -p /usr/local/zzyl-vue/conf
  2. 创建并启动容器:

    bash 复制代码
    docker run -d \
    --name zzyl-vue \
    -v /usr/local/zzyl-vue/html:/usr/share/nginx/html \
    -v /usr/local/zzyl-vue/conf:/etc/nginx/conf.d \
    -v /usr/local/zzyl-vue/logs:/var/log/nginx \
    -p 80:80 \
    nginx:latest

4.3 部署前端

  1. 把前端项目打包之后的文件上传到服务器/usr/local/zzyl-vue/html目录中,注意:要连带dist目录一起上传,最终效果如下:
  1. 配置静态部署和反向代理

    将当天资料中的zzyl-vue.conf文件上传到Linux的/usr/local/zzyl-vue/conf目录下。内容如下:

    bash 复制代码
    # 配置代理服务  serve的地址为:中州养老后台管理服务ip+端口
    upstream  heima-admin{
        server 192.168.100.168:9000;
    }
    ​
    server {
        listen 80;
        # 访问静态服务
        location / {
            # 前端静态文件的目录
            root /usr/share/nginx/html/dist/;
            index index.html;
        }
        # 访问后端服务接口  注意目前是prod-api(正式环境)
        location /prod-api/ {
            proxy_pass http://heima-admin/; # 访问代理服务
            proxy_set_header HOST $host;  # 不改变源请求头的值
            proxy_pass_request_body on;  #开启获取请求体
            proxy_pass_request_headers on;  #开启获取请求头
            proxy_set_header X-Real-IP $remote_addr;   # 记录真实发出请求的客户端IP
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #记录代理信息
        }
    }

配置完成后,重启nginx容器 docker restart zzyl-vue,打开浏览器访问http://192.168.100.168:80 ,没有问题则证明部署成功

⚡️当前端项目与后端项目的部署过程,全程测试完成以后,可以关闭虚拟机里的Jenkins、zzyl-admin后端服务容器、zzyl-vue前端服务容器

至此,从后端多环境配置、Jenkins 流水线构建,到前端打包部署、Nginx 反向代理,我们在单台虚拟机内完成了企业级项目部署的核心环节。无论是清理工作空间、构建 Docker 镜像的自动化步骤,还是静态资源挂载、接口代理的细节配置,每一步都为你铺就了从学习到实战的桥梁。动手实践这些步骤,你不仅能收获可运行的前后端服务,更能掌握部署领域的核心工具与思维,为应对更复杂的生产环境打下坚实基础。

觉得有用的话,点赞收藏就是对硬核干货最好的认可~谢谢啦~

相关推荐
聆风吟º4 小时前
CANN开源项目深度实践:基于amct-toolkit实现自动化模型量化与精度保障策略
运维·开源·自动化·cann
侠客行03175 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪5 小时前
深入浅出LangChain4J
java·langchain·llm
Victor3565 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor3565 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
较劲男子汉7 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
灰子学技术7 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
老毛肚7 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎7 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
Doro再努力7 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim