Files
2026-10-08 17:50:29 +08:00

12 KiB
Raw Permalink Blame History

11.11 CI/CD 流水线

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

:::info 模板说明 本项目仓库当前未内置 .github/workflows 或 .gitee/workflows,以下配置为通用模板:按实际代码托管平台(GitHub / Gitee / GitLab)调整后放入对应目录即可使用。 :::

流水线架构

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

GitHub Actions

后端 CI 配置

# =============================================================================
# .github/workflows/backend-ci.yml — 后端持续集成
# =============================================================================
# 触发条件:push 到 main/develop 分支,或向 main 发起 PR
# 执行环境:Ubuntu 最新版
# 流程:代码风格检查 → 单元测试 → 覆盖率上传
# 说明:phpunit.xml 已配置 sqlite :memory: 测试库,无需外部数据库服务
# =============================================================================

name: Backend CI

on:
  push:
    branches: [main, develop]               # push 到这些分支时触发
  pull_request:
    branches: [main]                        # 向 main 发起 PR 时触发

jobs:
  test:
    runs-on: ubuntu-latest                  # 使用 GitHub 托管的最新 Ubuntu 运行器

    steps:
      # 拉取代码(checkout@v4 是 GitHub 官方 Action)
      - uses: actions/checkout@v4

      # 安装 PHP 8.3 及所需扩展
      - name: Setup PHP
        uses: shivammathur/setup-php@v2     # 第三方 Action,自动安装 PHP
        with:
          php-version: '8.3'                # PHP 版本(Laravel 13 要求)
          extensions: pdo_mysql, mbstring, gd, bcmath, zip, 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 artisan key:generate          # 生成应用密钥

      # 代码风格检查(Laravel Pint,PSR-12 + Laravel 风格)
      - name: Code Style Check
        run: vendor/bin/pint --test

      # 运行单元测试(composer test = config:clear + artisan test,
      # phpunit.xml 使用 sqlite :memory:,不依赖 MySQL)
      - name: Run Tests
        run: composer test -- --coverage-text

      # 上传覆盖率报告到 Codecov(免费的覆盖率追踪平台)
      - name: Upload Coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml              # 覆盖率报告文件

:::tip 无外部依赖的测试 phpunit.xml 将 DB_CONNECTION 设为 sqlite、DB_DATABASE 设为 :memory:,单元测试在内存库中运行,CI 无需启动 MySQL 容器。如需对真实 MySQL 做集成测试,再参考下文 GitLab CI 的 services 配置。 :::

前端 CI 配置

# =============================================================================
# .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 22 并配置 pnpm 缓存
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '22'                # Node.js 版本(22 LTS;根目录 Vite 7 要求 20.19+/22.12+)
          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 代码风格检查(脚本自带 --fix,会自动修复可修复问题)
      - name: Lint
        working-directory: ui
        run: pnpm lint:eslint

      # TypeScript 类型检查(发现类型错误)
      - name: Type Check
        working-directory: ui
        run: pnpm type:check

      # 生产环境构建(验证构建是否成功)
      - name: Build
        working-directory: ui
        run: pnpm build

:::info 前端暂无单元测试脚本 ui/package.json 未提供 test 脚本(如后续引入 Vitest,再补 pnpm test 步骤)。 :::

自动部署配置

# =============================================================================
# .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 作为版本标签)
      # 项目自带 Dockerfile 与 docker-compose.yml(含 docker/.env.docker)
      - 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 --env-file docker/.env.docker pull   # 拉取最新镜像
            docker compose --env-file docker/.env.docker up -d --build
            # 数据库结构变更:手动执行 document/mysql 下的 SQL 脚本(本项目无 migration)

GitLab CI

# =============================================================================
# .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.3-cli                      # PHP 8.3 CLI 镜像
  services:
    - mysql:8.0                           # MySQL 服务容器(集成测试用;单元测试走 sqlite 内存库)
  script:
    # 安装系统依赖(PHP 扩展编译需要)
    - apt-get update && apt-get install -y libpq-dev libzip-dev
    # 安装 PHP 扩展
    - docker-php-ext-install pdo_mysql zip
    - pecl install redis && docker-php-ext-enable redis
    # 安装 Composer 依赖
    - composer install --no-progress
    # 准备环境并执行测试
    - cp .env.example .env
    - php artisan key:generate
    - vendor/bin/pint --test               # 代码风格检查
    - composer test                        # 运行测试(phpunit.xml 用 sqlite :memory:)
  cache:
    key: ${CI_COMMIT_REF_SLUG}            # 按分支名缓存
    paths:
      - vendor/                           # 缓存 Composer 依赖

# -----------------------------------------------------------------------------
# 前端测试
# -----------------------------------------------------------------------------
frontend:test:
  stage: test
  image: node:22                          # Node.js 22 镜像
  script:
    - cd ui
    - npm install -g pnpm                 # 全局安装 pnpm
    - pnpm install --frozen-lockfile      # 安装依赖
    - pnpm lint:eslint                    # 代码风格检查
    - pnpm type:check                     # TypeScript 类型检查
    - 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 --env-file docker/.env.docker up -d --build"
  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)"
        }
      }'

相关文档