Become a sponsor

概述
本节介绍项目的持续集成/持续部署(CI/CD)方案,包括 GitHub Actions 和 GitLab CI 的配置示例,以及自动化测试、构建和部署流程。
代码提交 → 代码检查 → 单元测试 → 构建打包 → 部署上线
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Git Push ESLint PHPUnit Docker 生产环境
PHPStan Vitest Image# =============================================================================
# .github/workflows/backend-ci.yml — 后端持续集成
# =============================================================================
# 触发条件:push 到 main/develop 分支,或向 main 发起 PR
# 执行环境:Ubuntu 最新版 + MySQL 8.0 服务容器
# 流程:代码检查 → 静态分析 → 单元测试 → 覆盖率上传
# =============================================================================
name: Backend CI
on:
push:
branches: [main, develop] # push 到这些分支时触发
pull_request:
branches: [main] # 向 main 发起 PR 时触发
jobs:
test:
runs-on: ubuntu-latest # 使用 GitHub 托管的最新 Ubuntu 运行器
# -------------------------------------------------------------------------
# 服务容器(GitHub Actions 自动启动,与运行器共享网络)
# -------------------------------------------------------------------------
services:
mysql:
image: mysql:8.0 # MySQL 8.0 官方镜像
env:
MYSQL_ROOT_PASSWORD: root # 测试环境密码(仅用于 CI)
MYSQL_DATABASE: test_db # 自动创建的测试数据库
ports:
- 3306:3306 # 暴露端口供测试连接
# 健康检查:确保 MySQL 就绪后再执行后续步骤
options: >-
--health-cmd="mysqladmin ping" # 用 mysqladmin ping 检测
--health-interval=10s # 每 10 秒检查一次
--health-timeout=5s # 超时时间
--health-retries=3 # 重试 3 次后判定失败
steps:
# 拉取代码(checkout@v4 是 GitHub 官方 Action)
- uses: actions/checkout@v4
# 安装 PHP 8.2 及所需扩展
- name: Setup PHP
uses: shivammathur/setup-php@v2 # 第三方 Action,自动安装 PHP
with:
php-version: '8.2' # PHP 版本
extensions: pdo_mysql, gd, redis # 安装的扩展
coverage: xdebug # 代码覆盖率工具
# 安装 Composer 依赖(--no-progress 隐藏进度条,--prefer-dist 下载压缩包更快)
- name: Install Dependencies
run: composer install --no-progress --prefer-dist
# 准备运行环境
- name: Prepare Environment
run: |
cp .env.example .env # 复制环境配置模板
php think db:migrate # 执行数据库迁移
# 代码风格检查(PSR-12 标准)
- name: Code Style Check
run: vendor/bin/phpcs --standard=PSR12 app/
# 静态分析(level=5 为严格模式,可发现潜在 bug)
- name: Static Analysis
run: vendor/bin/phpstan analyse app/ --level=5
# 运行单元测试(--coverage-text 输出文本格式覆盖率报告)
- name: Run Tests
run: php think test --coverage-text
# 上传覆盖率报告到 Codecov(免费的覆盖率追踪平台)
- name: Upload Coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage.xml # 覆盖率报告文件# =============================================================================
# .github/workflows/frontend-ci.yml — 前端持续集成
# =============================================================================
# 触发条件:仅当 ui/ 目录下的文件变更时触发(避免后端改动触发前端 CI)
# 流程:Lint → 类型检查 → 单元测试 → 构建
# =============================================================================
name: Frontend CI
on:
push:
branches: [main, develop]
paths:
- 'ui/**' # 仅 ui/ 目录变更时触发
pull_request:
branches: [main]
paths:
- 'ui/**'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# 安装 Node.js 18 并配置 pnpm 缓存
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18' # Node.js 版本
cache: 'pnpm' # 启用 pnpm 缓存(加速依赖安装)
cache-dependency-path: ui/pnpm-lock.yaml # 缓存依赖的 lockfile 路径
# 全局安装 pnpm 包管理器
- name: Install pnpm
run: npm install -g pnpm
# 安装前端依赖(--frozen-lockfile 确保版本与 lockfile 一致)
- name: Install Dependencies
working-directory: ui # 在 ui/ 目录下执行
run: pnpm install --frozen-lockfile
# ESLint 代码风格检查
- name: Lint
working-directory: ui
run: pnpm lint:eslint
# TypeScript 类型检查(发现类型错误)
- name: Type Check
working-directory: ui
run: pnpm type:check
# 运行单元测试(Vitest)
- name: Run Tests
working-directory: ui
run: pnpm test
# 生产环境构建(验证构建是否成功)
- name: Build
working-directory: ui
run: pnpm build# =============================================================================
# .github/workflows/deploy.yml — 自动部署
# =============================================================================
# 触发条件:push 到 main 分支,或打 v* 标签
# 流程:构建 Docker 镜像 → 推送到镜像仓库 → SSH 部署到服务器
# =============================================================================
name: Deploy
on:
push:
branches: [main]
tags:
- 'v*' # 匹配 v1.0.0、v2.1.3 等标签
jobs:
deploy:
runs-on: ubuntu-latest
needs: [test] # 依赖 test job 通过后才执行
if: github.ref == 'refs/heads/main' # 仅 main 分支触发部署
steps:
- uses: actions/checkout@v4
# 构建 Docker 镜像(用 git commit SHA 作为版本标签)
- name: Build Docker Image
run: |
docker build -t myapp:${{ github.sha }} . # 用 commit SHA 标记
docker tag myapp:${{ github.sha }} myapp:latest # 同时打 latest 标签
# 推送镜像到 Docker Hub(secrets 在 GitHub 仓库设置中配置)
- name: Push to Registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push myapp:${{ github.sha }} # 推送版本标签
docker push myapp:latest # 推送 latest 标签
# 通过 SSH 连接服务器执行部署命令
- name: Deploy to Server
uses: appleboy/ssh-action@v1 # 第三方 SSH Action
with:
host: ${{ secrets.SERVER_HOST }} # 服务器地址
username: ${{ secrets.SERVER_USER }} # SSH 用户名
key: ${{ secrets.SSH_KEY }} # SSH 私钥
script: |
cd /opt/myapp # 进入项目目录
docker-compose pull # 拉取最新镜像
docker-compose up -d # 重建并启动容器
php think db:migrate # 执行数据库迁移# =============================================================================
# .gitlab-ci.yml — GitLab CI/CD 配置
# =============================================================================
# 阶段:test → build → deploy
# 缓存:vendor/ 和 node_modules/ 按分支缓存,加速后续构建
# =============================================================================
stages:
- test # 测试阶段
- build # 构建阶段
- deploy # 部署阶段
# 全局变量(所有 job 共享)
variables:
MYSQL_ROOT_PASSWORD: root # 测试数据库密码
MYSQL_DATABASE: test_db # 测试数据库名
# -----------------------------------------------------------------------------
# 后端测试
# -----------------------------------------------------------------------------
backend:test:
stage: test
image: php:8.2-cli # PHP 8.2 CLI 镜像
services:
- mysql:8.0 # MySQL 服务容器
script:
# 安装系统依赖(PHP 扩展编译需要)
- apt-get update && apt-get install -y libpq-dev libzip-dev
# 安装 PHP 扩展
- docker-php-ext-install pdo_mysql zip
# 安装 Composer 依赖
- composer install --no-progress
# 准备环境并执行测试
- cp .env.example .env
- php think db:migrate # 数据库迁移
- php think test # 运行测试
cache:
key: ${CI_COMMIT_REF_SLUG} # 按分支名缓存
paths:
- vendor/ # 缓存 Composer 依赖
# -----------------------------------------------------------------------------
# 前端测试
# -----------------------------------------------------------------------------
frontend:test:
stage: test
image: node:18 # Node.js 18 镜像
script:
- cd ui
- npm install -g pnpm # 全局安装 pnpm
- pnpm install --frozen-lockfile # 安装依赖
- pnpm lint:eslint # 代码风格检查
- pnpm test # 单元测试
- pnpm build # 生产构建
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- ui/node_modules/ # 缓存前端依赖
# -----------------------------------------------------------------------------
# 生产部署(仅 main 分支)
# -----------------------------------------------------------------------------
deploy:production:
stage: deploy
only:
- main # 仅 main 分支触发
script:
- docker build -t myapp:$CI_COMMIT_SHA . # 构建镜像
- docker push myapp:$CI_COMMIT_SHA # 推送到仓库
# SSH 到服务器执行部署
- ssh $SERVER_USER@$SERVER_HOST "cd /opt/myapp && docker-compose pull && docker-compose up -d"
environment:
name: production
url: https://api.example.com当前版本(蓝) → 新版本(绿)
│ │
▼ ▼
Nginx 切换流量 ← 验证通过# Docker Swarm 滚动更新
# --update-parallelism 1 → 每次只更新 1 个容器实例
# --update-delay 30s → 每批更新间隔 30 秒(观察健康状态)
# --image myapp:v2.0 → 新版本镜像
# myapp_web → 服务名称
docker service update \
--update-parallelism 1 \
--update-delay 30s \
--image myapp:v2.0 \
myapp_web# GitHub Actions 部署成功通知(发送到钉钉机器人)
- name: Notify on Success
if: success() # 仅在部署成功时执行
run: |
# curl 发送 POST 请求到钉钉 Webhook
# -H → 设置请求头为 JSON 格式
# -d → 发送 JSON 消息体(Markdown 格式)
curl -X POST "${{ secrets.DINGTALK_WEBHOOK }}" \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title": "部署成功",
"text": "## ✅ 部署成功\n- **项目**: myapp\n- **分支**: ${{ github.ref_name }}\n- **提交**: ${{ github.sha }}\n- **时间**: $(date)"
}
}'