# 11.11 CI/CD 流水线 :::tip 概述 本节介绍项目的持续集成/持续部署(CI/CD)方案,包括 GitHub Actions 和 GitLab CI 的配置示例,以及自动化测试、构建和部署流程。 ::: :::info 模板说明 本项目仓库当前未内置 `.github/workflows` 或 `.gitee/workflows`,以下配置为**通用模板**:按实际代码托管平台(GitHub / Gitee / GitLab)调整后放入对应目录即可使用。 ::: ## 流水线架构 ```text 代码提交 → 代码检查 → 单元测试 → 构建打包 → 部署上线 │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ Git Push Pint PHPUnit Docker 生产环境 ESLint (sqlite) Image ``` ## GitHub Actions ### 后端 CI 配置 ```yaml # ============================================================================= # .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 配置 ```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 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` 步骤)。 ::: ### 自动部署配置 ```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 作为版本标签) # 项目自带 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 ```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.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 ``` ## 部署策略 ### 蓝绿部署 ```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)