项目自动化部署-Jenkins
项目部署
整体部署思路

在企业正常的项目部署中,可能会涉及多台服务器共同使用,如上图中的Nginx、Tomcat、Redis、MySQL都会有对应的单独的服务器,让各个服务器进行互相通信即可完成部署
- 其中的微信小程序必须租腾讯的服务器才能完成部署
- 在学习阶段,所有的服务都使用同一个虚拟机来完成项目的部署
环境准备
软件环境:
| 软件 | 版本 | 安装方式 |
|---|---|---|
| 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 |
shell脚本:
#!/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安装完成"
Jenkins介绍和安装
参考文档:Jenkins(扩展文档)
资料提供的虚拟机中已经使用docker安装了jenkins,启动命令:docker start jenkins

访问地址:http://虚拟机ip地址:8000/

用户名:zzyl 密码:itcast
登录后的效果:

部署后端项目
多环境说明
在项目开发部署的过程中,一般都会有三套项目环境
- Development :开发环境
- Production :生产环境
- Test :测试环境
例如:开发环境连接的是本地安装的MySQL,生产环境需要连接线上的MySQL环境
- 合并配置文件(application.yml和application-druid.yml),合并之后的内容如下: - 把application-druid.yml拷贝到application.yml中,注意格式,application-druid.yml的配置都在spring的配置下
# 项目相关配置
ruoyi:
# 名称
name: RuoYi
# 版本
version: 3.9.0
# 版权年份
copyrightYear: 2025
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
profile: E:/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:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://192.168.2.17: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
# 资源信息
messages:
# 国际化资源文件路径
basename: i18n/messages
# profiles:
# active: druid
# 文件上传
servlet:
multipart:
# 单个文件大小
max-file-size: 10MB
# 设置总上传的文件大小
max-request-size: 20MB
# 服务模块
devtools:
restart:
# 热部署开关
enabled: true
# redis 配置
redis:
# 地址
host: 192.168.2.17
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: 123456
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# token配置
token:
# 令牌自定义标识
header: Authorization
# 令牌密钥
secret: abcdefghijklmnopqrstuvwxyz
# 令牌有效期(默认30分钟)
expireTime: 1800
# 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/*
# 阿里云OSS
aliyun:
oss:
endpoint: https://oss-cn-shanghai.aliyuncs.com
bucketName: norlcyan-javaweb-ai
region: cn-shanghai
# 百度千帆API
baidu:
qianfan:
apiKey: bce-v3/ALTAK-tBQEfdmxk9589E4u5kfEO/7f113d2b37f73ebc98a0e7afbfc8037ddadba43b
baseUrl: https://qianfan.baidubce.com/v2/
model: ernie-4.0-8k
打开后端项目,基于原来的application.yml文件再再拷贝三份配置文件,命名如下:
- application-dev.yml 开发环境
- application-test.yml 测试环境
- application-prod.yml 正式环境 为了区分不同的环境生效,把prod的服务端口改为
9000
修改application.yml文件内容,在这里只留一个开关,需要激活哪套环境:
spring:
profiles:
active: prod
什么是流水线
Jenkins 流水线配置是 Jenkins 中用于定义和执行自动化构建、测试和部署过程的一种方式

在声明式流水线中,整个流水线过程被定义在一个 pipeline 块中,该块包含了流水线执行所需的所有指令和阶段
- 基本结构
声明式流水线的基本结构如下:
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:定义触发流水线执行的条件或事件,如定时触发、代码推送触发等
Jenkins 流水线配置
新建任务
任务名称:zzyl-admin(根据引导类的项目名)

输入项目名称

参数化配置

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

定义参数:GIT_URL

定义代码分支参数:GIT_TAG

设置代码拉取的密码:

设置登录凭证:

选择设置的密码和参数类型

设置Docker打包镜像版本 Docker_TAG:

流水线的配置,从代码仓库中读取Jenkinsfile文件

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

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

新建 Jenkinsfile 文件并上传到代码仓库中, 在Jenkins中完成流水线配置
将 Jenkinsfile 文件上传到Git做为共享配置使用,存储到项目**根目录**,命名必须是:Jenkinsfile

