10-实战:RuoYi-Cloud的自动化发布

一、项目准备

1.微服务拆分

  • Ruoyi-Cloud在设计上有7个微服务,分别负责不同功能,以下信息将在后续配置用到
模块 微服务 代码根目录 dockerfile路径
网关模块 RuoYi-Gateway ruoyi-gateway/ docker/ruoyi/gateway
认证模块 RuoYi-Auth ruoyi-auth/ docker/ruoyi/auth
系统模块 RuoYi-System ruoyi-modules/ruoyi-system/ docker/ruoyi/modules/system
代码生成 RuoYi-Gen ruoyi-modules/ruoyi-gen docker/ruoyi/modules/gen
定时任务 RuoYi-Job ruoyi-modules/ruoyi-job docker/ruoyi/modules/job
文件服务 RuoYi-File ruoyi-modules/ruoyi-file docker/ruoyi/modules/file
监控中心 RuoYi-Monitor ruoyi-visual/ruoyi-monitor/ docker/ruoyi/visual/monitor
  • 针对这些微服务,我们需要创建7条Jenkins多分支流水线来分别对这些微服务进行CICD

2.数据库文件导入

  • 在若依根目录sql/路径中,有预设的数据库脚本,需要创建对应数据库导入

  • 在数据库服务器10.0.0.138中,将sql/路径下的脚本全部传输到服务器,进入数据库

bash 复制代码
create database `ry-cloud`;
create database `ry-config`;
# 编写导入脚本
vim ry-data.sh
------------------------------------------------------------------------
echo $(date)
mysql -uroot -pPangle@123 -f ry-cloud < ry_20250523.sql
echo $(date)
​
echo $(date)
mysql -uroot -pPangle@123 -f ry-cloud < quartz.sql
echo $(date)
​
echo $(date)
mysql -uroot -pPangle@123 -f ry-config < ry_config_20260311.sql
echo $(date)
------------------------------------------------------------------------
​
# 执行脚本
nohup sh ry-data.sh > ry-data.log &

3.nacos安装

  • 我们的操作基于master分支,而该分支代码已升级至SpringBoot4 对应nacos版本为3.X.X

  • 10.0.0.140服务器上部署nacos,前往nacos官网下载压缩版:https://nacos.io/

bash 复制代码
# 下载解压
cd /usr/local
wget https://download.nacos.io/nacos-server/nacos-server-3.1.1.zip
unzip nacos-server-3.1.1.zip
​
cd nacos/
# 配置文件编辑数据库路径
vim conf/application.properties
------------------------------------------------------------------------
spring.sql.init.platform=mysql
db.num=1
db.url.0=jdbc:mysql://{数据库服务器ip}:3306/ry-config?characterEncoding=utf8&connectTimeout=5000&socketTimeout=10000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
db.user=root
db.password={root用户密码}
------------------------------------------------------------------------
​
# 单机模式启动
sh bin/startup.sh -m standalone

4.配置地址

- 配置微服务注册地址

  • 若依微服务依赖nacos进行服务注册和读取配置,首先要为每个微服务配置nacos地址

  • 在每个微服务代码根目录src/main/resources/bootstrap.yml 文件中修改:

  • 修改每个配置文件中,每个server-addr: 后的内容,将127.0.0.1改为nacos服务器ip10.0.0.140

- nacos配置数据库地址

  • nocos将从数据库的ry-config库读取配置文件

  • 登录nacos主页 > 配置管理 > 配置列表

    • 修改 ruoyi-gateway-dev.ymlruoyi-auth-dev.ymlruoyi-system-dev.ymlruoyi-gen-dev.ymlruoyi-job-dev.yml

    • redis的配置地址 host: {数据库服务器ip}

    • datasource中的 url: 和 password: 设置成自己数据库相关配置

- 配置后端API地址

  • 前端将部署到10.0.0.135,后端将以微服务容器部署到10.0.0.137

  • 因此在前端代码文件需要配置后端服务器地址和访问端口

  • 在前端目录ruoyi-ui中,编辑配置文件vue.config.js

    • 找到target: http://127.0.0.0:8080,将其改为自定义的target: http://10.0.0.137:8080

二、编写配置文件

1.Jenkinsfile

  • 在对项目完成准备工作后,将其上传到Gitlab仓库Ruoyi-Cloud,然后新建分支prod-peizhi,将其作为生产Jenkins需要发布的分支

  • prod-peizhi分支中,在每个微服务代码根目录新建文件Jenkinsfile并编辑内容:

    • 针对不同微服务,只需要配置SERVICE_TYPEBASE_NAMEHOST_PORTCONTAINER_PORT就可复用该Jenkinsfile

    SERVICE_TYPE BASE_NAME HOST_PORT & CONTAINER_PORT
    basic gateway 8080
    basic auth 9200
    modules system 9201
    modules gen 9202
    modules job 9203
    modules file 9300
    visual monitor 9100
    • 在Jenkins全局配置中,添加前后端目标服务器信息:命名为若依微服务-前端若依微服务-后端,供Jenkinsfile调用
