项目的流水线被划分为多个阶段 (Stages),确保任务按预定顺序执行,完整流程如下:
notify_start -> lint -> sonar -> build -> deploy -> notify_end
dev 或 master 分支,或手动触发时执行,完成应用的构建和部署。


.
├── .gitlab-ci.yml # 主配置文件,定义 stages, workflow, variables, include 规则
├── .gitlab-dev.yml # dev 环境的作业 (build, deploy, notify)
├── .gitlab-prod.yml # prod 环境的作业 (build, notify)
└── scripts/
└── ci/
├── package-zip.sh # 打包构建产物为 .zip 并生成环境变量
├── deploy-zip.sh # 部署 .zip 包到目标服务器
└── wechat-notify.js # 发送企业微信通知
# 全局规则:只在指定分支或合并到指定分支的请求中运行
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: always
- if: $CI_PIPELINE_SOURCE == "push" && ($CI_COMMIT_BRANCH == "dev" || $CI_COMMIT_BRANCH == "master")
when: always
- if: $CI_PIPELINE_SOURCE == "web"
when: always
- when: never
# 定义流水线的阶段
stages:
- notify_start
- lint
- sonar
- build
- deploy
- notify_end
# 定义变量
variables:
NODE_VERSION: lts
PNPM_VERSION: latest
GIT_DEPTH: 0
GIT_STRATEGY: clone
BUILD_ENV:
value: dev
options:
- dev
- prod
description: 选择构建环境(dev/prod)
SONAR_HOST_URL: # SonarQube 主机地址(自定义)
value: 'http://10.0.0.100:9000'
description: SonarQube主机地址
WECHAT_WEBHOOK_URL: # 企业微信webhook地址,用于通知(自定义)
value: 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your-webhook-key-here'
description: 企业微信webhook地址
BUILD_DIR: # 构建目录(自定义)
value: app-gen/dist/web/your-app-name
description: 构建目录
DEPLOY_HOST: # 测试部署主机(一般固定,无需修改)
value: 10.0.0.100
description: 测试部署主机
DEPLOY_DIR: # 测试部署目录(自定义)
value: /home/user/nginx/html/
description: 测试部署目录
default:
image: node:${NODE_VERSION}
tags:
- sonarqube
include:
- local: .gitlab-dev.yml
rules:
- if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == "dev"
- if: $CI_PIPELINE_SOURCE == "web" && $BUILD_ENV == "dev"
- local: .gitlab-prod.yml
rules:
- if: $CI_PIPELINE_SOURCE == "web" && $BUILD_ENV == "prod"
# 代码检查作业
lint:
stage: lint
script:
- npm install -g pnpm@${PNPM_VERSION}
- pnpm install --frozen-lockfile
- pnpm eslint 'app/**/*.{ts,tsx,vue}'
artifacts:
when: always
reports:
junit: .eslintcache
paths:
- .eslintcache
expire_in: 1 week
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
allow_failure: true
# SonarQube 代码分析
sonarqube-check:
stage: sonar
image: openjdk:11-jre-slim
variables:
SONAR_PROJECT_KEY: ${CI_PROJECT_ID}
SONAR_PROJECT_NAME: ${CI_PROJECT_TITLE}
SONAR_PROJECT_VERSION: ${CI_COMMIT_SHORT_SHA}
SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar'
SONAR_SCANNER_VERSION: 4.6.2.2472
cache:
key: 'sonarqube-${SONAR_SCANNER_VERSION}'
paths:
- .sonar/cache
- sonar-scanner/
before_script:
- echo "准备 SonarScanner CLI ${SONAR_SCANNER_VERSION}..."
- |
if [ ! -d "sonar-scanner" ]; then
echo "下载并安装 SonarScanner CLI..."
apt-get update && apt-get install -y wget unzip
wget -O sonar-scanner.zip "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux.zip"
unzip sonar-scanner.zip
mv sonar-scanner-${SONAR_SCANNER_VERSION}-linux sonar-scanner
else
echo "使用缓存的 SonarScanner CLI..."
fi
- export PATH="$PWD/sonar-scanner/bin:$PATH"
script:
- echo "运行测试并生成覆盖率报告..."
- export PATH="$PWD/sonar-scanner/bin:$PATH"
- |
sonar-scanner \
-Dsonar.projectKey=${SONAR_PROJECT_KEY} \
-Dsonar.projectName="${SONAR_PROJECT_NAME}" \
-Dsonar.projectVersion=${SONAR_PROJECT_VERSION} \
-Dsonar.host.url=${SONAR_HOST_URL} \
-Dsonar.login=${SONAR_TOKEN}
allow_failure: true
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 流水线开始通知
notify-start:
stage: notify_start
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- node scripts/ci/wechat-notify.js --label "${CI_MERGE_REQUEST_TITLE}" --type CI-start
when: on_success
# 流水线结束通知成功
notify-end-success:
stage: notify_end
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- node scripts/ci/wechat-notify.js --label "${CI_MERGE_REQUEST_TITLE}" --type CI-end --success
when: on_success
# 流水线结束通知失败
notify-end-failed:
stage: notify_end
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- node scripts/ci/wechat-notify.js --label "${CI_MERGE_REQUEST_TITLE}" --type CI-end --failed
when: on_failure
package.json 中定义了两个构建脚本:
build: 用于 prod 环境,执行生产环境的完整构建。build:dev: 用于 dev 环境,执行测试环境的特定构建。.gitlab-ci.yml 会根据 $BUILD_ENV 变量的值,通过 include 规则动态加载 .gitlab-dev.yml 或 .gitlab-prod.yml,从而执行对应环境的作业。| 变量名 | 用途 |
|---|---|
BUILD_ENV | 控制构建环境 (dev/prod),流水线自动或手动选择 |
BUILD_DIR | 指定构建产物的输出目录,根据项目调整 |
SONAR_HOST_URL | SonarQube 服务器地址 |
WECHAT_WEBHOOK_URL | 企业微信 WebHook地址,用于通知 |
DEPLOY_HOST | 部署目标服务器 IP 地址 |
DEPLOY_DIR | 部署到服务器上的目标目录 |
BUILD_DIR 等变量需要根据不同项目进行修改,请确保其指向正确的构建产物目录。流水线的关键节点会通过企业微信机器人发送实时通知。
scripts/ci/wechat-notify.js 负责组装消息内容并发送。<@userid> 的语法来提及指定成员,请确保填入的是成员的账号 (userid),而不是手机号或姓名。

