Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/.vitepress/dist/assets/zh_deploy_ci-cd.md.FFit-2qd.js
T
2026-10-11 19:06:33 +08:00

269 lines
59 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.BbmSSMjL.js";const g=JSON.parse('{"title":"11.11 CI/CD 流水线","description":"","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><div class="info custom-block"><p class="custom-block-title">模板说明</p><p>本项目仓库当前未内置 <code>.github/workflows</code> 或 <code>.gitee/workflows</code>,以下配置为<strong>通用模板</strong>:按实际代码托管平台(GitHub / Gitee / GitLab)调整后放入对应目录即可使用。</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 Pint PHPUnit Docker 生产环境</span></span>
<span class="line"><span> ESLint (sqlite) 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 最新版</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;"># 说明:phpunit.xml 已配置 sqlite :memory: 测试库,无需外部数据库服务</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:#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.3 及所需扩展</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.3</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # PHP 版本(Laravel 13 要求)</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, mbstring, gd, bcmath, zip, redis</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 artisan key:generate # 生成应用密钥</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 代码风格检查(Laravel Pint,PSR-12 + Laravel 风格)</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/pint --test</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 运行单元测试(composer test = config:clear + artisan test,</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # phpunit.xml 使用 sqlite :memory:,不依赖 MySQL)</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;"> composer 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><div class="tip custom-block"><p class="custom-block-title">无外部依赖的测试</p><p><code>phpunit.xml</code> 将 <code>DB_CONNECTION</code> 设为 <code>sqlite</code>、<code>DB_DATABASE</code> 设为 <code>:memory:</code>,单元测试在内存库中运行,CI 无需启动 MySQL 容器。如需对真实 MySQL 做集成测试,再参考下文 GitLab CI 的 services 配置。</p></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 22 并配置 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;">22</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # Node.js 版本(22 LTS;根目录 Vite 7 要求 20.19+/22.12+)</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 代码风格检查(脚本自带 --fix,会自动修复可修复问题)</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;"> # 生产环境构建(验证构建是否成功)</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><div class="info custom-block"><p class="custom-block-title">前端暂无单元测试脚本</p><p><code>ui/package.json</code> 未提供 <code>test</code> 脚本(如后续引入 Vitest,再补 <code>pnpm test</code> 步骤)。</p></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:#7B7F8B;--shiki-light:#A0ADA0;"> # 项目自带 Dockerfile 与 docker-compose.yml(含 docker/.env.docker)</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 --env-file docker/.env.docker pull # 拉取最新镜像</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> docker compose --env-file docker/.env.docker up -d --build</span></span>
<span class="line"><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> # 数据库结构变更:手动执行 document/mysql 下的 SQL 脚本(本项目无 migration)</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.3-cli</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # PHP 8.3 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 服务容器(集成测试用;单元测试走 sqlite 内存库)</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:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> pecl install redis &amp;&amp; docker-php-ext-enable redis</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 artisan key:generate</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> -</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;"> vendor/bin/pint --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;"> composer test</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # 运行测试(phpunit.xml 用 sqlite :memory:)</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:22</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> # Node.js 22 镜像</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 type:check</span><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:#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 --env-file docker/.env.docker up -d --build&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>`,26)])])}const c=i(l,[["render",h]]);export{g as __pageData,c as default};