源本科技 | 码上会

GitLab CI 准备篇之手动部署

2026/05/14
72
0

本地代码测试

修改根 POM

  • 修改项目根目录的 pom.xml 追加 Maven 多环境配置

<profiles>
    <profile>
        <id>druid</id>
        <properties>
            <!-- 这里增加了 spring.active 属性 -->
            <spring.active>druid</spring.active>
        </properties>
        <!-- 设置默认激活的配置 -->
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <spring.active>prod</spring.active>
        </properties>
    </profile>
</profiles>

<build>
    <!-- 在 build 元素中追加资源管理相关配置 -->
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

生产环境配置

  • 修改 ruoyi-admin/src/main/resources/application.yml 配置

spring:
  profiles:
    # 主要修改让 Spring Boot 能够读取到 Maven 的多环境配置
    active: @spring.active@
  • 新建 ruoyi-admin/src/main/resources/application-prod.yml 生产环境配置

spring:
  data:
    redis:
      host: ${REDIS_HOST}
      port: ${REDIS_PORT}
      database: 0
      password: ${REDIS_PASSWORD}
      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://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: ${MYSQL_USERNAME}
        password: ${MYSQL_PASSWORD}
      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

记住上面的变量,后面要用

  • ${REDIS_HOST}

  • ${REDIS_PORT}

  • ${REDIS_PASSWORD}

  • ${MYSQL_HOST}

  • ${MYSQL_PORT}

  • ${MYSQL_DATABASE}

  • ${MYSQL_USERNAME}

  • ${MYSQL_PASSWORD}

测试后端打包

  • 使用 mvn clean package -Pdruid 命令测试本地环境是否正常

  • 使用【命令提示符 cmd】进入 ruoyi-admin\target 目录

  • 使用 java -jar .\ruoyi-admin.jar 测试是否能够正常运行,确保激活的环境是 druid

注意:

  • 可以继续使用 mvn clean package -Pprod 命令测试是否能够正确加载生产环境配置

  • 也可以等到上生产时再行测试

测试前端打包

  • 进入 ruoyi-ui 目录

  • 使用 yarn build:prod 命令测试是否能够正常打包

  • 打包成功后会多出一个 dist 目录

生产代码测试

拉取代码

  • 先将修改后的代码提交到 GitLab 服务器

  • 进入我们的生产环境,这里是 Ubuntu Service 虚拟机,拉取代码

# 克隆代码
git clone ssh://git@192.168.203.134:2222/lusifer/myproject.git

# 拉取代码
cd ~/myproject/
git pull
  • 执行完拉取命令后,可以看到代码已更行

构建后端代码

  • 创建 Maven 配置文件 settings.xml

  • 配置文件与项目目录平级,这里是 cd ~

<settings>
  <servers>
    <server>
        <id>maven-public</id>
        <username>admin</username>
        <password>12345678</password>
    </server>
    <server>
        <id>maven-release</id>
        <username>admin</username>
        <password>12345678</password>
    </server>
    <server>
        <id>maven-snapshots</id>
        <username>admin</username>
        <password>12345678</password>
    </server>
  </servers>
  <mirrors>
    <mirror>
      <!-- 注意这里的 ID 需要匹配上面 Server 元素的 ID,用于配置 Nexus 的账号密码 -->
      <id>maven-public</id>
      <url>http://192.168.203.132:8081/repository/maven-public/</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>
</settings>
  • 我们使用 Docker Compose 进行构建,创建一个名为 docker-compose-maven.yml 的脚本

services:
  maven-build:
    # 这里是 JDK21
    image: maven:3.9.15-eclipse-temurin-21
    volumes:
      # 映射项目目录
      - ./myproject:/app
      # 本地仓库目录
      - ~/.m2:/root/.m2
      # 映射刚才创建的配置文件
      - ./settings.xml:/root/.m2/settings.xml
    working_dir: /app
    # 使用开发环境配置打包作为测试
    # command: mvn clean package -DskipTests -Pdruid
    # 实际使用生产环境配置打包
    command: mvn clean package -DskipTests -Pprod
    environment:
      - MAVEN_OPTS=-Dmaven.repo.local=/root/.m2/repository
  • 先手动下载镜像 docker pull maven:3.9.15-eclipse-temurin-21

  • 使用如下命令执行构建