文件内容如下:
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: [[credentialsId: 'Gitee_ID', url: GIT_URL]]
])
sh "pwd"
}
}
stage('重新Maven打包') {
steps {
script {
echo "正在执行maven打包...."
sh "mvn clean install -DskipTests"
}
}
}
stage('重新构建镜像') {
steps {
echo "当前打镜像tag:${DOCKER_TAG}"
script {
for (ds in services.tokenize(",")) {
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 '任务构建完毕'
}
}
}
- 注意,其中的
Gitee_ID要与jenkins中配置的gitee登录凭证ID一致

在部署的项目模块(zzyl-admin)目录下 新增两个文件
- Dockerfile
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV OSS_ACCESS_KEY_ID ....(自己的ACCESS_KEY_ID)
ENV OSS_ACCESS_KEY_SECRET ....(自己的ACCESS_KEY_SECRET)
# 拷贝jar包
COPY zzyl-admin.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
一定要把OSS_ACCESS_KEY_ID设置为自己的
- deploy.sh
#!/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
最终的代码结构

所有配置新增之后和修改之后,需要把代码提交,并且**推送到远程仓库**
代码合并
将代码从dev*开头的分支中,合并到master分支中
- 切换分支到master分支
- 注意:切换分支之前,必须执行commit操作提交代码到本地仓库,防止代码丢失
项目部署
打开jenkins,找到刚才创建的项目,选择参数化构建,选择需要配置,如下图,点击Build开始构建

查看日志

查看docker容器
当项目部署成功之后,可以在docker中查看是否创建了对应的镜像和容器

也可以查看正在运行的容器

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

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

OSS_ACCESS_KEY的安全传递方式(拓展内容)
在Jenkins流水线配置中,直接将OSS_ACCESS_KEY以明文的方式写死在了Dockerfile中,并且还将其上传到了公网Git仓库中,这是及其危险的行为
解决的方式:以Jenkins凭据的形式保存在Jenkins服务中,动态的获取到OSS_ACCESS_KEY
- 添加凭据:
- 访问地址:http://ip:port/manage/credentials/store/system/domain/_/
- 点击
Add Credentials,进入添加凭据参数页面 - 类型选择Secret Text,Secret这一栏填写AccessKey ID- ID这一栏叫OSS_ACCESS_KEY_ID即可,其他内容也行,不过后续要使用到,不能填写错误 - 点击create创建完毕,随后再创建对应的AccessKey Secret,步骤和上面相同 - 修改项目根目录下的Jenkinsfile:
stage('部署服务'){
steps {
script {
withCredentials([
string(credentialsId: 'OSS_ACCESS_KEY_ID', variable: 'OSS_KEY_ID'),
string(credentialsId: 'OSS_ACCESS_KEY_SECRET', variable: 'OSS_KEY_SECRET')
]) {
for (ws in services.tokenize(",")) {
sh "pwd"
sh "cd `pwd`"
echo "部署升级:${ws}服务"
sh "OSS_KEY_ID=${OSS_KEY_ID} OSS_KEY_SECRET=${OSS_KEY_SECRET} chmod +x ./${ws}/deploy.sh && sh ./${ws}/deploy.sh ${ws} ${DOCKER_TAG}"
}
}
}
}
}
withCredentials中的credentialsId就是之前ID栏填写的内容,用于获取到Secret栏的值,并保存到variable对应的参数中
- 修改Dockerfile文件,只需要把设置环境变量
OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_ID这两栏去掉即可 - 修改deploy.sh:
#!/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 \
-e OSS_ACCESS_KEY_ID=$OSS_KEY_ID \
-e OSS_ACCESS_KEY_SECRET=$OSS_KEY_SECRET \
-v /usr/local/zzyl-admin/logs:/home/ruoyi/logs \
-p 9000:9000 $image_name:$image_tag
fi
在启动容器时以参数的形式设置环境变量,
$OSS_KEY_ID和$OSS_KEY_SECRET就是通过刚才修改的Jenkinsfile中获取的
部署前端项目
前端项目可以采用nginx进行部署,需要使用nginx中的两个特性,分别是静态服务器和反向代理
如下图:

- 首先把目前的vue项目进行打包,打包成静态资源
- 使用docker创建nginx容器
- 部署静态资源(前端打包之后的静态资源)
- 配置反向代理服务访问
多环境选择打包
在前端项目中也有多环境配置,如下面的三个文件,不同的文件对应了不同的环境配置项

- development 开发环境
- production 正式环境
- staging 预发布环境
指定环境打包,打包命令:npm run build:prod (选择了正式环境)
- 打包成功之后,会在项目的根目录下生成一个文件夹
dist - dist目录中存储的就是打包之后的静态文件,会压缩,存储较小,部署使用这里面内容

创建nginx容器
在目前提供的服务器中已经提供了nginx的镜像,可以直接使用这个镜像制作nginx容器。命令如下:
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
部署前端和配置反向代理
- 把前端项目打包之后的文件上传到服务器
/usr/local/zzyl-vue/html目录中,注意:要连带dist目录一起上传,最终效果如下:

- 配置反向代理
在/usr/local/zzyl-vue/conf 目录下创建一个文件:zzyl-vue.conf
可以直接使用资料中提供的文件:

详细内容如下:
# 配置代理服务 serve的地址为:中州养老后台管理服务ip+端口
upstream heima-admin{
server ip:port;
}
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,效果:
