345 lines
12 KiB
Markdown
345 lines
12 KiB
Markdown
# 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)
|