手把手教你全流程项目部署:从 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 镜像的自动化步骤,还是静态资源挂载、接口代理的细节配置,每一步都为你铺就了从学习到实战的桥梁。动手实践这些步骤,你不仅能收获可运行的前后端服务,更能掌握部署领域的核心工具与思维,为应对更复杂的生产环境打下坚实基础。

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

相关推荐
苦学编程的谢1 分钟前
Linux
linux·运维·服务器
用户4822137167753 分钟前
C++——纯虚函数、抽象类
后端
G_H_S_3_8 分钟前
【网络运维】Linux 文本处理利器:sed 命令
linux·运维·网络·操作文本
张同学的IT技术日记13 分钟前
必看!用示例代码学 C++ 基础入门,快速掌握基础知识,高效提升编程能力
后端
UserNamezhangxi21 分钟前
kotlin 协程笔记
java·笔记·kotlin·协程
林太白22 分钟前
Nuxt3 功能篇
前端·javascript·后端
咖啡里的茶i31 分钟前
数字化图书管理系统设计实践(java)
java·课程设计
拾心2136 分钟前
【运维进阶】Linux 正则表达式
linux·运维·正则表达式
得物技术1 小时前
营销会场预览直通车实践|得物技术
后端·架构·测试
九转苍翎1 小时前
Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify
java·thread