docker-compose -f docker-compose-maven.yml up
  • 可以看到执行成功会从私服下载依赖并开始构建

注意 lastUpdated

Maven 在下载依赖失败(版本错误、网络不通、私服异常)时,会在本地仓库生成 .lastUpdated 后缀的文件

  • 这个文件是失败标记,Maven 会认为该依赖永久不可用

  • 即使你后续修复了版本、网络、私服配置,Maven 也不会自动重新下载依赖,会直接读取这个标记文件,导致构建持续失败

  • 只有删除所有 .lastUpdated 文件,Maven 才会重新从你的 Nexus3 私服拉取依赖

解决方案 1:直接运行清理命令

find ~/.m2 -name "*.lastUpdated" -type f -delete

解决方案 2:保存为脚本文件

  • 创建脚本文件

touch maven-clean.sh
chmod +x maven-clean.sh
  • 编辑文件

#!/bin/bash
# 清理 Maven 本地仓库中所有 .lastUpdated 失败标记文件
echo "开始清理 ~/.m2 下的 .lastUpdated 文件..."
find ~/.m2 -name "*.lastUpdated" -type f -delete
echo "清理完成!"
  • 执行清理脚本

./maven-clean.sh

解决 Python 报错

  • 构建成功后可能会报一个错误,其实没有影响,打包确实是成功了

Exception in thread Thread-4 (watch_events):
Traceback (most recent call last):
  File "/usr/lib/python3.12/threading.py", line 1073, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.12/threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "/usr/lib/python3/dist-packages/compose/cli/log_printer.py", line 202, in watch_events
    for event in event_stream:
  File "/usr/lib/python3/dist-packages/compose/project.py", line 626, in yield_loop
    yield build_container_event(event)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/compose/project.py", line 594, in build_container_event
    container = Container.from_id(self.client, event['id'])
                                               ~~~~~^^^^^^
KeyError: 'id'
root_maven-build_1 exited with code 0
  • 这是因为我们用的是 Docker Compose V1 官方已弃用 Python 版的 docker-compose,新版是 Go 语言编写的 docker compose

  • 我们可以升级 Docker Compose 的版本到 V2

apt update && apt install -y docker-compose-v2
  • 之后执行就不要带横杠 (-) 了

# --remove-orphans 自动清理孤儿容器
docker compose -f docker-compose-maven.yml up --remove-orphans
  • 使用新版本打包就不再报错了

构建前端代码

  • 先手动下载镜像:docker pull node:22-alpine

  • 我们使用 Docker Compose 进行构建,创建一个名为 docker-compose-node.yml 的脚本

services:
  frontend-build:
    image: node:22-alpine
    environment:
      - YARN_REGISTRY=https://registry.npmmirror.com
      - SASS_BINARY_SITE=https://npmmirror.com/mirrors/node-sass/
    volumes:
      - ./myproject/ruoyi-ui:/app
      - yarn-cache:/usr/local/share/.cache/yarn
    working_dir: /app
    command: sh -c "yarn && yarn build:prod"

volumes:
  yarn-cache:
  • 使用如下命令执行构建

# --remove-orphans 自动清理孤儿容器
docker compose -f docker-compose-node.yml up --remove-orphans
  • 执行成功效果如下

  • 进入前端代码目录,查看是否生成了 dist 目录

服务器直接运行

  • usr/local/docker 目录下创建 myproject 目录用于测试

  • 创建 docker-compose.yml

services:
  myproject:
    # JDK 21
    image: eclipse-temurin:21-jre-alpine
    container_name: myproject
    ports:
      - "8080:8080"
    volumes:
      - ./app.jar:/app/app.jar
    # 工作目录
    working_dir: /app
    # 启动命令:直接运行 jar
    command: ["java", "-jar", "app.jar"]
    # 环境变量(生产配置)
    environment:
      - TZ=Asia/Shanghai
      - REDIS_HOST=192.168.203.129
      - REDIS_PORT=6379
      - REDIS_PASSWORD=123456
      - MYSQL_HOST=192.168.203.129
      - MYSQL_PORT=3306
      - MYSQL_DATABASE=ry-vue
      - MYSQL_USERNAME=root
      - MYSQL_PASSWORD=123456
    # 自动重启
    restart: always
  • 复制刚才打包的 jar 文件到当前目录

