Skip to content

11.11 CI/CD 流水线 ​

概述

本节介绍项目的持续集成/持续部署(CI/CD)方案,包括 GitHub Actions 和 GitLab CI 的配置示例,以及自动化测试、构建和部署流程。

流水线架构 ​

text
代码提交 → 代码检查 → 单元测试 → 构建打包 → 部署上线
    │          │          │          │          │
    ▼          ▼          ▼          ▼          ▼
  Git Push   ESLint    PHPUnit    Docker     生产环境
             PHPStan   Vitest     Image

GitHub Actions ​

后端 CI 配置 ​

yaml
# =============================================================================
# .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              # 覆盖率报告文件

前端 CI 配置 ​

yaml
# =============================================================================
# .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

自动部署配置 ​

yaml
# =============================================================================
# .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 ​

yaml
# =============================================================================
# .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

部署策略 ​

蓝绿部署 ​

text
当前版本(蓝)  →  新版本(绿)
     │                  │
     ▼                  ▼
  Nginx 切换流量  ←  验证通过

滚动更新 ​

bash
# 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

通知集成 ​

部署通知(钉钉/飞书) ​

yaml
# 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)"
        }
      }'

相关文档 ​

小蚂蚁云团队 · 提供技术支持