Groovy 复制代码
pipeline {
    agent any
    tools {
        // 全局工具已配置
        maven 'Maven-3.8.9'
        jdk 'JDK-17'
    }
    environment {
        // 不同微服务需修改此处:gateway/auth为 basic、job/file/gen/system为 modules、monitor为 visual
        SERVICE_TYPE = "basic"
        BASE_NAME = "gateway" // 基础服务名称
        HOST_PORT=8080  // 宿主机端口
        CONTAINER_PORT=8080 // 容器内端口
​
        // --------------------------------------------------------以下信息自动拼接----
        // 根据微服务类型和基础名称动态生成应用名称,基于RuoYi的项目结构
        // APPNAME 根据生成的jar包名,将作为镜像名称
        APPNAME = "ruoyi-${SERVICE_TYPE == 'basic' ? BASE_NAME : (SERVICE_TYPE == 'modules' ? "modules-${BASE_NAME}" : "visual-${BASE_NAME}")}"
        APP_DIR = "${SERVICE_TYPE == 'basic' ? "./ruoyi-${BASE_NAME}" : (SERVICE_TYPE == 'modules' ? "./ruoyi-modules/ruoyi-${BASE_NAME}" : "./ruoyi-visual/ruoyi-${BASE_NAME}")}"
        // 拼接jar包完整路径,供Dockerfile使用
        JAR_PATH = "${APP_DIR}/target/${APPNAME}.jar"
​
        // Dockerfile路径,根据微服务类型动态选择
        DOCKERFILE_DIR = "${SERVICE_TYPE == 'basic' ? "./docker/ruoyi/${BASE_NAME}" : (SERVICE_TYPE == 'modules' ? "./docker/ruoyi/modules/${BASE_NAME}" : "./docker/ruoyi/visual/${BASE_NAME}")}"
​
        // --------------------------------------------------------以下信息无需修改----
        VERSION="V1.0.${env.BUILD_NUMBER}"
        // 拼接为符合Harbor镜像规范的完整镜像名
        HARBOR_URL="121.4.90.98:80"
        IMAGE_NAME="${HARBOR_URL}/ruoyi/${APPNAME}:${VERSION}"
        // Harbor用户名密码
        HARBOR_USER="admin"
        HARBOR_PASS="Harbor12345"
        // 前后端服务器地址,在Jenkins系统配置中添加,命名
        NGINX_SERVER="若依微服务-前端"
        APP_SERVER="若依微服务-后端"
    }
    options {
        disableConcurrentBuilds()   // 禁止并行构建
        timestamps()    // 日志添加时间戳
    }
    stages {
        stage('Npm打包前端') {
            when {
                // 仅微服务名为ruoyi-gateway的流水线执行
                expression { return env.APPNAME == 'ruoyi-gateway' }
            }
            steps {
                script {
                    dir('ruoyi-ui') {
                        sh """
                            rm -rf dist*.zip
                            npm install --registry=https://registry.npmmirror.com
                            npm run build:prod
                            zip -r dist-${VERSION}.zip ./dist/*
                        """
                    }
                }
            }
        }
        stage('Maven打包后端') {
            steps {
                script {
                    sh """
                    # 将jar包构建后复制到Docker构建目录,供Dockerfile使用
                    mvn clean package -Dmaven.test.skip=true -pl ${APP_DIR} -am -f ./pom.xml
                    cp ${JAR_PATH} ${DOCKERFILE_DIR}/jar
                    """
                }
            }
        }
        stage('本地构建镜像') {
            steps {
                script {
                    sh """
                        cd ${DOCKERFILE_DIR}
                        docker build -t ${APPNAME}:${VERSION} .
                    """
                }
            }
        }
        stage('镜像推送Harbor') {
            steps {
                script {
                    sh """
                        docker login ${HARBOR_URL} -u ${HARBOR_USER} -p ${HARBOR_PASS}
                        docker tag ${APPNAME}:${VERSION} ${IMAGE_NAME}
                        docker push ${IMAGE_NAME}
                    """
                }
            }
        }
        stage('部署前端') {
            when {
                expression { return env.APPNAME == 'ruoyi-gateway' }
            }
            steps {
                script {
                    echo "=== 部署前端 ==="
                    // sshPublisher:Jenkins SSH插件,实现远程服务器操作
                    sshPublisher(publishers: [
                        sshPublisherDesc(
                            configName: NGINX_SERVER,  // 指定前端部署的SSH服务器
                            transfers: [
                                sshTransfer(
                                    sourceFiles: "ruoyi-ui/dist-${VERSION}.zip",  // 要推送的前端压缩包
                                    removePrefix: '',  // 不删除压缩包的前缀路径
                                    remoteDirectory: '',  // 推送至远程服务器的当前目录
                                    // 远程服务器执行的命令:解压压缩包并删除源文件
                                    execCommand: """
                                        # 切换到前端部署目录-->解压并覆盖旧文件-->删除压缩包
                                        cd /usr/local/RuoYi-Cloud/ruoyi-ui && unzip -o dist-${VERSION}.zip && rm -rf dist-${VERSION}.zip
​
                                    """
                                )
                            ],
                            verbose: true  // 输出详细SSH操作日志
                        )
                    ])
                }
            }
        }
        stage('部署后端') {
            options {
                timeout(time: 30, unit: 'MINUTES')  // 超时时间10分钟
            }
            steps {
                script {
                    echo "=== 部署后端 ==="
                    sshPublisher(publishers: [
                        sshPublisherDesc(
                            configName: APP_SERVER,  // 指定后端部署的SSH服务器
                            transfers: [
                                sshTransfer(
                                    sourceFiles: '',  // 无需推送文件
                                    removePrefix: '',
                                    remoteDirectory: '',
                                    // 远程执行部署脚本:标准镜像名、端口等参数
                                    execCommand: """
                                        sh /usr/bin/ry-cloud.sh ${IMAGE_NAME} ${HOST_PORT} ${CONTAINER_PORT}
                                    """
                                )
                            ],
                            verbose: true
                        )
                    ])
                }
            }
        }
    }
}