cp ~/myproject/ruoyi-admin/target/ruoyi-admin.jar app.jar
  • 启动脚本测试是否可以运行

docker compose up -d
  • 查看日志

docker compose logs -f
  • 注意现在的环境为生产环境,之前设置的变量(如 ${MYSQL_USERNAME})生效了

  • 打开浏览器访问:http://你的服务器 IP:8080/

  • 测试成功后先停止,我们一会开始处理前端

docker compose down
  • 继续在当前目录创建 nginx.conf 配置文件

注意:

  • 下面配置中带有 IP 的部分修改为你自己的

  • 由于项目的 .env.production 配置文件中定义接口 /prod-api nginx localtion 部分修改为 location /prod-api/

# 使用 nginx 用户,工作进程数自动适配 CPU 核心
user nginx;
worker_processes auto;

# 错误日志配置
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

events {
    # 单个工作进程最大连接数
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 访问日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    # 开启高效文件传输
    sendfile on;
    # 长连接超时时间
    keepalive_timeout 65;

    # 开启 gzip 压缩,优化前端资源加载速度
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_comp_level 2;
    gzip_types text/plain application/javascript text/css application/xml image/jpeg image/png;
    gzip_vary on;

    server {
        # 监听 80 端口
        listen 80;
        server_name localhost;

        # 前后端分离 - API 反向代理(解决跨域)
        # 结尾带 / 会删除 api 前缀
        location /prod-api/ {
            proxy_pass http://192.168.203.129:8080/; # 结尾带斜杠会删除 api 前缀
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }

        # 根路径匹配
        location / {
            # 指向 Webpack 打包的 dist 目录(推荐 root,适配前端静态资源)
            root /usr/share/nginx/html/;
            index index.html;

            # HTML 文件不缓存,保证实时更新
            if ($request_filename ~* .*\.(html|htm)$) {
                expires -1s;
                add_header Cache-Control "no-cache";
            }
        }

        # 错误页面配置
        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
            root /usr/share/nginx/html;
        }
    }
}
  • 修改 docker-compose.yml 脚本,追加 nginx 相关配置,下面是完整脚本

services:
  myproject:
    # JDK 21
    image: eclipse-temurin:21-jre-alpine
    container_name: myproject
    ports:
      - "8080:8080"
    volumes:
      - ./app.jar:/app/app.jar
    # 工作目录
    working_dir: /app
    # 启动命令:直接运行 jar
    command: ["java", "-jar", "app.jar"]
    # 环境变量(生产配置)
    environment:
      - TZ=Asia/Shanghai
      - REDIS_HOST=192.168.203.129
      - REDIS_PORT=6379
      - REDIS_PASSWORD=123456
      - MYSQL_HOST=192.168.203.129
      - MYSQL_PORT=3306
      - MYSQL_DATABASE=ry-vue
      - MYSQL_USERNAME=root
      - MYSQL_PASSWORD=123456
    # 自动重启
    restart: always

  myproject-nginx:
    image: nginx:1.26.2-alpine
    restart: always
    container_name: myproject-nginx
    environment:
      NGINX_HOST: localhost
      NGINX_PORT: 80
    ports:
      - 80:80
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./dist:/usr/share/nginx/html
  • 手动拉取 nginx 镜像

docker pull nginx:1.26.2-alpine
  • 复制刚才构建的前端静态资源文件到当前目录

# 先删除当前目录下的 dist 目录,再把最新的复制过来
rm -rf ./dist && cp -r ~/myproject/ruoyi-ui/dist/ ./
  • 再次启动脚本并查看日志

# 启动容器
docker compose up -d

# 查看日志
docker compose logs -f
  • 现在可以直接访问了,试试看:http://192.168.203.129/

至此最基本的手动部署暂时告一段落