项目部署
整体部署思路
在企业正常的项目部署中,可能会涉及多台服务器共同使用,如上图中的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脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #!/bin/bash
echo "开始安装 Git..." yum install git -y echo "Git安装完成" echo `git --version`
echo "开始下载并安装 JDK 11..."
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安装完成"
echo "开始下载并安装 Maven..." MAVEN_URL="https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz" 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的配置下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
| ruoyi: name: RuoYi version: 3.9.0 copyrightYear: 2025 profile: E:/ruoyi/uploadPath addressEnabled: false captchaType: math
server: port: 8080 servlet: context-path: / tomcat: uri-encoding: UTF-8 accept-count: 1000 threads: max: 800 min-spare: 100
logging: level: com.zzyl: debug org.springframework: warn
user: password: maxRetryCount: 5 lockTime: 10
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 log-slow-sql: true slow-sql-millis: 1000 merge-sql: true wall: config: multi-statement-allow: true messages: basename: i18n/messages
servlet: multipart: max-file-size: 10MB max-request-size: 20MB devtools: restart: enabled: true redis: host: 192.168.2.17 port: 6379 database: 0 password: 123456 timeout: 10s lettuce: pool: min-idle: 0 max-idle: 8 max-active: 8 max-wait: -1ms
token: header: Authorization secret: abcdefghijklmnopqrstuvwxyz expireTime: 1800
mybatis-plus: typeAliasesPackage: com.zzyl.**.domain mapperLocations: classpath*:mapper/**/*Mapper.xml global-config: db-config: id-type: auto configuration: map-underscore-to-camel-case: true
pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql
swagger: enabled: true pathMapping: /dev-api
xss: enabled: true excludes: /system/notice urlPatterns: /system/*,/monitor/*,/tool/*
aliyun: oss: endpoint: https://oss-cn-shanghai.aliyuncs.com bucketName: norlcyan-javaweb-ai region: cn-shanghai
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文件内容,在这里只留一个开关,需要激活哪套环境:
1 2 3
| spring: profiles: active: prod
|
什么是流水线
Jenkins 流水线配置是 Jenkins 中用于定义和执行自动化构建、测试和部署过程的一种方式

在声明式流水线中,整个流水线过程被定义在一个 pipeline 块中,该块包含了流水线执行所需的所有指令和阶段
- 基本结构
声明式流水线的基本结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| pipeline { agent 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

文件内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| 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)目录下 新增两个文件
1 2 3 4 5 6 7 8 9 10 11
| 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)
COPY zzyl-admin.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
|
一定要把OSS_ACCESS_KEY_ID设置为自己的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #!/bin/bash
container_name=$1
image_name=$1
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
- 添加凭据:
- 修改项目根目录下的Jenkinsfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #!/bin/bash
container_name=$1
image_name=$1
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容器。命令如下:
1 2 3 4 5 6 7
| 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
可以直接使用资料中提供的文件:

详细内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| upstream heima-admin{ server ip:port; }
server { listen 80; location / { root /usr/share/nginx/html/dist/; index index.html; } 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
|
配置完成后,重启nginx容器 docker restart zzyl-vue,效果:
