Files
rxthinkcmf-wiki/RXThinkCMF_ThinkPhp8_EleVue/.vitepress/dist/assets/zh_deploy_ci-cd.md.BRWcT7CB.js
T
2026-10-25 18:52:36 +08:00

291 lines
62 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{_ as i,o as a,c as n,a2 as p}from"./chunks/framework.B-QBKwc1.js";const g=JSON.parse('{"title":"CI/CD 流水线","description":"本节介绍项目的持续集成/持续部署(CI/CD)方案,包括 GitHub Actions 和 GitLab CI 的配置示例,以及自动化测试、构建和部署流程。","frontmatter":{},"headers":[],"relativePath":"zh/deploy/ci-cd.md","filePath":"zh/deploy/ci-cd.md"}'),l={name:"zh/deploy/ci-cd.md"};function h(k,s,t,e,r,d){return a(),n("div",null,[...s[0]||(s[0]=[p(`<h1 id="_11-11-CI-CD-流水线" tabindex="-1">11.11 CI/CD 流水线 <a class="header-anchor" href="#_11-11-CI-CD-流水线" aria-label="Permalink to &quot;11.11 CI/CD 流水线&quot;">​</a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>本节介绍项目的持续集成/持续部署(CI/CD)方案,包括 GitHub Actions 和 GitLab CI 的配置示例,以及自动化测试、构建和部署流程。</p></div><h2 id="流水线架构" tabindex="-1">流水线架构 <a class="header-anchor" href="#流水线架构" aria-label="Permalink to &quot;流水线架构&quot;">​</a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>代码提交 → 代码检查 → 单元测试 → 构建打包 → 部署上线</span></span>
<span class="line"><span> │ │ │ │ │</span></span>
<span class="line"><span> ▼ ▼ ▼ ▼ ▼</span></span>
<span class="line"><span> Git Push ESLint PHPUnit Docker 生产环境</span></span>
<span class="line"><span> PHPStan Vitest Image</span></span></code></pre></div><h2 id="GitHub-Actions" tabindex="-1">GitHub Actions <a class="header-anchor" href="#GitHub-Actions" aria-label="Permalink to &quot;GitHub Actions&quot;">​</a></h2><h3 id="后端-CI-配置" tabindex="-1">后端 CI 配置 <a class="header-anchor" href="#后端-CI-配置" aria-label="Permalink to &quot;后端 CI 配置&quot;">​</a></h3><div class="language-yaml vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .github/workflows/backend-ci.yml — 后端持续集成</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 触发条件:push 到 main/develop 分支,或向 main 发起 PR</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 执行环境:Ubuntu 最新版 + MySQL 8.0 服务容器</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 流程:代码检查 → 静态分析 → 单元测试 → 覆盖率上传</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Backend CI</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;">on</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> push</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> branches</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">main</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> develop</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # push 到这些分支时触发</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> pull_request</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> branches</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">main</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 向 main 发起 PR 时触发</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">jobs</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> test</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> runs-on</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ubuntu-latest</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 使用 GitHub 托管的最新 Ubuntu 运行器</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # -------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 服务容器(GitHub Actions 自动启动,与运行器共享网络)</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # -------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> services</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> mysql</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> image</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> mysql:8.0</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # MySQL 8.0 官方镜像</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> env</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> MYSQL_ROOT_PASSWORD</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> root</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 测试环境密码(仅用于 CI)</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> MYSQL_DATABASE</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> test_db</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 自动创建的测试数据库</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> ports</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> 3306:3306</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 暴露端口供测试连接</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 健康检查:确保 MySQL 就绪后再执行后续步骤</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> options</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> &gt;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> --health-cmd=&quot;mysqladmin ping&quot; # 用 mysqladmin ping 检测</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> --health-interval=10s # 每 10 秒检查一次</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> --health-timeout=5s # 超时时间</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> --health-retries=3 # 重试 3 次后判定失败</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> steps</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 拉取代码(checkout@v4 是 GitHub 官方 Action)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> uses</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> actions/checkout@v4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装 PHP 8.2 及所需扩展</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Setup PHP</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> uses</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> shivammathur/setup-php@v2</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 第三方 Action,自动安装 PHP</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> with</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> php-version</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">8.2</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # PHP 版本</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> extensions</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pdo_mysql, gd, redis</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装的扩展</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> coverage</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> xdebug</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 代码覆盖率工具</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装 Composer 依赖(--no-progress 隐藏进度条,--prefer-dist 下载压缩包更快)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Install Dependencies</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> composer install --no-progress --prefer-dist</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 准备运行环境</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Prepare Environment</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> |</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> cp .env.example .env # 复制环境配置模板</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> php think db:migrate # 执行数据库迁移</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 代码风格检查(PSR-12 标准)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Code Style Check</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> vendor/bin/phpcs --standard=PSR12 app/</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 静态分析(level=5 为严格模式,可发现潜在 bug)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Static Analysis</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> vendor/bin/phpstan analyse app/ --level=5</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 运行单元测试(--coverage-text 输出文本格式覆盖率报告)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Run Tests</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> php think test --coverage-text</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 上传覆盖率报告到 Codecov(免费的覆盖率追踪平台)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Upload Coverage</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> uses</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> codecov/codecov-action@v3</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> with</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> file</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ./coverage.xml</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 覆盖率报告文件</span></span></code></pre></div><h3 id="前端-CI-配置" tabindex="-1">前端 CI 配置 <a class="header-anchor" href="#前端-CI-配置" aria-label="Permalink to &quot;前端 CI 配置&quot;">​</a></h3><div class="language-yaml vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .github/workflows/frontend-ci.yml — 前端持续集成</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 触发条件:仅当 ui/ 目录下的文件变更时触发(避免后端改动触发前端 CI)</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 流程:Lint → 类型检查 → 单元测试 → 构建</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Frontend CI</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;">on</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> push</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> branches</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">main</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> develop</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> paths</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">ui/**</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 仅 ui/ 目录变更时触发</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> pull_request</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> branches</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">main</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> paths</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">ui/**</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">jobs</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> test</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> runs-on</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ubuntu-latest</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> steps</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> uses</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> actions/checkout@v4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装 Node.js 18 并配置 pnpm 缓存</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Setup Node.js</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> uses</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> actions/setup-node@v4</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> with</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> node-version</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">18</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # Node.js 版本</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> cache</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">pnpm</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 启用 pnpm 缓存(加速依赖安装)</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> cache-dependency-path</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ui/pnpm-lock.yaml</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 缓存依赖的 lockfile 路径</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 全局安装 pnpm 包管理器</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Install pnpm</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> npm install -g pnpm</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装前端依赖(--frozen-lockfile 确保版本与 lockfile 一致)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Install Dependencies</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> working-directory</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ui</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 在 ui/ 目录下执行</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm install --frozen-lockfile</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # ESLint 代码风格检查</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Lint</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> working-directory</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ui</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm lint:eslint</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # TypeScript 类型检查(发现类型错误)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Type Check</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> working-directory</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ui</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm type:check</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 运行单元测试(Vitest)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Run Tests</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> working-directory</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ui</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm test</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 生产环境构建(验证构建是否成功)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Build</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> working-directory</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ui</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm build</span></span></code></pre></div><h3 id="自动部署配置" tabindex="-1">自动部署配置 <a class="header-anchor" href="#自动部署配置" aria-label="Permalink to &quot;自动部署配置&quot;">​</a></h3><div class="language-yaml vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .github/workflows/deploy.yml — 自动部署</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 触发条件:push 到 main 分支,或打 v* 标签</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 流程:构建 Docker 镜像 → 推送到镜像仓库 → SSH 部署到服务器</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Deploy</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;">on</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> push</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> branches</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">main</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> tags</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">v*</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 匹配 v1.0.0、v2.1.3 等标签</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">jobs</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> deploy</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> runs-on</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ubuntu-latest</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> needs</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">test</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 依赖 test job 通过后才执行</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> if</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> github.ref == &#39;refs/heads/main&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 仅 main 分支触发部署</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> steps</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> uses</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> actions/checkout@v4</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 构建 Docker 镜像(用 git commit SHA 作为版本标签)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Build Docker Image</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> |</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker build -t myapp:\${{ github.sha }} . # 用 commit SHA 标记</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker tag myapp:\${{ github.sha }} myapp:latest # 同时打 latest 标签</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 推送镜像到 Docker Hub(secrets 在 GitHub 仓库设置中配置)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Push to Registry</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> |</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> echo \${{ secrets.DOCKER_PASSWORD }} | docker login -u \${{ secrets.DOCKER_USERNAME }} --password-stdin</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker push myapp:\${{ github.sha }} # 推送版本标签</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker push myapp:latest # 推送 latest 标签</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 通过 SSH 连接服务器执行部署命令</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Deploy to Server</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> uses</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> appleboy/ssh-action@v1</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 第三方 SSH Action</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> with</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> host</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> \${{ secrets.SERVER_HOST }}</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 服务器地址</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> username</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> \${{ secrets.SERVER_USER }}</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # SSH 用户名</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> key</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> \${{ secrets.SSH_KEY }}</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # SSH 私钥</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> script</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> |</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> cd /opt/myapp # 进入项目目录</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker-compose pull # 拉取最新镜像</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker-compose up -d # 重建并启动容器</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> php think db:migrate # 执行数据库迁移</span></span></code></pre></div><h2 id="GitLab-CI" tabindex="-1">GitLab CI <a class="header-anchor" href="#GitLab-CI" aria-label="Permalink to &quot;GitLab CI&quot;">​</a></h2><div class="language-yaml vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .gitlab-ci.yml — GitLab CI/CD 配置</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 阶段:test → build → deploy</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 缓存:vendor/ 和 node_modules/ 按分支缓存,加速后续构建</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># =============================================================================</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">stages</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> test</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 测试阶段</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> build</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 构建阶段</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> deploy</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 部署阶段</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 全局变量(所有 job 共享)</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">variables</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> MYSQL_ROOT_PASSWORD</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> root</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 测试数据库密码</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> MYSQL_DATABASE</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> test_db</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 测试数据库名</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># -----------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 后端测试</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># -----------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">backend:test</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> stage</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> test</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> image</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> php:8.2-cli</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # PHP 8.2 CLI 镜像</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> services</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> mysql:8.0</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # MySQL 服务容器</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> script</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装系统依赖(PHP 扩展编译需要)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> apt-get update &amp;&amp; apt-get install -y libpq-dev libzip-dev</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装 PHP 扩展</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker-php-ext-install pdo_mysql zip</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装 Composer 依赖</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> composer install --no-progress</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 准备环境并执行测试</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> cp .env.example .env</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> php think db:migrate</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 数据库迁移</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> php think test</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 运行测试</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> cache</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> key</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> \${CI_COMMIT_REF_SLUG}</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 按分支名缓存</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> paths</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> vendor/</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 缓存 Composer 依赖</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># -----------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 前端测试</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># -----------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">frontend:test</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> stage</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> test</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> image</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> node:18</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # Node.js 18 镜像</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> script</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> cd ui</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> npm install -g pnpm</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 全局安装 pnpm</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm install --frozen-lockfile</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 安装依赖</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm lint:eslint</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 代码风格检查</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm test</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 单元测试</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pnpm build</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 生产构建</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> cache</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> key</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> \${CI_COMMIT_REF_SLUG}</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> paths</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ui/node_modules/</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 缓存前端依赖</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># -----------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 生产部署(仅 main 分支)</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># -----------------------------------------------------------------------------</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">deploy:production</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> stage</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> deploy</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> only</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> main</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 仅 main 分支触发</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> script</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker build -t myapp:$CI_COMMIT_SHA .</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 构建镜像</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker push myapp:$CI_COMMIT_SHA</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 推送到仓库</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # SSH 到服务器执行部署</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> ssh $SERVER_USER@$SERVER_HOST &quot;cd /opt/myapp &amp;&amp; docker-compose pull &amp;&amp; docker-compose up -d&quot;</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> environment</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> production</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> url</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> https://api.example.com</span></span></code></pre></div><h2 id="部署策略" tabindex="-1">部署策略 <a class="header-anchor" href="#部署策略" aria-label="Permalink to &quot;部署策略&quot;">​</a></h2><h3 id="蓝绿部署" tabindex="-1">蓝绿部署 <a class="header-anchor" href="#蓝绿部署" aria-label="Permalink to &quot;蓝绿部署&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>当前版本(蓝) → 新版本(绿)</span></span>
<span class="line"><span> │ │</span></span>
<span class="line"><span> ▼ ▼</span></span>
<span class="line"><span> Nginx 切换流量 ← 验证通过</span></span></code></pre></div><h3 id="滚动更新" tabindex="-1">滚动更新 <a class="header-anchor" href="#滚动更新" aria-label="Permalink to &quot;滚动更新&quot;">​</a></h3><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># Docker Swarm 滚动更新</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># --update-parallelism 1 → 每次只更新 1 个容器实例</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># --update-delay 30s → 每批更新间隔 30 秒(观察健康状态)</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># --image myapp:v2.0 → 新版本镜像</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># myapp_web → 服务名称</span></span>
<span class="line"><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">docker</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> service</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> update</span><span style="--shiki-dark:#F286C4;--shiki-light:#A65E2B;"> \\</span></span>
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> --update-parallelism</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 1</span><span style="--shiki-dark:#F286C4;--shiki-light:#A65E2B;"> \\</span></span>
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> --update-delay</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> 30s</span><span style="--shiki-dark:#F286C4;--shiki-light:#A65E2B;"> \\</span></span>
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;"> --image</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> myapp:v2.0</span><span style="--shiki-dark:#F286C4;--shiki-light:#A65E2B;"> \\</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> myapp_web</span></span></code></pre></div><h2 id="通知集成" tabindex="-1">通知集成 <a class="header-anchor" href="#通知集成" aria-label="Permalink to &quot;通知集成&quot;">​</a></h2><h3 id="部署通知-钉钉-飞书-" tabindex="-1">部署通知(钉钉/飞书) <a class="header-anchor" href="#部署通知-钉钉-飞书-" aria-label="Permalink to &quot;部署通知(钉钉/飞书)&quot;">​</a></h3><div class="language-yaml vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">yaml</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># GitHub Actions 部署成功通知(发送到钉钉机器人)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">-</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> Notify on Success</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> if</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> success()</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 仅在部署成功时执行</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> run</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> |</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> # curl 发送 POST 请求到钉钉 Webhook</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> # -H → 设置请求头为 JSON 格式</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> # -d → 发送 JSON 消息体(Markdown 格式)</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> curl -X POST &quot;\${{ secrets.DINGTALK_WEBHOOK }}&quot; \\</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> -H &#39;Content-Type: application/json&#39; \\</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> -d &#39;{</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> &quot;msgtype&quot;: &quot;markdown&quot;,</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> &quot;markdown&quot;: {</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> &quot;title&quot;: &quot;部署成功&quot;,</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> &quot;text&quot;: &quot;## ✅ 部署成功\\n- **项目**: myapp\\n- **分支**: \${{ github.ref_name }}\\n- **提交**: \${{ github.sha }}\\n- **时间**: $(date)&quot;</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> }</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> }&#39;</span></span></code></pre></div><h2 id="相关文档" tabindex="-1">相关文档 <a class="header-anchor" href="#相关文档" aria-label="Permalink to &quot;相关文档&quot;">​</a></h2><ul><li><a href="/zh/deploy/docker.html">Docker 生产部署</a></li><li><a href="/zh/deploy/backend.html">后端部署</a></li><li><a href="/zh/deploy/frontend.html">前端部署</a></li></ul>`,23)])])}const E=i(l,[["render",h]]);export{g as __pageData,E as default};