除了自动化触发,你也可以手动运行流水线,并指定构建环境或者其他定义的全局变量。
构建 -> 流水线 页面。运行流水线 按钮。变量 区域,BUILD_ENV 变量会提供一个下拉框,你可以选择 dev 或 prod 环境。
这是因为 .gitlab-ci.yml 中的 BUILD_DIR 变量被配置为了绝对路径 (例如 /app/dist/...)。
$CI_PROJECT_DIR,构建产物路径应该是相对于该目录的相对路径。解决方案:
修改 .gitlab-ci.yml 中的 BUILD_DIR 变量,将其改为相对路径 (例如 app/dist/...)。
因为该通知作业的 needs 依赖中只包含了 build 作业,而没有包含 deploy 作业,导致它在 build 完成后就立即执行。
因为 .gitlab-dev.yml 的 include 规则依赖于 $BUILD_ENV 变量,但在自动触发的 MR 流水线中,定义在 .gitlab-ci.yml 文件内部的 variables 在 include 解析阶段是不可用的。
这是因为部署脚本中的 scp 命令源路径使用了 .../. 结尾,这表示只复制目录的内容,而不是目录本身。
因为失败通知作业 (notify-deploy-end-failed) 的 needs 依赖了上游作业。一旦上游作业失败,该通知作业自身会被 GitLab 跳过 (skipped)。
企业微信的 Markdown 消息中,@ 成员有严格的语法要求。