# 11.11 CI/CD 流水线 :::tip 概述 本节介绍项目的持续集成/持续部署(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)" } }' ``` ## 相关文档 - [Docker 生产部署](/zh/deploy/docker.html) - [后端部署](/zh/deploy/backend.html) - [前端部署](/zh/deploy/frontend.html)