2.脚本文件

  • 部署后端阶段是调用后端服务器的/usr/bin/ry-cloud.sh脚本文件,需在后端服务器添加改文件
bash 复制代码
vim /usr/bin/ry-cloud.sh
------------------------------------------------------------------------
#!/bin/bash
FULL_IMAGE_NAME=$1
HOST_PORT=$2
CONTAINER_PORT=$3
​
# ===== 根据Jenkins传入参数,解析镜像名 =====
# 1:按最后一个:分割 "地址/项目名/镜像名" 和 "版本号"
IMAGE_PREFIX=${FULL_IMAGE_NAME%:*}  # 取:左侧部分 → 121.4.90.98:80/ruoyi/ruoyi-gateway
VERSION=${FULL_IMAGE_NAME##*:}      # 取:右侧部分 → V1.0.11
​
# 2:按最后一个/分割 "地址/项目名" 和 "镜像名"
APP_NAME=${IMAGE_PREFIX##*/}       # 取最后一个/右侧 → ruoyi-gateway
HARBOR_ADDR=${IMAGE_PREFIX%/*}     # 取最后一个/左侧 → 121.4.90.98:80/ruoyi
​
# ===== 打印解析结果 =====
echo "===== 部署参数解析结果 ====="
echo "完整镜像名: ${FULL_IMAGE_NAME}"
echo "Harbor地址+项目名: ${HARBOR_ADDR}"
echo "微服务名: ${APP_NAME}"
echo "版本号: ${VERSION}"
echo "主机端口: ${HOST_PORT}"
echo "容器端口: ${CONTAINER_PORT}"
echo "============================"
​
# ===== 停止并删除旧容器 =====
echo "===== 停止并删除旧容器 ====="
OLD_CONTAINER=$(docker ps -aq --filter "name=${APP_NAME}")
if [ -n "${OLD_CONTAINER}" ]; then
    echo "发现运行中的旧容器,ID: ${OLD_CONTAINER},正在停止"
    docker stop ${OLD_CONTAINER} || echo "警告:容器可能已停止"
    echo "删除旧容器: ${OLD_CONTAINER}"
    docker rm ${OLD_CONTAINER} || echo "警告:容器可能已删除"
else
    echo "未发现运行中的旧容器,跳过"
fi
​
# ===== 删除旧镜像 =====
echo "===== 删除旧镜像 ====="
# 检查旧镜像是否存在
if docker images -q ${FULL_IMAGE_NAME} > /dev/null 2>&1; then
    echo "发现旧镜像 ${FULL_IMAGE_NAME},正在删除"
    docker rmi ${FULL_IMAGE_NAME} || echo "警告:镜像可能已删除"
else
    echo "未发现旧镜像 ${FULL_IMAGE_NAME},跳过"
fi
​
# ===== 登录Harbor并拉取新镜像 =====
echo "===== 登录Harbor并拉取新镜像 ====="
# Harbor用户名密码,也可由Jenkins传入
HARBOR_USER="admin"
HARBOR_PASS="Harbor12345"
docker login ${HARBOR_ADDR%/*} -u ${HARBOR_USER} -p ${HARBOR_PASS} || echo "警告:Harbor登录失败"
docker pull ${FULL_IMAGE_NAME} || { echo "ERROR:拉取新镜像失败!"; exit 1; }
​
# ===== 启动新容器 =====
echo "===== 启动新容器 ====="
docker run -d \
  --name ${APP_NAME} \
  --restart=always \
  -p ${HOST_PORT}:${CONTAINER_PORT} \
  ${FULL_IMAGE_NAME}
​
# 验证容器是否启动成功
if docker ps --filter "name=${APP_NAME}" | grep -q "${APP_NAME}"; then
    echo "===== 部署成功 ====="
    echo "容器名: ${APP_NAME}"
    echo "映射端口: ${HOST_PORT}:${CONTAINER_PORT}"
    exit 0
else
    echo "ERROR:容器启动失败 "
    docker logs ${APP_NAME}  # 打印日志便于排查
    exit 1
fi
------------------------------------------------------------------------

三、配置Jenkins任务

  • 微服务名在Jenkins新建任务并命名,选择多分支流水线类型

  • 分支源选择Git,项目仓库填写Ruoyi-Cloud的Gitlab仓库地址,并配置Gitlab凭据

  • 行为:发现分支后添加根据名称过滤(支持通配符),选择包含prod*,匹配生产分支prod-peizhi

  • Build Configuration的脚本路径填写对应微服务的代码根目录/Jenkinsfile,如:ruoyi-gateway/Jenkinsfile

  • 点击应用后保存,Jenkins就会自动扫描对应分支,并根据其Jenkinsfile进行构建

  • 其他微服务同理建立Jenkins多分支流水线任务并做好配置

总结

  • 文档从自由风格的项目到多分支流水线的任务搭建配置,我们可以发现,在篇幅上自由风格项目的Jenkins配置较多,而多分支流水线的Jenkins配置非常少,只需要编写好Jenkinsfile,然后告诉Jenkins去哪里找Jenkinsfile文件即可,这也就体现出流水线任务的优势,不用过多去关注Jenkins的插件、系统设置、配置等,只需要根据需求编写Jenkinsfile即可,后续修改也很好找修改点。

  • 在内容上按 单前、后端的简单项目、前后端分离项目 最后到 微服务项目的项目设计,由浅入深通过Jenkins完成不同项目的CICD,已适配较多互联网公司的自动化发布任务。

  • 在自动化发布上:后续将设计编写K8s集群整合Jenkins、Gitlab CI/CD、Gitlab Runner以及Argo CD等更多CICD工具完成不同项目的自动化发布。

  • 在制品保存、链路追踪上:后续将搭建加入nexus的maven私有仓库、skywalking全链路追踪,进一步为项目高并发能力的提升作支持

  • 在日志收集分析、监控告警上:将搭建ELK日志处理、Prometheus+kibana监控告警一体化平台,保障项目高可用与问题的告警及时处理

  • 对于文档内容、设计以及后续文档有任何建议或意见欢迎交流:

相关推荐
哎呦,帅小伙哦1 小时前
Linux 时间:从原子钟到 clock_gettime 的每一面
linux·运维·服务器
sxgzzn1 小时前
新能源场站数智化转型:基于数字孪生与AI的智慧运维管理平台解析
大数据·运维·人工智能
张小姐的猫2 小时前
【Linux】多线程 —— 线程互斥
linux·运维·服务器·c++
迁移科技2 小时前
告别人工分拣!迁移科技 AI+3D 视觉让机器人 “看懂” 无序抓取
人工智能·科技·3d·机器人·自动化·视觉检测
CodeMartain2 小时前
Dify Windows 原生部署(无 Docker、纯本地)
运维·docker·容器
xxx1x1x2 小时前
极客向:DLL/运行库故障的底层逻辑与自动化修复方案
运维·自动化·dll文件·dll·dll修复·dll缺失·dll一键修复
YuanDaima20482 小时前
Linux 进阶运维与 AI 环境实战:进程管理、网络排错与 GPU 监控
linux·运维·服务器·网络·人工智能
lolo大魔王4 小时前
Linux 数据文件处理实战:排序、搜索、压缩、归档一站式详解
linux·运维·服务器
llrraa20104 小时前
配置docker国内镜像源
运维·docker·容器