<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://sakura-hu.top/</id>
  <title type="text">Sakura</title>
  <subtitle type="text">blog</subtitle>
  <updated>2026-09-12T00:00:00.000Z</updated>
  <author><name>Sakura</name></author>
  <link rel="alternate" href="https://sakura-hu.top/"/>
  <link rel="self" href="https://sakura-hu.top/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://sakura-hu.top/posts/%E6%9D%82%E9%A1%B9/%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF%E7%94%9F%E6%80%81%E5%9C%B0%E5%9B%BE/</id>
      <title type="text">软件开发技术生态地图(claude)</title>
      <published>2026-04-19T00:00:00.000Z</published>
      <updated>2026-04-19T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E6%9D%82%E9%A1%B9/%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E6%8A%80%E6%9C%AF%E7%94%9F%E6%80%81%E5%9C%B0%E5%9B%BE/"/>
      <summary type="text">软件开发的技术生态地图</summary>
      <content type="html"><![CDATA[<section><h2>📌 前言<a href="#-前言"><span>#</span></a></h2><p>这份文档将软件开发的众多技术栈按<strong>层级和功能</strong>分类，帮助你理解它们的关系和应用场景。</p><hr /></section>
<section><h2>第一部分：核心分层架构<a href="#第一部分核心分层架构"><span>#</span></a></h2><p>软件开发从下到上分为以下几个层次：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>┌──────────────────────────────────────────────────┐</span></div></div><div><div><div>2</div></div><div><span>│           用户界面层 (UI/UX)                      │</span></div></div><div><div><div>3</div></div><div><span>├──────────────────────────────────────────────────┤</span></div></div><div><div><div>4</div></div><div><span>│     前端框架层 (Web/Mobile Frontend)              │</span></div></div><div><div><div>5</div></div><div><span>├──────────────────────────────────────────────────┤</span></div></div><div><div><div>6</div></div><div><span>│        后端服务层 (Backend Services)              │</span></div></div><div><div><div>7</div></div><div><span>├──────────────────────────────────────────────────┤</span></div></div><div><div><div>8</div></div><div><span>│      数据存储层 (Databases &amp; Cache)               │</span></div></div><div><div><div>9</div></div><div><span>├──────────────────────────────────────────────────┤</span></div></div><div><div><div>10</div></div><div><span>│    基础设施层 (DevOps &amp; Cloud)                    │</span></div></div><div><div><div>11</div></div><div><span>├──────────────────────────────────────────────────┤</span></div></div><div><div><div>12</div></div><div><span>│      操作系统 &amp; 硬件层                            │</span></div></div><div><div><div>13</div></div><div><span>└──────────────────────────────────────────────────┘</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h2>第二部分：详细技术生态<a href="#第二部分详细技术生态"><span>#</span></a></h2><section><h3>1️⃣ 前端开发技术栈<a href="#1️⃣-前端开发技术栈"><span>#</span></a></h3><section><h4>核心语言<a href="#核心语言"><span>#</span></a></h4><ul>
<li><strong>JavaScript/TypeScript</strong> - 前端最流行的编程语言
<ul>
<li>TypeScript：JavaScript的超集，添加了类型系统</li>
</ul>
</li>
</ul></section><section><h4>前端框架（三大巨头）<a href="#前端框架三大巨头"><span>#</span></a></h4>

<table><thead><tr><th>框架</th><th>特点</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>React</strong></td><td>组件化、单向数据流、生态最强</td><td>SPA、复杂交互应用</td></tr><tr><td><strong>Vue</strong></td><td>渐进式、学习曲线平缓、中文文档好</td><td>中小型项目、快速开发</td></tr><tr><td><strong>Angular</strong></td><td>企业级、功能完整、学习陡峭</td><td>大型企业应用</td></tr></tbody></table></section><section><h4>轻量级框架<a href="#轻量级框架"><span>#</span></a></h4><ul>
<li><strong>Svelte</strong> - 编译时框架，性能优秀</li>
<li><strong>Qwik</strong> - 可恢复性框架，快速交互时间</li>
</ul></section><section><h4>状态管理<a href="#状态管理"><span>#</span></a></h4>

<table><thead><tr><th>工具</th><th>用途</th><th>适用</th></tr></thead><tbody><tr><td><strong>Redux</strong></td><td>集中式状态管理</td><td>React大型应用</td></tr><tr><td><strong>Vuex/Pinia</strong></td><td>Vue状态管理</td><td>Vue应用</td></tr><tr><td><strong>MobX</strong></td><td>响应式状态管理</td><td>React应用</td></tr><tr><td><strong>Zustand</strong></td><td>轻量级状态管理</td><td>中小型项目</td></tr><tr><td><strong>TanStack Query</strong></td><td>数据获取和缓存</td><td>任何框架</td></tr></tbody></table></section><section><h4>路由管理<a href="#路由管理"><span>#</span></a></h4><ul>
<li><strong>React Router</strong> - React生态最流行</li>
<li><strong>Vue Router</strong> - Vue官方路由</li>
<li><strong>Next.js / Nuxt</strong> - 全栈框架包含路由</li>
</ul></section><section><h4>UI组件库<a href="#ui组件库"><span>#</span></a></h4><ul>
<li><strong>Material-UI (MUI)</strong> - Material Design设计系统</li>
<li><strong>Ant Design</strong> - 企业级UI库（中国最受欢迎）</li>
<li><strong>Shadcn/ui</strong> - 无头组件库</li>
<li><strong>Bootstrap</strong> - 传统响应式设计</li>
<li><strong>Tailwind CSS</strong> - 原子化CSS框架（现在最流行）</li>
</ul></section><section><h4>样式方案<a href="#样式方案"><span>#</span></a></h4><ul>
<li><strong>CSS Modules</strong> - 局部作用域样式</li>
<li><strong>CSS-in-JS</strong> (Styled Components, Emotion)</li>
<li><strong>Tailwind CSS</strong> - 功能类优先</li>
<li><strong>SASS/LESS</strong> - CSS预处理器</li>
</ul></section><section><h4>构建工具（打包工具）<a href="#构建工具打包工具"><span>#</span></a></h4>

<table><thead><tr><th>工具</th><th>特点</th><th>现状</th></tr></thead><tbody><tr><td><strong>Webpack</strong></td><td>功能最完整、配置复杂</td><td>逐渐被替代</td></tr><tr><td><strong>Vite</strong></td><td>极速开发体验、现代工具</td><td>⭐ 快速替代Webpack</td></tr><tr><td><strong>Turbopack</strong></td><td>Next.js团队开发、超高速</td><td>新兴工具</td></tr><tr><td><strong>esbuild</strong></td><td>Go编写、性能最快</td><td>用于转译</td></tr><tr><td><strong>Rollup</strong></td><td>库打包最优</td><td>库开发首选</td></tr></tbody></table></section><section><h4>全栈框架（包含前后端）<a href="#全栈框架包含前后端"><span>#</span></a></h4><ul>
<li><strong>Next.js</strong> (React) - 最火的全栈框架</li>
<li><strong>Nuxt</strong> (Vue) - Vue的全栈方案</li>
<li><strong>SvelteKit</strong> (Svelte)</li>
<li><strong>Remix</strong> (React) - 现代Web框架</li>
</ul><hr /></section></section><section><h3>2️⃣ 后端开发技术栈<a href="#2️⃣-后端开发技术栈"><span>#</span></a></h3><section><h4>主流后端编程语言<a href="#主流后端编程语言"><span>#</span></a></h4>

<table><thead><tr><th>语言</th><th>特点</th><th>应用</th></tr></thead><tbody><tr><td><strong>Node.js</strong> (JavaScript)</td><td>非阻塞IO、前后端统一</td><td>Web服务、API、实时应用</td></tr><tr><td><strong>Python</strong></td><td>简洁易学、库丰富</td><td>Web、AI/ML、数据科学</td></tr><tr><td><strong>Java</strong></td><td>强类型、企业级、生态完整</td><td>大型企业应用、微服务</td></tr><tr><td><strong>Go</strong></td><td>高性能、并发优秀、编译快</td><td>系统软件、微服务、DevOps</td></tr><tr><td><strong>C#/.NET</strong></td><td>微软生态、功能完整</td><td>Windows应用、企业服务</td></tr><tr><td><strong>Ruby</strong></td><td>优雅简洁、快速开发</td><td>Web应用（逐渐衰落）</td></tr><tr><td><strong>PHP</strong></td><td>Web应用起源、快速部署</td><td>中小型网站</td></tr><tr><td><strong>Rust</strong></td><td>内存安全、性能最优</td><td>系统编程、高性能服务</td></tr></tbody></table></section><section><h4>Node.js Web框架<a href="#nodejs-web框架"><span>#</span></a></h4>

<table><thead><tr><th>框架</th><th>特点</th><th>规模</th></tr></thead><tbody><tr><td><strong>Express</strong></td><td>简洁、灵活、生态庞大</td><td>⭐ 最流行</td></tr><tr><td><strong>Koa</strong></td><td>轻量、中间件优雅</td><td>中等</td></tr><tr><td><strong>Nest.js</strong></td><td>企业级、TypeScript优先、结构化</td><td>大型项目</td></tr><tr><td><strong>Fastify</strong></td><td>高性能、JSON Schema验证</td><td>性能优先</td></tr><tr><td><strong>Hono</strong></td><td>边缘计算友好</td><td>新兴</td></tr></tbody></table></section><section><h4>Python Web框架<a href="#python-web框架"><span>#</span></a></h4>

<table><thead><tr><th>框架</th><th>特点</th><th>规模</th></tr></thead><tbody><tr><td><strong>Django</strong></td><td>大而全、内置功能多、生态强</td><td>大型应用</td></tr><tr><td><strong>Flask</strong></td><td>轻量、灵活、学习曲线缓</td><td>中小型</td></tr><tr><td><strong>FastAPI</strong></td><td>现代、高性能、自动文档</td><td>⭐ 新兴流行</td></tr><tr><td><strong>Pyramid</strong></td><td>灵活可扩展</td><td>中大型</td></tr></tbody></table></section><section><h4>Java框架生态<a href="#java框架生态"><span>#</span></a></h4><ul>
<li><strong>Spring Boot</strong> - 现代Java开发标准</li>
<li><strong>Spring Cloud</strong> - 微服务架构</li>
<li><strong>Quarkus</strong> - 轻量级、云原生</li>
<li><strong>Micronaut</strong> - 微服务框架</li>
</ul></section><section><h4>认证和授权<a href="#认证和授权"><span>#</span></a></h4><ul>
<li><strong>JWT (JSON Web Token)</strong> - 无状态认证标准</li>
<li><strong>OAuth 2.0</strong> - 第三方授权标准</li>
<li><strong>Session-Based Auth</strong> - 传统会话认证</li>
<li><strong>Passport.js</strong> - Node认证中间件库</li>
</ul></section><section><h4>API设计标准<a href="#api设计标准"><span>#</span></a></h4><ul>
<li><strong>REST</strong> - 资源表述传输（最常用）</li>
<li><strong>GraphQL</strong> - 查询语言（精确数据获取）</li>
<li><strong>gRPC</strong> - 高性能RPC框架</li>
<li><strong>WebSocket</strong> - 双向实时通信</li>
</ul><hr /></section></section><section><h3>3️⃣ 移动开发技术栈<a href="#3️⃣-移动开发技术栈"><span>#</span></a></h3><section><h4>iOS开发<a href="#ios开发"><span>#</span></a></h4><ul>
<li><strong>Swift</strong> - 苹果官方语言</li>
<li><strong>Objective-C</strong> - 传统苹果语言（逐渐退出）</li>
</ul></section><section><h4>Android开发<a href="#android开发"><span>#</span></a></h4><ul>
<li><strong>Kotlin</strong> - 谷歌官方推荐语言</li>
<li><strong>Java</strong> - 传统Android开发</li>
</ul></section><section><h4>跨平台开发<a href="#跨平台开发"><span>#</span></a></h4>

<table><thead><tr><th>方案</th><th>基础</th><th>特点</th></tr></thead><tbody><tr><td><strong>React Native</strong></td><td>JavaScript</td><td>Facebook维护、学习曲线缓</td></tr><tr><td><strong>Flutter</strong></td><td>Dart</td><td>谷歌开发、性能好、视觉效果强</td></tr><tr><td><strong>Xamarin</strong></td><td>C#</td><td>微软方案</td></tr><tr><td><strong>Ionic</strong></td><td>Web技术</td><td>混合应用</td></tr></tbody></table></section><section><h4>小程序开发<a href="#小程序开发"><span>#</span></a></h4><ul>
<li><strong>微信小程序</strong> - 中国最主流</li>
<li><strong>支付宝小程序</strong></li>
<li><strong>抖音小程序</strong></li>
<li><strong>Taro/uni-app</strong> - 跨小程序框架</li>
</ul><hr /></section></section><section><h3>4️⃣ 数据存储技术栈<a href="#4️⃣-数据存储技术栈"><span>#</span></a></h3><section><h4>关系型数据库（SQL）<a href="#关系型数据库sql"><span>#</span></a></h4>

<table><thead><tr><th>数据库</th><th>特点</th><th>适用</th></tr></thead><tbody><tr><td><strong>MySQL</strong></td><td>开源、稳定、应用最广</td><td>⭐ 互联网应用首选</td></tr><tr><td><strong>PostgreSQL</strong></td><td>功能最完整、可靠性最高</td><td>大型应用、复杂查询</td></tr><tr><td><strong>Oracle</strong></td><td>企业级、功能强大、昂贵</td><td>大型企业</td></tr><tr><td><strong>SQL Server</strong></td><td>微软生态</td><td>Windows企业应用</td></tr><tr><td><strong>MariaDB</strong></td><td>MySQL分支、更开放</td><td>MySQL替代品</td></tr></tbody></table></section><section><h4>NoSQL数据库<a href="#nosql数据库"><span>#</span></a></h4><p><strong>文档型</strong></p><ul>
<li><strong>MongoDB</strong> - 最流行NoSQL、JSON文档</li>
<li><strong>CouchDB</strong> - 分布式JSON存储</li>
</ul><p><strong>键值对存储</strong></p><ul>
<li><strong>Redis</strong> - 高速缓存、会话存储、消息队列</li>
<li><strong>Memcached</strong> - 简单缓存（逐渐被Redis替代）</li>
<li><strong>DynamoDB</strong> - AWS托管键值存储</li>
</ul><p><strong>搜索引擎</strong></p><ul>
<li><strong>Elasticsearch</strong> - 全文搜索和日志分析（最流行）</li>
<li><strong>Solr</strong> - Lucene搜索平台</li>
<li><strong>Meilisearch</strong> - 轻量级搜索</li>
</ul><p><strong>时间序列数据库</strong></p><ul>
<li><strong>InfluxDB</strong> - 时序数据存储</li>
<li><strong>Prometheus</strong> - 监控指标存储</li>
<li><strong>TimescaleDB</strong> - PostgreSQL扩展</li>
</ul><p><strong>图数据库</strong></p><ul>
<li><strong>Neo4j</strong> - 图数据库领先者</li>
<li><strong>ArangoDB</strong> - 多模型数据库</li>
</ul></section><section><h4>缓存策略<a href="#缓存策略"><span>#</span></a></h4><ul>
<li><strong>Redis</strong> - 分布式缓存标准</li>
<li><strong>Memcached</strong> - 传统缓存</li>
<li><strong>CDN</strong> - 内容分发网络缓存</li>
</ul></section><section><h4>消息队列<a href="#消息队列"><span>#</span></a></h4>

<table><thead><tr><th>消息队列</th><th>特点</th><th>应用</th></tr></thead><tbody><tr><td><strong>RabbitMQ</strong></td><td>可靠性最高、功能完整</td><td>企业应用</td></tr><tr><td><strong>Apache Kafka</strong></td><td>高吞吐、分布式流处理</td><td>大数据、日志系统</td></tr><tr><td><strong>Redis Streams</strong></td><td>轻量、快速</td><td>中小型应用</td></tr><tr><td><strong>AWS SQS</strong></td><td>云原生</td><td>AWS生态</td></tr><tr><td><strong>RocketMQ</strong></td><td>阿里开源、性能好</td><td>中文社区</td></tr></tbody></table><hr /></section></section><section><h3>5️⃣ DevOps和基础设施<a href="#5️⃣-devops和基础设施"><span>#</span></a></h3><section><h4>容器化技术<a href="#容器化技术"><span>#</span></a></h4><ul>
<li><strong>Docker</strong> - 容器标准（必须掌握）</li>
<li><strong>Podman</strong> - Docker替代品</li>
</ul></section><section><h4>容器编排<a href="#容器编排"><span>#</span></a></h4><ul>
<li><strong>Kubernetes (K8s)</strong> - 容器编排标准（复杂但强大）</li>
<li><strong>Docker Compose</strong> - 多容器管理（开发常用）</li>
<li><strong>Docker Swarm</strong> - Docker原生编排</li>
</ul></section><section><h4>云平台<a href="#云平台"><span>#</span></a></h4>

<table><thead><tr><th>平台</th><th>特点</th><th>占有率</th></tr></thead><tbody><tr><td><strong>AWS</strong></td><td>功能最完整、生态最大</td><td>⭐ 全球第一</td></tr><tr><td><strong>Azure</strong></td><td>微软生态、企业友好</td><td>企业常用</td></tr><tr><td><strong>Google Cloud</strong></td><td>数据分析强、AI能力强</td><td>技术优先</td></tr><tr><td><strong>阿里云</strong></td><td>中国最大、本地化好</td><td>中国首选</td></tr><tr><td><strong>腾讯云</strong></td><td>游戏云服务强</td><td>中国第二</td></tr></tbody></table></section><section><h4>基础设施即代码 (IaC)<a href="#基础设施即代码-iac"><span>#</span></a></h4><ul>
<li><strong>Terraform</strong> - 最流行的IaC工具</li>
<li><strong>CloudFormation</strong> - AWS官方IaC</li>
<li><strong>Ansible</strong> - 配置管理</li>
<li><strong>Chef/Puppet</strong> - 服务器配置</li>
</ul></section><section><h4>CI/CD工具<a href="#cicd工具"><span>#</span></a></h4>

<table><thead><tr><th>工具</th><th>特点</th><th>流行度</th></tr></thead><tbody><tr><td><strong>GitHub Actions</strong></td><td>GitHub原生、免费</td><td>⭐ 快速流行</td></tr><tr><td><strong>GitLab CI/CD</strong></td><td>GitLab原生、功能完整</td><td>企业常用</td></tr><tr><td><strong>Jenkins</strong></td><td>开源、功能强大、配置复杂</td><td>传统企业</td></tr><tr><td><strong>CircleCI</strong></td><td>SaaS服务、易用</td><td>中小型团队</td></tr><tr><td><strong>Travis CI</strong></td><td>开源项目常用</td><td>开源项目</td></tr></tbody></table></section><section><h4>监控和日志<a href="#监控和日志"><span>#</span></a></h4>

<table><thead><tr><th>工具</th><th>用途</th></tr></thead><tbody><tr><td><strong>ELK Stack</strong> (Elasticsearch, Logstash, Kibana)</td><td>日志收集、分析、可视化</td></tr><tr><td><strong>Prometheus + Grafana</strong></td><td>指标监控和可视化</td></tr><tr><td><strong>Datadog</strong></td><td>全面监控SaaS平台</td></tr><tr><td><strong>New Relic</strong></td><td>应用性能监控</td></tr></tbody></table><hr /></section></section><section><h3>6️⃣ 数据科学和AI/ML<a href="#6️⃣-数据科学和aiml"><span>#</span></a></h3><section><h4>数据科学工具<a href="#数据科学工具"><span>#</span></a></h4><ul>
<li><strong>Python</strong> - 主要语言</li>
<li><strong>Jupyter Notebook</strong> - 交互式编程环境</li>
<li><strong>Pandas</strong> - 数据处理</li>
<li><strong>NumPy</strong> - 数值计算</li>
<li><strong>Scikit-learn</strong> - 机器学习库</li>
<li><strong>Matplotlib/Seaborn</strong> - 数据可视化</li>
</ul></section><section><h4>深度学习框架<a href="#深度学习框架"><span>#</span></a></h4>

<table><thead><tr><th>框架</th><th>特点</th><th>流行度</th></tr></thead><tbody><tr><td><strong>TensorFlow</strong></td><td>Google开发、应用最广</td><td>⭐ 生产环境首选</td></tr><tr><td><strong>PyTorch</strong></td><td>Meta开发、研究优先</td><td>⭐ 研究首选</td></tr><tr><td><strong>JAX</strong></td><td>高性能计算</td><td>新兴</td></tr><tr><td><strong>Keras</strong></td><td>高级API（多数集成在TensorFlow中）</td><td>入门友好</td></tr></tbody></table></section><section><h4>大语言模型相关<a href="#大语言模型相关"><span>#</span></a></h4><ul>
<li><strong>OpenAI API</strong> - ChatGPT, GPT-4</li>
<li><strong>LangChain</strong> - LLM应用开发框架</li>
<li><strong>LlamaIndex</strong> - 数据索引和检索</li>
<li><strong>HuggingFace</strong> - 开源模型平台</li>
</ul><hr /></section></section></section>
<section><h2>第三部分：技术栈组合方案<a href="#第三部分技术栈组合方案"><span>#</span></a></h2><section><h3>常见的生产级技术栈组合<a href="#常见的生产级技术栈组合"><span>#</span></a></h3><section><h4>方案1：全栈JavaScript (现代创业公司常用)<a href="#方案1全栈javascript-现代创业公司常用"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>前端：React/Vue + Tailwind CSS + TypeScript</span></div></div><div><div><div>2</div></div><div><span>后端：Node.js + Express/Nest.js + TypeScript</span></div></div><div><div><div>3</div></div><div><span>数据库：MongoDB + Redis</span></div></div><div><div><div>4</div></div><div><span>部署：Docker + GitHub Actions + AWS/DigitalOcean</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>方案2：Python数据+Web (创业和中型公司)<a href="#方案2python数据web-创业和中型公司"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>前端：React/Vue + Tailwind CSS</span></div></div><div><div><div>2</div></div><div><span>后端：Python + FastAPI/Django</span></div></div><div><div><div>3</div></div><div><span>数据库：PostgreSQL + Redis</span></div></div><div><div><div>4</div></div><div><span>数据科学：Pandas + Scikit-learn</span></div></div><div><div><div>5</div></div><div><span>部署：Docker + GitHub Actions + AWS</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>方案3：Java企业级 (大型公司)<a href="#方案3java企业级-大型公司"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>前端：React + TypeScript + Ant Design</span></div></div><div><div><div>2</div></div><div><span>后端：Spring Boot + Spring Cloud (微服务)</span></div></div><div><div><div>3</div></div><div><span>数据库：MySQL + Redis + Elasticsearch</span></div></div><div><div><div>4</div></div><div><span>消息队列：Kafka</span></div></div><div><div><div>5</div></div><div><span>监控：Prometheus + Grafana</span></div></div><div><div><div>6</div></div><div><span>部署：Kubernetes + Jenkins</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>方案4：Go高性能 (互联网大厂)<a href="#方案4go高性能-互联网大厂"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>后端：Go + Gin框架</span></div></div><div><div><div>2</div></div><div><span>数据库：PostgreSQL + Redis</span></div></div><div><div><div>3</div></div><div><span>消息队列：Kafka</span></div></div><div><div><div>4</div></div><div><span>缓存：Redis</span></div></div><div><div><div>5</div></div><div><span>部署：Docker + Kubernetes</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>方案5：移动端 (移动优先)<a href="#方案5移动端-移动优先"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>跨平台：Flutter (推荐) 或 React Native</span></div></div><div><div><div>2</div></div><div><span>后端：Node.js/Python/Go</span></div></div><div><div><div>3</div></div><div><span>数据库：Firebase 或 PostgreSQL</span></div></div><div><div><div>4</div></div><div><span>推送：Firebase Cloud Messaging</span></div></div><div><div><div>5</div></div><div><span>分析：Firebase Analytics</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section></section>
<section><h2>第四部分：如何选择技术栈<a href="#第四部分如何选择技术栈"><span>#</span></a></h2><section><h3>选择标准<a href="#选择标准"><span>#</span></a></h3>

<table><thead><tr><th>因素</th><th>考虑点</th></tr></thead><tbody><tr><td><strong>项目规模</strong></td><td>小 → 简洁方案；大 → 可扩展方案</td></tr><tr><td><strong>团队技能</strong></td><td>优先选择团队熟悉的技术</td></tr><tr><td><strong>性能需求</strong></td><td>高并发 → Go/Java；一般 → Node/Python</td></tr><tr><td><strong>时间压力</strong></td><td>紧 → 全栈框架；充足 → 自主选择</td></tr><tr><td><strong>成本</strong></td><td>云成本、人力成本</td></tr><tr><td><strong>生态</strong></td><td>社区、库、工具、文档</td></tr><tr><td><strong>学习曲线</strong></td><td>难度、学习成本</td></tr></tbody></table></section><section><h3>不同场景推荐<a href="#不同场景推荐"><span>#</span></a></h3><p><strong>初创公司</strong> → React/Vue + Node.js + PostgreSQL + Firebase部署<br />
<strong>SaaS应用</strong> → Next.js全栈 + PostgreSQL + Vercel部署<br />
<strong>高并发系统</strong> → Go/Java + MySQL + Redis + Kubernetes<br />
<strong>数据密集型</strong> → Python + PostgreSQL + Elasticsearch<br />
<strong>移动应用</strong> → Flutter (iOS+Android一套代码)<br />
<strong>企业系统</strong> → Spring Boot + Oracle + Kubernetes</p><hr /></section></section>
<section><h2>第五部分：技术学习路线图<a href="#第五部分技术学习路线图"><span>#</span></a></h2><section><h3>初级开发者学习路线<a href="#初级开发者学习路线"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1. 学习HTML/CSS/JavaScript基础 (1-2周)</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>3</div></div><div><span>2. 选一个前端框架深入学习 (2-3个月)</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>- React 或 Vue (推荐React)</span></div></div><div><div><div>5</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>6</div></div><div><span>3. 学习后端基础 (2-3个月)</span></div></div><div><div><div>7</div></div><div><span><span>   </span></span><span>- Node.js + Express 或 Python + Flask</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>9</div></div><div><span>4. 学习数据库 (1-2个月)</span></div></div><div><div><div>10</div></div><div><span><span>   </span></span><span>- MySQL/PostgreSQL + Redis</span></div></div><div><div><div>11</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>12</div></div><div><span>5. 做一个完整的全栈项目 (1个月)</span></div></div><div><div><div>13</div></div><div><span><span>   </span></span><span>↓</span></div></div><div><div><div>14</div></div><div><span>6. 学习部署上线 (2-3周)</span></div></div><div><div><div>15</div></div><div><span><span>   </span></span><span>- Docker + GitHub Actions</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>中级开发者进阶路线<a href="#中级开发者进阶路线"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>1. 深入学习选定的框架生态</span></div></div><div><div><div>2</div></div><div><span>2. 学习架构设计和系统设计</span></div></div><div><div><div>3</div></div><div><span>3. 学习高级后端技能</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>- 微服务架构</span></div></div><div><div><div>5</div></div><div><span><span>   </span></span><span>- 消息队列</span></div></div><div><div><div>6</div></div><div><span><span>   </span></span><span>- 缓存策略</span></div></div><div><div><div>7</div></div><div><span>4. 学习DevOps和部署</span></div></div><div><div><div>8</div></div><div><span><span>   </span></span><span>- Kubernetes</span></div></div><div><div><div>9</div></div><div><span><span>   </span></span><span>- 监控告警</span></div></div><div><div><div>10</div></div><div><span>5. 选择一个专向深入</span></div></div><div><div><div>11</div></div><div><span><span>   </span></span><span>- 前端专家 → 性能优化、设计系统</span></div></div><div><div><div>12</div></div><div><span><span>   </span></span><span>- 后端专家 → 架构、并发、性能</span></div></div><div><div><div>13</div></div><div><span><span>   </span></span><span>- 全栈专家 → 系统设计、产品思维</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h2>第六部分：常见问题解答<a href="#第六部分常见问题解答"><span>#</span></a></h2><section><h3>Q: React vs Vue选哪个？<a href="#q-react-vs-vue选哪个"><span>#</span></a></h3><p><strong>A:</strong></p><ul>
<li><strong>React</strong>: 生态最强、工作机会最多、学习资源最丰富 → <strong>推荐初学者</strong></li>
<li><strong>Vue</strong>: 学习曲线更平缓、中文文档好、国内使用广泛</li>
<li><strong>选择标准</strong>: 看公司技术栈、团队技能、个人兴趣</li>
</ul></section><section><h3>Q: Node.js 还是 Python 做后端？<a href="#q-nodejs-还是-python-做后端"><span>#</span></a></h3><p><strong>A:</strong></p><ul>
<li><strong>Node.js</strong>: 前后端统一、实时应用好、性能可以 → 全栈开发首选</li>
<li><strong>Python</strong>: 库最多、学习最简单、AI/ML生态强 → 数据导向项目首选</li>
<li><strong>建议</strong>: 先学一个深入，再学另一个会很快</li>
</ul></section><section><h3>Q: 一定要学Kubernetes吗？<a href="#q-一定要学kubernetes吗"><span>#</span></a></h3><p><strong>A:</strong></p><ul>
<li><strong>小团队</strong>: 不需要，Docker Compose + 云平台就够</li>
<li><strong>中大型</strong>: 必须学，是业界标准</li>
<li><strong>学习时机</strong>: 后端经验2年以上再学</li>
</ul></section><section><h3>Q: 什么时候该学习DevOps？<a href="#q-什么时候该学习devops"><span>#</span></a></h3><p><strong>A:</strong></p><ul>
<li><strong>最早</strong>: 学会Docker、基本的CI/CD (半年经验后)</li>
<li><strong>深入</strong>: 1-2年后学Kubernetes和完整DevOps体系</li>
<li><strong>完全可选</strong>: 专业DevOps工程师才需要深入精通</li>
</ul></section><section><h3>Q: 开源的技术栈和商业的有什么区别？<a href="#q-开源的技术栈和商业的有什么区别"><span>#</span></a></h3><p><strong>A:</strong></p><ul>
<li><strong>开源</strong>: 免费、社区支持、可自定义、适合初创</li>
<li><strong>商业</strong>: 官方支持、更稳定、企业友好、适合大公司</li>
<li><strong>趋势</strong>: 越来越多优秀开源替代商业方案</li>
</ul><hr /></section></section>
<section><h2>第七部分：技术栈演变趋势<a href="#第七部分技术栈演变趋势"><span>#</span></a></h2><section><h3>近5年主要变化<a href="#近5年主要变化"><span>#</span></a></h3><ul>
<li>✅ <strong>Node.js全栈</strong> 成为主流</li>
<li>✅ <strong>TypeScript</strong> 逐渐成为标准</li>
<li>✅ <strong>Vite</strong> 快速替代Webpack</li>
<li>✅ <strong>Next.js/Nuxt</strong> 等全栈框架爆发</li>
<li>✅ <strong>Go语言</strong> 在后端和DevOps领域崛起</li>
<li>✅ <strong>Kubernetes</strong> 成为容器编排标准</li>
<li>✅ <strong>AI/LLM</strong> 集成成为新常态</li>
</ul></section><section><h3>未来方向<a href="#未来方向"><span>#</span></a></h3><ul>
<li>🚀 <strong>边缘计算</strong> - 更接近用户的计算</li>
<li>🚀 <strong>Serverless/FaaS</strong> - 无服务器架构</li>
<li>🚀 <strong>WebAssembly</strong> - 浏览器中运行高性能代码</li>
<li>🚀 <strong>AI自动化开发</strong> - AI辅助编码加速</li>
<li>🚀 <strong>低代码/无代码</strong> - 降低开发门槛</li>
<li>🚀 <strong>Rust</strong> - 系统编程和高性能应用</li>
</ul><hr /></section></section>
<section><h2>总结：你需要掌握的核心技能<a href="#总结你需要掌握的核心技能"><span>#</span></a></h2><section><h3>必学（所有开发者）<a href="#必学所有开发者"><span>#</span></a></h3><ul>
<li>一门编程语言深入学习</li>
<li>一个前端框架（如果做Web开发）</li>
<li>关系型数据库设计和SQL</li>
<li>版本控制（Git）</li>
<li>基本的HTTP和API概念</li>
</ul></section><section><h3>重要（根据职业方向）<a href="#重要根据职业方向"><span>#</span></a></h3><ul>
<li>状态管理（如果做前端）</li>
<li>一个后端框架（如果做全栈）</li>
<li>缓存和消息队列（后端）</li>
<li>Docker容器化（DevOps）</li>
<li>基本的系统设计（2年+经验）</li>
</ul></section><section><h3>进阶（职业发展）<a href="#进阶职业发展"><span>#</span></a></h3><ul>
<li>选定一个方向专深（前端/后端/DevOps）</li>
<li>架构设计和性能优化</li>
<li>微服务和分布式系统</li>
<li>Kubernetes容器编排</li>
<li>监控告警体系</li>
</ul><hr /></section></section>
<section><h2>快速参考：技术分类速查表<a href="#快速参考技术分类速查表"><span>#</span></a></h2>

<table><thead><tr><th>分类</th><th>推荐</th><th>备选</th><th>观察</th></tr></thead><tbody><tr><td><strong>Web前端</strong></td><td>React</td><td>Vue</td><td>Svelte</td></tr><tr><td><strong>后端(JS)</strong></td><td>Express/Nest</td><td>Fastify</td><td>Hono</td></tr><tr><td><strong>后端(Py)</strong></td><td>FastAPI</td><td>Django</td><td>Flask</td></tr><tr><td><strong>后端(Go)</strong></td><td>Gin</td><td>Echo</td><td>Fiber</td></tr><tr><td><strong>数据库</strong></td><td>PostgreSQL</td><td>MySQL</td><td>MongoDB</td></tr><tr><td><strong>缓存</strong></td><td>Redis</td><td>Memcached</td><td>-</td></tr><tr><td><strong>搜索</strong></td><td>Elasticsearch</td><td>Meilisearch</td><td>-</td></tr><tr><td><strong>消息队列</strong></td><td>Kafka</td><td>RabbitMQ</td><td>Redis</td></tr><tr><td><strong>容器</strong></td><td>Docker</td><td>Podman</td><td>-</td></tr><tr><td><strong>编排</strong></td><td>Kubernetes</td><td>Docker Compose</td><td>-</td></tr><tr><td><strong>CI/CD</strong></td><td>GitHub Actions</td><td>GitLab CI</td><td>Jenkins</td></tr><tr><td><strong>UI组件</strong></td><td>Shadcn/ui</td><td>Ant Design</td><td>MUI</td></tr><tr><td><strong>CSS方案</strong></td><td>Tailwind</td><td>CSS Modules</td><td>Styled Components</td></tr></tbody></table><hr /></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ai%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8-python%E9%87%8D%E7%82%B9%E7%AC%94%E8%AE%B0/</id>
      <title type="text">AI大模型开发入门 Python 重点笔记</title>
      <published>2026-09-12T00:00:00.000Z</published>
      <updated>2026-09-12T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ai%E5%A4%A7%E6%A8%A1%E5%9E%8B%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8-python%E9%87%8D%E7%82%B9%E7%AC%94%E8%AE%B0/"/>
      <summary type="text">Python 与 AI 大模型开发重点笔记：基础语法、逻辑判断、循环、函数、数据容器，以及用 Python 调用 Ollama 进行模型对话与聊天界面实战</summary>
      <content type="html"><![CDATA[<p>重点复习：</p>
<ol>
<li>基础语法中input输入、字符串格式化、运算符</li>
<li>逻辑判断：if、ifelse、if的嵌套</li>
<li>循环：for、while、嵌套（缩进）</li>
<li>函数：基础语法（函数多种传参、返回值的概念）</li>
<li>数据容器：列表（切片）、字符串（切片）、字典
<ol>
<li>切片非常常用，熟练掌握</li>
</ol>
</li>
<li>数量掌握lambda语法</li>
<li>返回多个返回值（返回元组）</li>
<li>文件操作（打开、关闭、读取、写入）</li>
<li>pip安装</li>
</ol>
<blockquote><p>方法：将以前的作业再来一遍</p><p>或询问AI，比如我想练习Python的循环，给我来10个</p></blockquote>
<section><h2>基础语法<a href="#基础语法"><span>#</span></a></h2><section><h3>字面量<a href="#字面量"><span>#</span></a></h3><p>概念：书<code>写</code>到代码内的，被<code>固定</code>下来的，<code>值</code>，就是字面量。</p><p>即：某些值是如何在代码里面写出来的。</p><p>值的类型：</p><p>Python中值类型很多，目前简单接触了解3个：</p><ul>
<li>整型（Integer、int）：整数的意思（包含负的）</li>
<li>浮点型（Float）：浮点数的意思（有小数点的数字）（包含负的）</li>
<li>字符串、文本（String、str）：文本类型
<ul>
<li>中文、英文、数字、符号等一堆字符的集合体，就是字符串</li>
</ul>
</li>
</ul><p>三种类型在代码中的书写方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 整型</span></div></div><div><div><div>2</div></div><div><span>321</span></div></div><div><div><div>3</div></div><div><span>-</span><span>123</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 浮点型</span></div></div><div><div><div>6</div></div><div><span>32.11</span></div></div><div><div><div>7</div></div><div><span>-</span><span>11.23</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 字符串</span></div></div><div><div><div>10</div></div><div><span>"黑马程序员666"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>整型、浮点型和日常生活中所见，基本一致</p><p>字符串（文本）需要以<code>""</code>包围起来（Python语法要求，必须）</p></blockquote><p>print也可以将字面量在屏幕中显示出来，如下代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>print</span><span>(</span><span>666</span><span>)</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>12.34</span><span>)</span></div></div><div><div><div>3</div></div><div><span>print</span><span>(</span><span>"黑马666"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>注释<a href="#注释"><span>#</span></a></h3><p>概念：对代码的解释说明文本。注释本身不是代码（执行的时候会被自动删除），只在阅读的时候有用。</p><p>简单来说，有了注释，就可以对代码的内容做解释和说明。</p><p>分类：</p><ul>
<li>单行注释：以<code>#</code> 开头，其<code>所在行右侧</code>全部内容作为注释存在
<ul>
<li>一般建议<code>#</code> 和注释内容之间，相隔一个空格</li>
<li>示例：<code># 我是注释</code></li>
</ul>
</li>
<li>多行注释：以<code>"""</code>开头，以<code>"""</code> 结尾，在其闭合区间内，全部内容作为注释存在
<ul>
<li>注意：<code>"""   """   """</code>  如果这样写，最后的<code>"""</code>是无用的，即闭合区间的匹配逻辑是就近匹配一堆<code>"""</code></li>
</ul>
</li>
</ul></section><section><h3>变量<a href="#变量"><span>#</span></a></h3><p>概念：在程序运行的过程中，进行数据的存储，同时存储的数据也可以发生修改。</p><p>特点：</p><ol>
<li>
<p>可以存储数据，且不限类型</p>
</li>
<li>
<p>存储的数据是可以修改的（变量的变字由来）</p>
</li>
<li>
<p>变量本身没有类型，但是存储的数据是有类型的，注意如下口头语：</p>
<ol>
<li><code>字符串变量</code>，表示一个存放了字符串数据的变量</li>
</ol>
</li>
<li>
<p>可以被重复使用</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"周杰轮"</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(name)</span></div></div><div><div><div>3</div></div><div><span>print</span><span>(name)</span></div></div><div><div><div>4</div></div><div><span>...</span></div></div><div><div><div>5</div></div><div><span># name变量可以任意重复利用</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>变量名 </span><span>=</span><span> 变量值</span></span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>=是赋值语句，表示：右侧先执行得到结果，将结果赋予左侧的变量存储。</p><p>右侧先执行得到结果，将结果赋予左侧的变量存储。</p><p>右侧先执行得到结果，将结果赋予左侧的变量存储。</p><p>即：不论右侧如何写，反正右侧必须全部执行完毕得到结果，才会将结果赋予左侧变量。</p></blockquote></section><section><h3>print输出多份内容<a href="#print输出多份内容"><span>#</span></a></h3><p>print语句（函数）可以向屏幕内一次性输出多份内容，语法如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>print</span><span><span>(内容1, 内容2, </span><span>...</span><span> ,</span><span>...</span><span>, 内容N)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>内容可以是：字面量、变量、表达式（会产生计算结果的）（比如5+3）</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"大锤"</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"你好我是"</span><span>, name, </span><span>"，很高兴认识你"</span><span>)</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 结果： 你好我是 大锤 ，很高兴认识你</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>数据类型<a href="#数据类型"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/16/20250716182040.png" alt="image-20250716182040756" /><figcaption>image-20250716182040756</figcaption></figure><p></p><p>上图是Python较为完整的常见数据类型，我们目前仅仅接触三个：</p><ul>
<li>整型（int、整数）</li>
<li>浮点型（float、小数）</li>
<li>字符串型（string、str、文本）</li>
</ul><section><h4>转换<a href="#转换"><span>#</span></a></h4><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/16/20250716182146.png" alt="image-20250716182146716" /><p>在转换的时候要注意：</p><ol>
<li>任何类型都可以转换为字符串</li>
<li>字符串不一定能转换为数字，比如：
<ol>
<li><code>"123"</code> 可以转换为123</li>
<li><code>"abc123"</code> 就无法转换为数字了</li>
<li>即：要求字符串内部全部是数字才行</li>
</ol>
</li>
</ol></section></section><section><h3>标识符<a href="#标识符"><span>#</span></a></h3><p>概念：标识符是编程语言中，对任何事物的命名，这个名字都叫做标识符</p><p>简单来说，标识符就是名字</p><section><h4>规则（强制）<a href="#规则强制"><span>#</span></a></h4><ol>
<li>
<p>内容限定：只允许数字、下划线、中文、英文</p>
<ol>
<li>数字不可以开头</li>
<li>中文不推荐（可以用，但不建议）</li>
</ol>
</li>
<li>
<p>大小写敏感，变量<code>Age</code>和变量<code>age</code> 是2个不同的东西</p>
</li>
<li>
<p>关键字限定，不可以在起名的时候用Pyhton内置关键字</p>
<img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/16/20250716182453.png" alt="image-20250716182453833" />
<p>这些关键字：不要背</p>
</li>
</ol></section><section><h4>规范（建议）<a href="#规范建议"><span>#</span></a></h4><ol>
<li>见名知义
<ol>
<li>明了：名字要能体现变量的作用</li>
<li>简洁：尽量长度短一点</li>
</ol>
</li>
<li>下划线命名：多个单词混在一起可以用下划线分开</li>
<li>字母全小写（仅用于变量）：变量名用的英文字母，全部小写</li>
</ol></section></section><section><h3>运算符<a href="#运算符"><span>#</span></a></h3><section><h4>算术运算符<a href="#算术运算符"><span>#</span></a></h4><p>做数学运算的。</p><ul>
<li><code>+</code> 加法</li>
<li><code>-</code> 减法</li>
<li><code>*</code> 乘法</li>
<li><code>/</code> 除法（非整除）</li>
<li><code>//</code> 整除</li>
<li><code>%</code> 取余（取模）</li>
<li><code>**</code> 求指数（平方）</li>
</ul></section><section><h4>赋值运算符<a href="#赋值运算符"><span>#</span></a></h4><ul>
<li><code>=</code>  将右侧结果赋予左侧变量</li>
<li>复合赋值：<code>a?=b</code> 相当于：<code>a = a?b</code> ，这个<code>?</code> 可以是任何的算术运算符</li>
</ul></section></section><section><h3>字符串<a href="#字符串"><span>#</span></a></h3><section><h4>三种定义模式<a href="#三种定义模式"><span>#</span></a></h4><ol>
<li>单引号写法：<code>'黑马'</code></li>
<li>双引号写法：<code>"黑马"</code></li>
<li>三引号写法：<code>"""黑马"""</code>
<ol>
<li>这个写法，没有变量接收，则就是多行注释</li>
<li>如果用变量接收，就是字符串</li>
</ol>
</li>
</ol></section><section><h4>加法拼接字符串<a href="#加法拼接字符串"><span>#</span></a></h4><p>Python提供的<code>+</code> 号，在进行字符串相加的时候，是完成字符串的首尾拼接</p><p>如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>info </span><span>=</span><span> </span></span><span>"黑马"</span><span><span> </span><span>+</span><span> </span></span><span>"程序员"</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(info)</span></div></div><div><div><div>3</div></div><div><span># 运行结果：黑马程序员</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>注意：+拼接只用于字符串和字符串，如果用于其它类型，需要转换</p><p>比如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"黑马"</span></div></div><div><div><div>2</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>11</span></div></div><div><div><div>3</div></div><div><span><span>info </span><span>=</span><span> </span></span><span>"我是"</span><span><span> </span><span>+</span><span> name </span><span>+</span><span> </span></span><span>"，今年"</span><span><span> </span><span>+</span><span> </span></span><span>str</span><span><span>(age) </span><span>+</span><span> </span></span><span>"岁"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以看到拼接age的时候，用了str()做类型转换</p></blockquote></section></section><section><h3>字符串格式化（%占位符形式）<a href="#字符串格式化占位符形式"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"%s %d %f ... ..."</span><span><span> </span><span>%</span><span> (变量, 变量, 变量, </span><span>...</span><span>.,</span><span>...</span><span>)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>%s</code> 表示以字符串形式占位，等到数据的填充</li>
<li><code>%d</code> 表示以整数形式占位，等到数据的填充</li>
<li><code>%f</code>表示以浮点数形式占位，等到数据的填充</li>
<li>%占位符的数量要和后续提供填充的数据的数量是一比一</li>
<li>任意数据类型可以用%s占位，但是不是任意类型都可以用%d或%f占位，要注意数据的转换关系
<ul>
<li>如：%f占位，提供的数据是”abc”肯定是不可以的</li>
</ul>
</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"周杰轮"</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"你是：</span><span>%s</span><span>"</span><span><span> </span><span>%</span><span> name)</span></span></div></div><div><div><div>3</div></div><div><span># 结果是： 你是 周杰轮</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>11</span></div></div><div><div><div>6</div></div><div><span><span>height </span><span>=</span><span> </span></span><span>172.55</span></div></div><div><div><div>7</div></div><div><span>print</span><span>(</span><span>"你是</span><span>%s</span><span>，身高：</span><span>%f</span><span>，年龄：</span><span>%d</span><span>"</span><span><span> </span><span>%</span><span> (name, height, age))</span></span></div></div><div><div><div>8</div></div><div><span># 结果：你是周杰轮，身高：172.550000，年龄：11</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>默认的%f占位，浮点数会使用6位小数精度，没有的话会补充0</p></blockquote></section><section><h3>字符串格式化（f_format、f_string形式）<a href="#字符串格式化f_formatf_string形式"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>f</span><span>"</span><span>{</span><span>变量、常量、表达式</span><span>}</span><span>......"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"周杰轮"</span></div></div><div><div><div>2</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>11</span></div></div><div><div><div>3</div></div><div><span><span>height </span><span>=</span><span> </span></span><span>172.55</span></div></div><div><div><div>4</div></div><div><span>print</span><span>(</span><span>f</span><span>"你是</span><span>{</span><span>name</span><span>}</span><span>，身高：</span><span>{</span><span>height</span><span>}</span><span>，年龄：</span><span>{</span><span>age</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>5</div></div><div><span># 结果：你是周杰轮，身高：172.55，年龄：11</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>字符串格式化的数字精度<a href="#字符串格式化的数字精度"><span>#</span></a></h3><p>基础写法是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>%</span><span>md</span></span></div></div><div><div><div>2</div></div><div><span><span>%</span><span>m.nf</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>m</code> 表示数字的宽度</li>
<li><code>.n</code> 表示小数点的宽度
<ul>
<li>仅对浮点数有效</li>
</ul>
</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>11</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"</span><span>%5d</span><span>"</span><span><span> </span><span>%</span><span> age)    </span></span><span># 结果：   11   11前面还有3个空格补充到5的宽度</span></div></div><div><div><div>3</div></div><div><span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>age</span><span>:5d</span><span>}</span><span>"</span><span>)    </span><span># 结果：   11   11前面还有3个空格补充到5的宽度</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>height </span><span>=</span><span> </span></span><span>172.55</span></div></div><div><div><div>6</div></div><div><span>print</span><span>(</span><span>"</span><span>%7.1f</span><span>"</span><span><span> </span><span>%</span><span> height)   </span></span><span># 结果：  172.6    前面有2个空格 小数点精确1位（有四舍五入效果）</span></div></div><div><div><div>7</div></div><div><span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>height</span><span>:7.2f</span><span>}</span><span>"</span><span>)    </span><span># 结果：  172.6    前面有2个空格 小数点精确1位（有四舍五入效果）</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>表达式<a href="#表达式"><span>#</span></a></h3><p>概念：能够<code>产出明确结果</code>的代码语句，称之为表达式。</p><p>示例，如下都是表达式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>5</span><span>+</span><span>3</span></div></div><div><div><div>2</div></div><div><span>2</span><span>*</span><span>3</span><span>+</span><span>5</span><span>/</span><span>2</span></div></div><div><div><div>3</div></div><div><span>type</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>4</div></div><div><span><span>age </span><span>*</span><span> </span></span><span>10</span><span>  </span><span># age是一个变量</span></div></div><div><div><div>5</div></div><div><span>input</span><span>(</span><span>"请输入："</span><span>)</span></div></div><div><div><div>6</div></div><div><span>5</span><span>&gt;</span><span>2</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>input输入<a href="#input输入"><span>#</span></a></h3><p>概念：获取用户的键盘录入数据</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>input</span><span>()    </span><span># 从键盘输入内容，赋值给name变量</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>input</span><span>(</span><span>"请输入姓名："</span><span>)  </span><span># 先将请输入姓名的提升语输出，然后从键盘输入内容，赋值给name变量</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>特点：无论输入的是什么，得到的结果都是<code>字符串</code>类型，如果需要输入数字类型，请自行转换类型。</p><ul>
<li>print：对外输出信息数据</li>
<li>input：从外部接收信息输入</li>
</ul></section></section>
<section><h2>逻辑判断<a href="#逻辑判断"><span>#</span></a></h2><section><h3>布尔数据类型<a href="#布尔数据类型"><span>#</span></a></h3><p>概念：布尔类型表示的是2个状态，即：真或假。其字面量为：<code>True</code> 表示真、<code>False</code> 表示假</p><blockquote><p>注意：True和False的大小写</p></blockquote><p>布尔用于存储一些逻辑判断的结果，逻辑判断在生活中或程序中非常常见，用来表示成立或不成立，即非黑即白</p><p>所以用True表示成立、False表示不成立。</p><p>其类型名为：<code>bool</code></p></section><section><h3>比较运算符<a href="#比较运算符"><span>#</span></a></h3><p>比较运算符主要用于对2个数据进行大小的比较。</p><p>可用运算符有：</p><ul>
<li><code>==</code> 用于比较相等（注意是2个等于号）</li>
<li><code>!=</code> 用于比较不相等</li>
<li><code>&gt;</code> 比较大于</li>
<li><code>&lt;</code> 比较小于</li>
<li><code>&gt;=</code> 比较大于等于</li>
<li><code>&lt;=</code> 比较小于等于</li>
</ul><p>这些运算符统统产出的结果是：<code>布尔类型</code></p></section><section><h3>逻辑运算符<a href="#逻辑运算符"><span>#</span></a></h3><p>逻辑运算符主要对多个判断条件进行连接，进行统一判断。</p><p>主要有3个：</p><ul>
<li><code>and</code> 用法：<code>a and b</code>   a为True<code>并且</code>b为True，则结果为True</li>
<li><code>or</code> 用法：<code>a or b</code>   a为True<code>或者</code>b为True，则结果为True</li>
<li><code>not</code> 用法：<code>not a</code>  a为True则结果为False，反之a为False则结果为True，即取反</li>
</ul></section><section><h3>if语句<a href="#if语句"><span>#</span></a></h3><p>功能：做逻辑判断，即xxx是否满足，满足则做特定的事情。</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> 条件:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>if</code> 是关键字，表示如果</li>
<li>条件是表达式，要求必须必须必须是布尔类型结果</li>
<li><code>:</code>不要忘记</li>
<li>注意缩进有4个空格
<ul>
<li>表示层次关系，即条件满足后的代码，属于这个if判断</li>
</ul>
</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>19</span></div></div><div><div><div>2</div></div><div><span>if</span><span><span> age </span><span>&gt;=</span><span> </span></span><span>18</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>print</span><span>(</span><span>"成年"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>当满足条件，则输出成年</p></blockquote></section><section><h3>if else语句<a href="#if-else语句"><span>#</span></a></h3><p>功能：做逻辑判断，即满足xxx，则做特定的事情，否则，则做特定的事情</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> 条件:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>4</div></div><div><span>else</span><span>:</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>else和if是同层次，即缩进是一致的</li>
<li>else后面不需要写条件了（使用if的条件）</li>
<li><code>:</code>不要忘记</li>
<li>else内的代码，不要忘记缩进4个空格</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>money </span><span>=</span><span> </span></span><span>int</span><span>(</span><span>input</span><span>(</span><span>"请告诉我你还有多少钱："</span><span>))</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>if</span><span><span> money </span><span>&gt;=</span><span> </span></span><span>10000</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(</span><span>"去买个新电脑"</span><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>else</span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>print</span><span>(</span><span>"洗洗睡吧"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>if elif else语句<a href="#if-elif-else语句"><span>#</span></a></h3><p>功能：做多个条件判断，对多个条件挨个做判断，满足则执行特定操作，如果都不满足则执行特定操作。</p><p>语法：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> 条件:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>4</div></div><div><span>elif</span><span> 条件2:</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>7</div></div><div><span>elif</span><span> 条件3:</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>10</div></div><div><span>...</span></div></div><div><div><div>11</div></div><div><span>...</span></div></div><div><div><div>12</div></div><div><span>elif</span><span> 条件N:</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>15</div></div><div><span>else</span><span>:</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>条件满足后的代码</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><ul>
<li><code>elif</code>是关键字，不要写错</li>
<li><code>elif</code> 可以写多个，无限制</li>
<li>每一个<code>elif</code> 需要写条件</li>
<li><code>elif</code> 不可以单独存在，是搭配<code>if</code>后存在的</li>
<li>注意缩进</li>
<li>if和多个elif是有序且互斥
<ul>
<li>从if开始向下挨个判断，只要有满足的，则就不会理会其它了</li>
<li>不管有多少条件，就是<code>N</code> 选 <code>1</code></li>
</ul>
</li>
</ul></section><section><h3>if嵌套<a href="#if嵌套"><span>#</span></a></h3><p>功能：在if判断内或else内或elif内，可以继续写if elif else</p><p>一般用于前置条件判断</p><p>语法：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> 条件:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>if</span><span> 条件:</span></div></div><div><div><div>4</div></div><div><span><span>        </span></span><span>...</span></div></div><div><div><div>5</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>...</span></div></div><div><div><div>7</div></div><div><span>elif</span><span> 条件:</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>9</div></div><div><span>    </span><span>if</span><span> 条件:</span></div></div><div><div><div>10</div></div><div><span><span>        </span></span><span>...</span></div></div><div><div><div>11</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>12</div></div><div><span>        </span><span>if</span><span><span> </span><span>...</span></span></div></div><div><div><div>13</div></div><div><span>      </span><span>elif</span><span><span> </span><span>...</span></span></div></div><div><div><div>14</div></div><div><span>        </span><span>else</span><span><span> </span><span>...</span></span></div></div><div><div><div>15</div></div><div><span>else</span><span>:</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>17</div></div><div><span>    </span><span>if</span><span> 条件:</span></div></div><div><div><div>18</div></div><div><span>        </span><span>if</span><span><span> </span><span>...</span></span></div></div><div><div><div>19</div></div><div><span>      </span><span>elif</span><span><span> </span><span>...</span></span></div></div><div><div><div>20</div></div><div><span>        </span><span>else</span><span><span> </span><span>...</span></span></div></div><div><div><div>21</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>如上语法，可以自由组合嵌套层次，层次不限制层数但是</p><blockquote><p>一般建议不超过3层嵌套</p><ul>
<li>Python解释器不会晕，100层都搞的定</li>
<li>我们人会晕</li>
</ul></blockquote><blockquote><p>猜数字案例，我们课上是3层嵌套完成的（3次猜测机会）</p><p>如果要避免嵌套还是有方法的：保密</p></blockquote></section></section>
<section><h2>循环<a href="#循环"><span>#</span></a></h2><section><h3>while循环基础语法<a href="#while循环基础语法"><span>#</span></a></h3><p>概念：循环是对特定的操作，当条件满足则持续执行下去，直到条件不满足为止。</p><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/17/20250717182958.png" alt="image-20250717182958065" /><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>while</span><span> 条件:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>循环代码</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>while</code> 是关键字不要写错</li>
<li><code>条件</code> 必须产出布尔结果</li>
<li><code>缩进</code> 不要忘记</li>
</ul><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>while</span><span> </span><span>1</span><span><span> </span><span>==</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(</span><span>"你好"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>这个代码满足循环语法，但是：无限循环</p></blockquote><p>为了避免无限循环，我们自行在语法外引入限制，即循环三要素：</p><ul>
<li>循环控制因子的创建</li>
<li>基于循环控制因子的条件判断</li>
<li>循环控制因子的更新</li>
</ul><p>三要素合力，确保循环受限。</p><p>示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 打印我喜欢你 10次</span></div></div><div><div><div>2</div></div><div><span># 基础循环，缺少控制，所以会导致无限执行</span></div></div><div><div><div>3</div></div><div><span># while True:</span></div></div><div><div><div>4</div></div><div><span>#     print("我喜欢你")</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 控制循环在有限次数内执行，有3个要素</span></div></div><div><div><div>7</div></div><div><span># 1. 循环控制因子（某个变量）</span></div></div><div><div><div>8</div></div><div><span># 2. 循环控制条件（基于因子做判断）</span></div></div><div><div><div>9</div></div><div><span># 3. 循环因子更新（修改因子的值）</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 控制因子</span></div></div><div><div><div>12</div></div><div><span><span>num </span><span>=</span><span> </span></span><span>0</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 控制条件（基于因子）</span></div></div><div><div><div>15</div></div><div><span>while</span><span><span> num </span><span>&lt;</span><span> </span></span><span>10</span><span>:</span></div></div><div><div><div>16</div></div><div><span>    </span><span>print</span><span>(</span><span>"第</span><span>%d</span><span>次表白：我喜欢你"</span><span><span> </span><span>%</span><span> num)</span></span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>num </span><span>+=</span><span> </span><span>1</span><span>        </span><span># 因子更新</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>while循环嵌套<a href="#while循环嵌套"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>while</span><span> 条件:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>    </span><span>while</span><span> 条件:</span></div></div><div><div><div>6</div></div><div><span><span>        </span></span><span>...</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如上是一个2层嵌套</p><p>使用嵌套循环一定注意：</p><ul>
<li><code>空格的缩进</code> ，因为Python是通过空格缩进决定层次关系</li>
<li>向右缩进，层次就低</li>
</ul><p>案例：九九乘法表</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>row </span><span>=</span><span> </span></span><span>1</span><span>                 </span><span># 外层循环因子</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>while</span><span><span> row </span><span>&lt;=</span><span> </span></span><span>9</span><span>:         </span><span># 基于因子的条件</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>col </span><span>=</span><span> </span><span>1</span><span>             </span><span># 内层循环因子</span></div></div><div><div><div>6</div></div><div><span>    </span><span>while</span><span><span> col </span><span>&lt;=</span><span> row:   </span></span><span># 基于因子的条件</span></div></div><div><div><div>7</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>col</span><span>}</span><span>*</span><span>{</span><span>row</span><span>}</span><span>=</span><span>{</span><span><span>col</span><span>*</span><span>row</span></span><span>}</span><span>"</span><span>, </span><span>end</span><span>=</span><span>"</span><span>\t</span><span>"</span><span>)</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>col </span><span>+=</span><span> </span><span>1</span><span>        </span><span># 内层循环因子更新</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>print</span><span>()             </span><span># 外层循环功能，换一行</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>row </span><span>+=</span><span> </span><span>1</span><span>            </span><span># 外层循环因子更新</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>for循环基础语法<a href="#for循环基础语法"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> 临时变量 </span><span>in</span><span> 待处理数据集:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>流程：从待处理数据集中，挨个取出内容，赋予临时变量，然后执行循环语句，循环往复。</p><p>和while循环的流程对比，如下是while：</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/19/20250719143446.png" alt="image-20250719143446385" /><figcaption>image-20250719143446385</figcaption></figure><p></p><p>如下是for：</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/19/20250719143501.png" alt="image-20250719143501617" /><figcaption>image-20250719143501617</figcaption></figure><p></p><p>如上图可以看到，for循环对比while的区别在于：</p><ul>
<li>while循环有三要素（因子创建、条件判断、因子更新），for循环没有</li>
<li>while有条件控制，则控制条件即可成为无限循环</li>
<li>for循环没有条件，只是挨个取出待处理元素，实际上无法形成无限循环</li>
</ul><section><h4>可迭代对象（类型）<a href="#可迭代对象类型"><span>#</span></a></h4><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> 临时变量 </span><span>in</span><span> 待处理数据集:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>上述语法的待处理数据集，本质上应当是：可迭代对象（类型）</li>
</ul><p>可迭代对象表示的是：支持挨个取出元素的类型，有如下类型属于可迭代类型：</p><ul>
<li><code>字符串</code></li>
<li>列表</li>
<li>元素</li>
<li>等</li>
</ul><p>所以，字符串是属于可迭代类型则可以用于for循环如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"itheima666你好"</span></div></div><div><div><div>2</div></div><div><span># 字符串可以被for循环使用，并且被逐个取出</span></div></div><div><div><div>3</div></div><div><span>for</span><span> x </span><span>in</span><span> name:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(x)</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>range语句<a href="#range语句"><span>#</span></a></h4><p>range语句可以形成一个数字序列。</p><p>序列：有序的集合，比如：<code>1, 2, 3, 4, 5, ...</code> 就是数字序列</p><p>range语句形成的数字序列是<code>可迭代类型</code>（其本质是一个迭代器对象，等同于list列表）</p><p>语法1：</p><p><code>range(num)</code>：形成从<code>0</code> 开始，到<code>num</code> 结束（不包括num本身）的数字序列</p><ul>
<li>如：<code>range(5)</code>结果是：0, 1, 2, 3, 4</li>
</ul><p>语法2：</p><p><code>range(num1, num2)</code> ：形成从num1开始，到num2结束（不含num2）的数字序列</p><ul>
<li>如：<code>range(5, 10)</code> 结果是：5, 6, 7, 8, 9</li>
</ul><p>语法3：</p><p><code>range(num1, num2, step)</code> ：形成从num1开始，到num2结束（不含num2）的数字序列，步长以step决定</p><ul>
<li>如：<code>range(5, 10, 2)</code>结果是：5, 7, 9    (注意步长是2)</li>
</ul></section></section><section><h3>变量作用域<a href="#变量作用域"><span>#</span></a></h3><p>在Python中变量是由其作用的范围的，离开其生效范围，则无法使用。</p><p>如下代码：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> x </span><span>in</span><span> </span><span>range</span><span>(</span><span>5</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>age </span><span>=</span><span> </span><span>10</span></div></div><div><div><div>3</div></div><div><span>    </span><span>print</span><span>(age)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>print</span><span>(age)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此代码中，age变量创建是在for循环内部，所以age的生效范围，仅在for循环内，即</p><ul>
<li>上述代码第三行，是OK的</li>
<li>第五行代码，是不OK的，因为脱离了age的生效范围</li>
</ul><p>变量的生效范围一般是：</p><ul>
<li>如果写在<code>:</code> 内，比如<code>if</code>  <code>elif</code>  <code>else</code>  <code>while</code>  <code>for</code>  <code>with</code> 等
<ul>
<li>则生效在<code>:</code>所属的范围内</li>
</ul>
</li>
<li>如果写在文件中，则全文件生效</li>
</ul><blockquote><p>口诀：缩进靠左访问缩进靠右变量，不可以</p><p>反之，缩进靠右访问缩进靠左是OK的</p></blockquote></section><section><h3>continue关键字<a href="#continue关键字"><span>#</span></a></h3><p>作用：可以控制循环的执行，具体是：当循环代码遇到<code>continue</code>的时候，将<code>跳过本次</code>循环，直接<code>进入下一次</code>循环</p><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> x </span><span>in</span><span> </span><span>range</span><span>(</span><span>5</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(</span><span>"aaa"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>continue</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(</span><span>"bbb"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>上述代码的<code>print("bbb")</code>将永远无法执行，因为遇到continue循环直接进入<code>下一次</code></li>
</ul></section><section><h3>break关键字<a href="#break关键字"><span>#</span></a></h3><p>作用：可以控制循环的执行，具体是：当循环代码遇到<code>break</code>的时候，直接结束循环</p><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> x </span><span>in</span><span> </span><span>range</span><span>(</span><span>5</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(</span><span>"aaa"</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>break</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(</span><span>"bbb"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>上述代码仅输出一次<code>aaa</code> 后就结束，因为遇到break后，整个循环就结束了</li>
</ul><p>注意：</p><blockquote><p>continue和break</p><ul>
<li>
<p>仅对循环有效（for和while）</p>
</li>
<li>
<p>在嵌套应用的时候，仅对所属的循环（for和while有效）</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> j </span><span>in</span><span> </span><span>range</span><span>(</span><span>1</span><span>, </span><span>11</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"今天是第</span><span>{</span><span>j</span><span>}</span><span>天干饭"</span><span>)</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>    </span><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>1</span><span>, </span><span>11</span><span>):</span></div></div><div><div><div>5</div></div><div><span><span>        </span></span><span>flag </span><span>=</span><span> </span><span>int</span><span>(</span><span>input</span><span>(</span><span>f</span><span>"第</span><span>{</span><span>j</span><span>}</span><span>天的第</span><span>{</span><span>i</span><span>}</span><span>碗饭吃不吃，吃输入1不吃输入0："</span><span>))</span></div></div><div><div><div>6</div></div><div><span>        </span><span>if</span><span><span> flag </span><span>==</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>7</div></div><div><span>            </span><span>break</span><span>       </span><span># break会直接结束循环（同样和if没关系，仅对for和while有效）</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"吃完了第</span><span>{</span><span>i</span><span>}</span><span>碗饭，真香"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>如上代码，break仅对第四行代码的这个for循环有效。</p>
</li>
</ul></blockquote></section></section>
<section><h2>函数<a href="#函数"><span>#</span></a></h2><section><h3>概念<a href="#概念"><span>#</span></a></h3><p>函数是：提前写好的、可以重复利用的、具有特定功能的代码片段</p><p>特点：</p><ul>
<li>提前写好才能用</li>
<li>写好后，可以无限制重复用</li>
<li>功能是特定功能</li>
</ul><blockquote><p>主要作用：避免程序内重复的代码，一旦有重复的代码就可以封装为函数。</p></blockquote><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>函数名</span><span>(</span><span>[形参列表]</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>函数内容</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>[</span><span>return</span><span> 返回值]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>调用函数：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>函数名</span><span>([实参列表])</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>参数<a href="#参数"><span>#</span></a></h3><p>参数分为：</p><ul>
<li>形式参数：在书写（声明）函数的时候，<code>（声明）要求</code>的传入参数</li>
<li>实际参数：在调用函数的时候，<code>实际</code>传入的参数</li>
</ul><p>特点：</p><ul>
<li>
<p>支持任意个传入参数</p>
</li>
<li>
<p>多个传入参数用逗号分隔，如：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>add</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span><span> x </span><span>+</span><span> y </span><span>+</span><span> z</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>如上三个参数，用逗号分隔</p>
</li>
<li>
<p>形参和实参是<code>一一对应</code>，如：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>add</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span><span> x </span><span>+</span><span> y </span><span>+</span><span> z</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>add</span><span>(</span></span><span>1</span><span>, </span><span>3</span><span>, </span><span>5</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>add函数在调用的时候，实际参数的数量和形式参数的数量要一致，同时1会提供给x，3给y，5给z，有对应关系</p>
</li>
<li>
<p>实参的传入类型不受限制，如下：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>add</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span><span> x </span><span>+</span><span> y </span><span>+</span><span> z</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>add</span><span>(</span></span><span>"1"</span><span>, </span><span>"2"</span><span>, </span><span>"3"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<blockquote><p>传入的时候自己注意，如果传入不兼容，可能报错</p></blockquote>
</li>
</ul></section><section><h3>返回值<a href="#返回值"><span>#</span></a></h3><p>概念：函数对外提供的执行结果</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>函数名</span><span>(</span><span>[形参列表]</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>函数内容</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>[</span><span>return</span><span> 返回值]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通过return对外提供</p><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>add</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span><span> x </span><span>+</span><span> y </span><span>+</span><span> z</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>sum</span><span><span> </span><span>=</span><span> </span><span>add</span><span>(</span></span><span>1</span><span>, </span><span>3</span><span>, </span><span>5</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>sum</code> 就能得到return提供的值：<code>9</code></li>
</ul></section><section><h3>None<a href="#none"><span>#</span></a></h3><p>在Python中有一个数据类型：<code>NoneType</code> ，它只有一个字面量：<code>None</code></p><p>None表示：空、啥也没有</p><p>函数如果没有写return，则默认返回值是：None</p><p>或者函数也可以写<code>return None</code> 直接对外返回None</p><p>作用：</p><ul>
<li>
<p>用在if判断中，因为None就等同于<code>False</code></p>
</li>
<li>
<p>用在变量声明上，某些变量在写的时候，暂时不需要初始值，可以以None代替</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>age </span><span>=</span><span> </span></span><span>None</span><span>    </span><span># 此时age不需要初始值，可以以None代替</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>for</span><span> i </span><span>in</span><span> </span><span>range</span><span>(</span><span>5</span><span>):</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>age </span><span>=</span><span> </span><span>int</span><span>(</span><span>input</span><span>())</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 为了避免作用域问题，age需要再for循环外部定义</span></div></div><div><div><div>7</div></div><div><span># for循环外定义的时候，不需要初始值，可以以None代替</span></div></div><div><div><div>8</div></div><div><span>print</span><span>(</span><span>"最后一个同学的年龄是：</span><span>%d</span><span> "</span><span><span> </span><span>%</span><span> age)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>用于返回的返回值，只要有需要就可以返回None</p>
</li>
</ul></section><section><h3>函数的规范注释<a href="#函数的规范注释"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>函数名</span><span>(</span><span>参数</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>"""</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>函数说明文本</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>:param 参数1: 参数1的说明</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>:param 参数2: 参数2的说明</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>:param 参数N: 参数N的说明</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>:return: 返回值的说明</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>"""</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>函数体</span></div></div><div><div><div>11</div></div><div><span>    </span><span>return</span><span><span> </span><span>...</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>作用：规范代码，提高可读性，并提升B格</p></section><section><h3>函数的嵌套调用<a href="#函数的嵌套调用"><span>#</span></a></h3><p>概念：嵌套调用就是在一个函数的函数体中调用了另一个函数</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/19/20250719180621.png" alt="image-20250719180621058" /><figcaption>image-20250719180621058</figcaption></figure><p></p><p>如图，为嵌套调用，结果是：1 2 3</p><p>特点：在函数执行的时候，如果遇到调用其它函数，则先执行这个函数，等函数运行完成后，在返回来继续执行未完成的工作。</p><blockquote><p>见到函数就进入执行</p></blockquote><p>示例：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"""</span></div></div><div><div><div>2</div></div><div><span>嵌套调用的基本原则是：```遇到函数就进入执行```，执行完成后再回到当初的位置继续向下</span></div></div><div><div><div>3</div></div><div><span>def func_b():</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>print("22222")</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>func_a()</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>print("33333")</span></div></div><div><div><div>7</div></div><div><span>比如执行完222后，遇到函数了，则进入，将函数执行完成后再回来继续执行333</span></div></div><div><div><div>8</div></div><div><span>"""</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>def</span><span> </span><span>func_a</span><span>():</span></div></div><div><div><div>12</div></div><div><span>    </span><span>print</span><span>(</span><span>"11111"</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>def</span><span> </span><span>func_b</span><span>():</span></div></div><div><div><div>16</div></div><div><span>    </span><span>print</span><span>(</span><span>"22222"</span><span>)</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>func_a</span><span>()</span></div></div><div><div><div>18</div></div><div><span>    </span><span>print</span><span>(</span><span>"33333"</span><span>)</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>def</span><span> </span><span>func_c</span><span>():</span></div></div><div><div><div>22</div></div><div><span>    </span><span>print</span><span>(</span><span>"44444"</span><span>)</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>func_b</span><span>()</span></div></div><div><div><div>24</div></div><div><span>    </span><span>print</span><span>(</span><span>"55555"</span><span>)</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span><span>func_c</span><span>()</span></span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span># 结果是：</span></div></div><div><div><div>31</div></div><div><span># 4 -&gt; 2 -&gt;1 -&gt; 3 -&gt; 5</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>局部变量<a href="#局部变量"><span>#</span></a></h3><p>概念：写在函数内部的是局部变量，其作用范围只在函数内部有效。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>():</span></div></div><div><div><div>2</div></div><div><span>    </span><span># num是局部变量，作用域（生命周期）仅在函数内部有效</span></div></div><div><div><div>3</div></div><div><span>    </span><span># 局部变量：写在函数内部的变量（也叫做临时变量）</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>num </span><span>=</span><span> </span><span>100</span></div></div><div><div><div>5</div></div><div><span>    </span><span>print</span><span>(</span><span>"内部输出"</span><span>, num)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>func</span><span>()</span></span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 当函数执行完成后，临时变量（局部变量）num已经不存在了</span></div></div><div><div><div>11</div></div><div><span># 即num存在的时间只有在调用函数的那一会儿</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 在print语句执行的这个地方，num已经不存在了</span></div></div><div><div><div>14</div></div><div><span>print</span><span>(</span><span>"外部输出："</span><span>, num)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如上代码，15行访问num是错误的，因为num此时不存在了，即脱离了它应有的作用域范围。</p></section><section><h3>全局变量<a href="#全局变量"><span>#</span></a></h3><p>概念：没有写在函数内部的是全局变量，作用范围在函数内和外都生效。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"""</span></div></div><div><div><div>2</div></div><div><span>全局变量就是：不定义在函数内部的变量</span></div></div><div><div><div>3</div></div><div><span>"""</span></div></div><div><div><div>4</div></div><div><span># 全局变量</span></div></div><div><div><div>5</div></div><div><span><span>num </span><span>=</span><span> </span></span><span>100</span><span>       </span><span># 作用域在于整个代码</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>func_a</span><span>():</span></div></div><div><div><div>9</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"a: </span><span>{</span><span>num</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>def</span><span> </span><span>func_b</span><span>():</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>num </span><span>=</span><span> </span><span>200</span><span>       </span><span># 这个num是一个和全局num同名的局部变量</span></div></div><div><div><div>14</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"b: </span><span>{</span><span>num</span><span>}</span><span>"</span><span>)  </span><span># 此时访问的num是局部变量num</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>func_a</span><span>()</span></span></div></div><div><div><div>18</div></div><div><span><span>func_b</span><span>()</span></span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>print</span><span>(</span><span>f</span><span>"外部：</span><span>{</span><span>num</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>如上代码，num就是全局，在函数内和函数外均可用</p><section><h4>global关键字<a href="#global关键字"><span>#</span></a></h4><p>作用：在函数内部声明使用的变量是全局变量</p><p>演示：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>num </span><span>=</span><span> </span></span><span>100</span><span>       </span><span># 作用域在于整个代码</span></div></div><div><div><div>2</div></div><div><span>def</span><span> </span><span>func_a</span><span>():</span></div></div><div><div><div>3</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"a: </span><span>{</span><span>num</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>def</span><span> </span><span>func_b</span><span>():</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>num </span><span>=</span><span> </span><span>200</span><span>       </span><span># 这个num是一个和全局num同名的局部变量</span></div></div><div><div><div>7</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"b: </span><span>{</span><span>num</span><span>}</span><span>"</span><span>)  </span><span># 此时访问的num是局部变量num</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span><span>func_a</span><span>()</span></span></div></div><div><div><div>10</div></div><div><span><span>func_b</span><span>()</span></span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>print</span><span>(</span><span>f</span><span>"外部：</span><span>{</span><span>num</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>函数的输出结果是：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>a：100</span></div></div><div><div><div>2</div></div><div><span>b：200</span></div></div><div><div><div>3</div></div><div><span>外部：100</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>函数b对num的修改是失效的，因为这个num在函数b中是写了一个和全局num同名的局部变量。</p><p>解决这个问题使用global关键字即可，如下代码：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"""</span></div></div><div><div><div>2</div></div><div><span>全局变量就是：不定义在函数内部的变量</span></div></div><div><div><div>3</div></div><div><span>"""</span></div></div><div><div><div>4</div></div><div><span># 全局变量</span></div></div><div><div><div>5</div></div><div><span><span>num </span><span>=</span><span> </span></span><span>100</span><span>       </span><span># 作用域在于整个代码</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>func_a</span><span>():</span></div></div><div><div><div>9</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"a: </span><span>{</span><span>num</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>def</span><span> </span><span>func_b</span><span>():</span></div></div><div><div><div>13</div></div><div><span>    </span><span>global</span><span> num      </span><span># 在此函数内部使用的num是全局变量</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>num </span><span>=</span><span> </span><span>200</span><span>       </span><span># 这个num就是访问全局变量，修改他的值</span></div></div><div><div><div>15</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"b: </span><span>{</span><span>num</span><span>}</span><span>"</span><span>)  </span><span># 此时访问的num是全局变量num</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span><span>func_a</span><span>()</span></span></div></div><div><div><div>19</div></div><div><span><span>func_b</span><span>()</span></span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span>print</span><span>(</span><span>f</span><span>"外部：</span><span>{</span><span>num</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>看<code>13</code> 行，通过global关键字可以在函数内部声明，num为全局变量，对num的所有使用都是用全局num</p></section></section></section>
<section><h2>数据容器<a href="#数据容器"><span>#</span></a></h2><section><h3>概念<a href="#概念-1"><span>#</span></a></h3><p>数据容器：一种数据类型，可以存储多个元素在内。</p><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721143326.png" alt="image-20250721143325942" /><p>根据是否可以修改、是否允许重复、是否有序等等特点数据容器有如下类型：</p><ul>
<li>列表（list）</li>
<li>元组（tuple）</li>
<li>字符串（string、str）</li>
<li>字典（dictate）</li>
<li>集合（set）</li>
</ul></section><section><h3>列表<a href="#列表"><span>#</span></a></h3><p>list（列表）</p><section><h4>特点：<a href="#特点"><span>#</span></a></h4><ul>
<li>可以支持非常多的元素在内（<code>2**63 -1</code>）</li>
<li>支持多种不同元素类型的混装</li>
<li>支持修改</li>
<li>内部数据有序(有下标索引)</li>
<li>支持重复元素</li>
</ul><p>类型名：<code>list</code></p><p>通过type语句输出的类型字符串是：<code>&lt;class list&gt;</code></p></section><section><h4>语法<a href="#语法"><span>#</span></a></h4><p>字面量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>[元素, 元素, </span><span>...</span><span>, 元素]</span></span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>[</span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>]</span></div></div><div><div><div>4</div></div><div><span>[</span><span>"itheima"</span><span>, </span><span>"你好"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>变量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> [</span></span><span>"itheima"</span><span>, </span><span>"666"</span><span>]</span></div></div><div><div><div>2</div></div><div><span><span>ages </span><span>=</span><span> [</span></span><span>11</span><span>, </span><span>22</span><span>, </span><span>33</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>空list</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> []</span></span></div></div><div><div><div>2</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>list</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>嵌套</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>ages </span><span>=</span><span> [ [</span></span><span>1</span><span>,</span><span>2</span><span>,</span><span>3</span><span>], [</span><span>4</span><span>,</span><span>5</span><span>,</span><span>6</span><span>]]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h5>下标访问<a href="#下标访问"><span>#</span></a></h5><p>列表元素有下标值（每一个元素的序号）</p><ul>
<li>从前到后（从左到右）下标是0、1、2、…、N  即从0开始递增</li>
<li>从后到钱（从右到左）下标是-1、-2、-3、…、-N  即从-1开始递减</li>
</ul><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721145243.png" alt="image-20250721145243298" /><figcaption>image-20250721145243298</figcaption></figure><p></p><p>可以通过下标值访问列表的特定位置的元素，如下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>nums </span><span>=</span><span> [</span></span><span>1</span><span>, </span><span>3</span><span>, </span><span>5</span><span>]</span></div></div><div><div><div>2</div></div><div><span>下标是：</span></div></div><div><div><div>3</div></div><div><span>[ </span><span>1</span><span>,  </span><span>3</span><span>,  </span><span>5</span><span>]</span></div></div><div><div><div>4</div></div><div><span>  </span><span>0</span><span>   </span><span>1</span><span>   </span><span>2</span><span>   正向下标</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>-</span><span>3</span><span><span>  </span><span>-</span></span><span>2</span><span><span>  </span><span>-</span></span><span>1</span><span>  反向下标</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>通过下标访问</span></div></div><div><div><div>8</div></div><div><span>nums[</span><span>0</span><span>]  </span><span># 得到1</span></div></div><div><div><div>9</div></div><div><span><span>num[</span><span>-</span></span><span>2</span><span>]  </span><span># 得到3</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h5>操作方法<a href="#操作方法"><span>#</span></a></h5><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721144007.png" alt="image-20250721144007198" /><figcaption>image-20250721144007198</figcaption></figure><p></p><blockquote><p>不需要背下来，练习两次即可。</p><p>用多了自然就记住了，用不到硬记也是个忘</p><p>需要用的时候有个大概概念印象即可查询AI等工具或翻看笔记</p></blockquote></section></section></section><section><h3>元组<a href="#元组"><span>#</span></a></h3><section><h4>特点<a href="#特点-1"><span>#</span></a></h4><ul>
<li>可以支持非常多的元素在内（<code>2**63 -1</code>）</li>
<li>支持多种不同元素类型的混装</li>
<li><code>不支持修改</code></li>
<li>内部数据有序(有下标索引)</li>
<li>支持重复元素</li>
</ul><blockquote><p>列表当一创建出来后，就固定了内容不变（内部嵌套list除外）</p></blockquote></section><section><h4>语法<a href="#语法-1"><span>#</span></a></h4><p>字面量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>(元素, </span><span>...</span><span>, 元素)</span></span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>(</span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>(</span><span>"itheima"</span><span>, )</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意：</p><p>元组内如果只有1个元素，写起来必须有逗号在内，如：<code>(1, )</code>  <code>("itheima", )</code></p><p>不写逗号则不是元素类型</p><p>变量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> (</span></span><span>"周杰伦"</span><span>, </span><span>"林军杰"</span><span>)</span></div></div><div><div><div>2</div></div><div><span><span>nums </span><span>=</span><span> (</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>空元组</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> ()</span></span></div></div><div><div><div>2</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>tuple</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>嵌套（元组也支持嵌套）</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>age </span><span>=</span><span> ( (</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>), (</span><span>4</span><span>, </span><span>5</span><span>, </span><span>6</span><span>) )</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>元组内有list<a href="#元组内有list"><span>#</span></a></h4><p>当元组内有list的时候，还是遵循元组不可改的特点，但是list内的元素可以修改。</p><p>比如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>age </span><span>=</span><span> (</span></span><span>1</span><span>, </span><span>2</span><span>, [</span><span>3</span><span>, </span><span>4</span><span>, </span><span>5</span><span>])</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>则内部的这个list的内容是可以改的。</p><p>这是因为元组在存储的时候，本质上存的是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>(</span><span>1</span><span>, </span><span>2</span><span>, 地址)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即，存放的是list的内存地址（指针）值。</p><p>所以，只要这个地址不变即可，就满足元组不可修改的特性了。</p><p>至于地址指向的内容区域的内容是否发生变化，则和元组无关。</p><p>所以下列操作中</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>nums </span><span>=</span><span> (</span></span><span>1</span><span>, </span><span>2</span><span>, [</span><span>5</span><span>, </span><span>6</span><span>, </span><span>7</span><span>])</span></div></div><div><div><div>2</div></div><div><span># 正常操作</span></div></div><div><div><div>3</div></div><div><span>nums[</span><span>2</span><span>][</span><span>2</span><span><span>] </span><span>=</span><span> </span></span><span>10</span><span>  </span><span># 可以的</span></div></div><div><div><div>4</div></div><div><span>nums[</span><span>2</span><span><span>].</span><span>append</span><span>(</span></span><span>11</span><span>) </span><span># 可以的</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 错误操作</span></div></div><div><div><div>7</div></div><div><span>nums[</span><span>2</span><span><span>] </span><span>=</span><span> [</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>]  </span><span># 不可以，相当于用新list去替换老的list，即记录的内存地址要变，是不允许的</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>操作方法<a href="#操作方法-1"><span>#</span></a></h4><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721144820.png" alt="image-20250721144819687" /><figcaption>image-20250721144819687</figcaption></figure><p></p><p>因为元组不可变，所以操作方法也少了很多。</p></section></section><section><h3>字符串<a href="#字符串-1"><span>#</span></a></h3><p>字符串：也是数据容器，其内部存放的元素固定为字符，是多个字符的集合体，称之为字符的串串。</p><section><h4>字符串特点<a href="#字符串特点"><span>#</span></a></h4><ul>
<li>可以支持非常多的元素在内（<code>2**63 -1</code>）</li>
<li><code>仅存放字符数据</code></li>
<li><code>不支持修改</code></li>
<li>内部数据有序(<code>有下标索引</code>)</li>
<li>支持重复元素</li>
</ul></section><section><h4>语法<a href="#语法-2"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"itheima"</span></div></div><div><div><div>2</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>'itheima'</span></div></div><div><div><div>3</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"""itheima"""</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>常用操作方法<a href="#常用操作方法"><span>#</span></a></h4><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721145104.png" alt="image-20250721145104314" /><p>重点方法：</p><ul>
<li>replace</li>
<li>split</li>
<li>strip</li>
</ul></section></section><section><h3>可迭代类型<a href="#可迭代类型"><span>#</span></a></h3><p>到目前为止，我们学习到的数据类型中，可迭代类型（可用于for循环的）有：</p><ul>
<li>列表</li>
<li>元组</li>
<li>字符串</li>
</ul><blockquote><p>带有下标的，都可以用于for循环</p></blockquote></section><section><h3>序列的切片<a href="#序列的切片"><span>#</span></a></h3><p>概念：序列是指有序的、<code>有下标索引</code>的数据容器。</p><p>序列是支持切片，语法：</p><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721181708.png" alt="image-20250721181708430" /><p>示例</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>lst </span><span>=</span><span> [</span></span><span>"aa"</span><span>, </span><span>"bb"</span><span>, </span><span>3</span><span>, </span><span>5</span><span>, </span><span>6</span><span>]</span></div></div><div><div><div>2</div></div><div><span># 取出来"bb", 3</span></div></div><div><div><div>3</div></div><div><span>print</span><span>(lst[</span><span>1</span><span>:</span><span>3</span><span>])</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 取出来 5, 3, "bb"</span></div></div><div><div><div>6</div></div><div><span>print</span><span><span>(lst[</span><span>-</span></span><span>2</span><span><span>:</span><span>-</span></span><span>5</span><span><span>:</span><span>-</span></span><span>1</span><span>])</span></div></div><div><div><div>7</div></div><div><span># 反向操作即步长为负数，要求 开始下标在结束下标的右侧</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span><span>t </span><span>=</span><span> (</span></span><span>1</span><span>, </span><span>3</span><span>, </span><span>5</span><span>, </span><span>7</span><span>, </span><span>9</span><span>, </span><span>11</span><span>, </span><span>13</span><span>, </span><span>15</span><span>, </span><span>17</span><span>)</span></div></div><div><div><div>10</div></div><div><span># 取出来 3 7</span></div></div><div><div><div>11</div></div><div><span>print</span><span>(t[</span><span>1</span><span>:</span><span>4</span><span>:</span><span>2</span><span>])</span></div></div><div><div><div>12</div></div><div><span># 取出来 15, 9, 3</span></div></div><div><div><div>13</div></div><div><span>print</span><span><span>(t[</span><span>-</span></span><span>2</span><span>:</span><span>0</span><span><span>:</span><span>-</span></span><span>3</span><span>])</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>name </span><span>=</span><span> </span></span><span>"itheima 666 and itcast"</span></div></div><div><div><div>16</div></div><div><span># 反转字符串</span></div></div><div><div><div>17</div></div><div><span>print</span><span><span>(name[::</span><span>-</span></span><span>1</span><span>])</span></div></div><div><div><div>18</div></div><div><span># d a 6 6 a</span></div></div><div><div><div>19</div></div><div><span>print</span><span><span>(name[</span><span>-</span></span><span>8</span><span>:</span><span>5</span><span><span>:</span><span>-</span></span><span>2</span><span>])</span></div></div><div><div><div>20</div></div><div><span># 取出来666</span></div></div><div><div><div>21</div></div><div><span>print</span><span>(name[</span><span>8</span><span>:</span><span>11</span><span>])       </span><span># 步长不写默认为1</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>"""</span></div></div><div><div><div>24</div></div><div><span>序列都可以切片（序列就是有下标的）</span></div></div><div><div><div>25</div></div><div><span>- 字符串</span></div></div><div><div><div>26</div></div><div><span>- 元组</span></div></div><div><div><div>27</div></div><div><span>- 列表</span></div></div><div><div><div>28</div></div><div><span>"""</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>集合<a href="#集合"><span>#</span></a></h3><p>特点：</p><ul>
<li>可以支持非常多的元素在内（<code>2**63 -1</code>）</li>
<li>存放任意数据类型，支持混装</li>
<li>支持修改</li>
<li><code>内部数据无序（无下标）</code></li>
<li><code>不支持重复元素</code></li>
</ul><blockquote><p>集合内部是去重的，重复的元素只保留一份</p><p>因为去重，所以内部无序（实际顺序是hash表顺序、如果hash算法不同，顺序可能不同）</p></blockquote><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>{元素, </span><span>...</span><span>, </span><span>...</span><span>, 元素}</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>字面量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>, </span><span>3</span><span>, </span><span>3</span><span>}  </span><span># 实际存储是 {1, 2, 3}  内部去重了</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>变量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>s </span><span>=</span><span> {</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>空集合</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>s </span><span>=</span><span> </span></span><span>set</span><span>()</span></div></div><div><div><div>2</div></div><div><span># 注意，s = {} 不是空集合，而是空字典</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>嵌套</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>s </span><span>=</span><span> {{</span></span><span>1</span><span>,</span><span>2</span><span>}, {</span><span>3</span><span>, </span><span>4</span><span>}, {</span><span>5</span><span>, </span><span>6</span><span>}}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>常用操作<a href="#常用操作"><span>#</span></a></h4><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721181434.png" alt="image-20250721181434136" /><blockquote><p>这些操作基本用不到，写两遍即可</p></blockquote></section></section><section><h3>字典<a href="#字典"><span>#</span></a></h3><p>概念：字典内部是存放了一堆的键值对，键值对是：<code>Key:Value</code>的对</p><p>在字典中<code>Key</code> 不允许重复，如果重复则新数据覆盖老数据。</p><p>字典可以基于<code>Key</code> 去找到其对应的<code>Value</code></p><ul>
<li>可以支持非常多的元素在内（<code>2**63 -1</code>）</li>
<li>存放任意数据类型，支持混装
<ul>
<li>Key数据类型不可为字典，一般类型用：字符串、整数</li>
<li>Value数据类型随意，不限制</li>
</ul>
</li>
<li>支持修改</li>
<li><code>内部数据无序（无下标）</code></li>
<li><code>不支持重复Key</code></li>
</ul><section><h4>语法<a href="#语法-3"><span>#</span></a></h4><p>空字典</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>d </span><span>=</span><span> {}</span></span></div></div><div><div><div>2</div></div><div><span><span>d </span><span>=</span><span> </span></span><span>dict</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>字面量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span><span>"a"</span><span>: </span><span>1</span><span>, </span><span>"b"</span><span>: </span><span>2</span><span>}</span></div></div><div><div><div>2</div></div><div><span># 是Key:Value的形式</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>变量</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>d </span><span>=</span><span> {</span></span><span>"周杰轮"</span><span>: </span><span>99</span><span>, </span><span>"王力鸿"</span><span>: </span><span>88</span><span>, </span><span>"林军杰"</span><span>: </span><span>77</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>嵌套</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>d </span><span>=</span><span> {</span></span></div></div><div><div><div>2</div></div><div><span>    </span><span>"周杰轮"</span><span>: {</span><span>"语文"</span><span>: </span><span>99</span><span>, </span><span>"数学"</span><span>: </span><span>88</span><span>, </span><span>"英语"</span><span>: </span><span>22</span><span>},</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"林军杰"</span><span>: {</span><span>"语文"</span><span>: </span><span>88</span><span>, </span><span>"数学"</span><span>: </span><span>66</span><span>, </span><span>"英语"</span><span>: </span><span>21</span><span>},</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"王力鸿"</span><span>: {</span><span>"语文"</span><span>: </span><span>66</span><span>, </span><span>"数学"</span><span>: </span><span>11</span><span>, </span><span>"英语"</span><span>: </span><span>33</span><span>},</span></div></div><div><div><div>5</div></div><div><span>}</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>d </span><span>=</span><span> {</span></span></div></div><div><div><div>8</div></div><div><span>    </span><span>"周杰轮"</span><span>: [</span><span>"唱"</span><span>, </span><span>"跳"</span><span>, </span><span>"rap"</span><span>],</span></div></div><div><div><div>9</div></div><div><span>    </span><span>"林军杰"</span><span>: [</span><span>"吃"</span><span>, </span><span>"喝"</span><span>, </span><span>"玩"</span><span>],</span></div></div><div><div><div>10</div></div><div><span>    </span><span>"王力鸿"</span><span>: [</span><span>"睡"</span><span>, </span><span>"呆"</span><span>, </span><span>"憨"</span><span>]</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>常用操作<a href="#常用操作-1"><span>#</span></a></h4><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721182404.png" alt="image-20250721182403867" /><figcaption>image-20250721182403867</figcaption></figure><p></p></section></section><section><h3>数据容器的对比<a href="#数据容器的对比"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721182540.png" alt="image-20250721182540450" /><figcaption>image-20250721182540450</figcaption></figure><p></p></section><section><h3>数据容器的通用操作<a href="#数据容器的通用操作"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721182559.png" alt="image-20250721182559523" /><figcaption>image-20250721182559523</figcaption></figure><p></p></section><section><h3>字符串的大小比较<a href="#字符串的大小比较"><span>#</span></a></h3><blockquote><p>字符串（中文、英文、数字、符号等）都是存储的数字，显示的是字符串</p><ul>
<li>英文、数字、符号，都是以ASCII码表，比如”a”对应97  “A”对应65</li>
<li>中文，一般以UTF-8或GBK编码（UTF-8更通用），也有对应的数字</li>
</ul></blockquote><p>所以字符串比较本质是比较各个字符的数字大小。</p><p>字符串是按位比较，一位位比，每一个位出结果则整体大小就确定。</p><p>比如：</p><ul>
<li><code>abe</code> 大于<code>abc</code> 以为<code>a==a</code>  <code>b==b</code>  <code>e&gt;c</code>  在第三位确定了大小</li>
<li><code>abc</code> 大于<code>ab</code>  因为<code>a==a  b==b</code> 但是<code>c &gt; 啥也没有</code></li>
<li><code>21</code> 大于 <code>123</code> 因为 <code>"2" &gt; "1"</code> 则直接出结果</li>
</ul><p>ASCII码表如下：</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/21/20250721183103.png" alt="image-20250721183103573" /><figcaption>image-20250721183103573</figcaption></figure><p></p><p>常用口诀（用于英文和数字）</p><ul>
<li>小写大于大写字母</li>
<li>大写字母大于数字</li>
<li>其余查表吧</li>
</ul></section></section>
<section><h2>函数高阶应用<a href="#函数高阶应用"><span>#</span></a></h2><section><h3>返回多个返回值<a href="#返回多个返回值"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>...</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div><span>    </span><span>return</span><span><span> 值,值,</span><span>...</span><span>,值    </span></span><span># 返回多个值，逗号分隔即可</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 调用的时候用多个变量接收</span></div></div><div><div><div>6</div></div><div><span><span>变量,变量,</span><span>...</span><span>,变量 </span><span>=</span><span> </span><span>func</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>...</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> </span><span>1</span><span>, </span><span>2</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>x, y </span><span>=</span><span> </span><span>func</span><span>()</span></span></div></div><div><div><div>5</div></div><div><span># x接收1</span></div></div><div><div><div>6</div></div><div><span># y接收2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>原理<a href="#原理"><span>#</span></a></h4><p><code>return 1, 2</code> 这种写法，本质上是返回了<code>元组</code> ，即返回的是：<code>(1, 2)</code></p><p>所以我们用2个变量接收，如：<code>x, y = func()</code> 会自动将元组的第一个元素赋值给x，第二个元素赋值给y</p><p>即：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>...</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>return</span><span> </span><span>1</span><span>, </span><span>2</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>x </span><span>=</span><span> </span><span>func</span><span>()</span></span></div></div><div><div><div>5</div></div><div><span># x的值是：(1, 2)   是一个元组</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>函数的多种传参形式<a href="#函数的多种传参形式"><span>#</span></a></h3><section><h4>位置传参<a href="#位置传参"><span>#</span></a></h4><blockquote><p>按照形式参数写的时候的位置，在调用函数的时候，传入实际参数。</p><p>实际参数和形式参数是一一对应的。</p></blockquote><p>示例：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>func</span><span>(</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>此时：1传入给x、2传入给y、3传入给z</p><ul>
<li>数量一致</li>
<li>顺序一致</li>
</ul></section><section><h4>关键字传参<a href="#关键字传参"><span>#</span></a></h4><p>语法：<code>形式参数名=实际参数值</code></p><p>示例</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>func</span><span>(</span></span><span>x</span><span>=</span><span>1</span><span>, </span><span>y</span><span>=</span><span>2</span><span>, </span><span>z</span><span>=</span><span>3</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>传入参数的时候，是指定了形式参数名称，这样的话，可以不按照形式参数的顺序进行传入。</p><p>比如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>func</span><span>(</span></span><span>y</span><span>=</span><span>2</span><span>, </span><span>z</span><span>=</span><span>3</span><span>, </span><span>x</span><span>=</span><span>1</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样写，同样是 x得到1、y得到2、z得到3</p><p>同时关键字传入参数和位置参数可以混用，如下</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>func</span><span>(</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>z</span><span>=</span><span>3</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>关键字传参的写法，必须在位置传参的右侧。即如下写法是<code>错误的</code></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>func</span><span>(</span></span><span>x</span><span>=</span><span>1</span><span>, </span><span>2</span><span>, </span><span>z</span><span>=</span><span>3</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>x=1写在了位置参数的左侧，不符合要求</p></section></section><section><h3>缺省参数（默认值参数）<a href="#缺省参数默认值参数"><span>#</span></a></h3><p>在编写函数的时候，可以给某个参数提供默认值，此参数即可实现</p><ul>
<li>调用的时候，不传入值，则使用默认值</li>
<li>如果传入值，则使用传入值</li>
</ul><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span><span> </span><span>z</span></span><span>=</span><span>10</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在调用的时候：</p><ul>
<li>写的是：<code>func(1, 2)</code> 可以看到只给x和y传入值，没有提供z的值，则z使用默认值10</li>
<li>写的是：<code>func(1, 2, 3)</code> 则z使用提供的3</li>
</ul><p>注意，<code>带有默认值的参数，必须在没有默认值参数的右侧。</code></p><p>如下写法是错误的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>=</span><span>1</span><span>,</span><span><span> </span><span>z</span></span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>y</code>这个带有默认值的参数，在非默认值参数z的左侧，是不符合语法要求，错误。</p></section><section><h3>不定长参数（元组接收）<a href="#不定长参数元组接收"><span>#</span></a></h3><p>概念：可以写一个参数，表示不定长（参数数量不确定），将传入的参数全部收集到一个元组内。</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span> </span><span>*</span><span>z</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>x和y是普通参数</li>
<li>z是不定长（元组接受）的参数，z就是一个元组，收集传入的实际参数</li>
</ul><p>示例</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span> </span><span>*</span><span>z</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>...</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>func</span><span>(</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>3</span><span>, </span><span>4</span><span>, </span><span>5</span><span>, </span><span>6</span><span>, </span><span>7</span><span>, </span><span>8</span><span>, </span><span>9</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如上代码：</p><ul>
<li>1和2是提供给普通参数x和y</li>
<li>后续的3, 4, 5, 6, 7, 8, 9 全部存入了参数z中</li>
<li>参数z是一个元组</li>
</ul></section><section><h3>不定长参数（字典接收）<a href="#不定长参数字典接收"><span>#</span></a></h3><p>概念：可以写一个参数，表示不定长（参数数量不确定），将传入的参数全部收集到一个<code>字典</code>内。</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span> </span><span>**</span><span>z</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>x和y是普通参数</li>
<li>z是不定长（元组接受）的参数，z就是一个元组，收集传入的实际参数</li>
</ul><blockquote><p>用字典接收是2个<code>*</code>号</p></blockquote><p>示例</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>,</span><span> </span><span>**</span><span>z</span><span>):</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>...</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span><span>func</span><span>(</span></span><span>1</span><span>, </span><span>2</span><span>, </span><span>id</span><span>=</span><span>1</span><span>,</span><span>name</span><span>=</span><span>'a'</span><span>,</span><span>addr</span><span>=</span><span>'shanghai'</span><span>,</span><span>gender</span><span>=</span><span>'男'</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如上：</p><ul>
<li>1和2传给普通参数x和y</li>
<li>后续的id=1,name=‘a’,addr=‘shanghai’,gender=‘男’会被收集为字典，存入变量z（字典）中</li>
<li>即z内部的数据是4个键值对</li>
</ul><section><h4>不定长元组接收和不定长字典接收混用<a href="#不定长元组接收和不定长字典接收混用"><span>#</span></a></h4><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>name</span><span>,</span><span><span> </span><span>age</span></span><span>,</span><span> </span><span>*</span><span>args</span><span>,</span><span> </span><span>**</span><span>kwargs</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"我是</span><span>{</span><span>name</span><span>}</span><span>, 年龄</span><span>{</span><span>age</span><span>}</span><span>岁, 我的爱好有："</span><span>, </span><span>end</span><span>=</span><span>""</span><span>)</span></div></div><div><div><div>3</div></div><div><span>    </span><span>for</span><span> i </span><span>in</span><span> args:          </span><span># args是元组</span></div></div><div><div><div>4</div></div><div><span>        </span><span>print</span><span>(i, </span><span>end</span><span>=</span><span>" "</span><span>)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>    </span><span>print</span><span>()</span></div></div><div><div><div>7</div></div><div><span>    </span><span>print</span><span>(</span><span>"我的其它信息："</span><span>, </span><span>end</span><span>=</span><span>" "</span><span>)</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>    </span><span>for</span><span> key </span><span>in</span><span> kwargs:      </span><span># kwargs是字典</span></div></div><div><div><div>10</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"</span><span>{</span><span>key</span><span>}</span><span>: </span><span>{</span><span>kwargs[key]</span><span>}</span><span>"</span><span>, </span><span>end</span><span>=</span><span>"、"</span><span>)</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span>print</span><span>()</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>func</span><span>(</span></span><span>"周杰轮"</span><span>, </span><span>11</span><span>, </span><span>"唱"</span><span>, </span><span>"rap"</span><span>, </span><span>"跳"</span><span>, </span><span>"打篮球"</span><span>, </span><span>addr</span><span>=</span><span>"石岩"</span><span>, </span><span>id</span><span>=</span><span>123</span><span>, </span><span>money</span><span>=</span><span>20000</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>如上代码，可以实现传入参数的2种不定长方式的混合使用。</p><ul>
<li>“周杰轮”, 11是提供给name和age（基于位置传参）</li>
<li><code>"唱", "rap", "跳", "打篮球"</code> 提供给不定长参数<code>*args</code> ，被收集到args这个元组内</li>
<li><code>addr="石岩", id=123, money=20000</code>  提供给不定长参数<code>**kwargs</code> ，被收集到kwargs这个字典内</li>
</ul></section></section><section><h3>函数作为参数传递<a href="#函数作为参数传递"><span>#</span></a></h3><p>概念：一般使用函数，都是传入的数据，如数字、字符串、数据容器。这种传递我们称之为：数据的传递。</p><p>除此以外，函数也可以接收函数作为参数传递，这样就不是传递的数据了，而是传递的<code>计算逻辑（函数）</code></p><p>示例</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"""</span></div></div><div><div><div>2</div></div><div><span>写函数的时候：</span></div></div><div><div><div>3</div></div><div><span>- 形式参数，可以接收函数传入</span></div></div><div><div><div>4</div></div><div><span>- 实际参数，真的可以传入一个函数</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>这种写法称之为函数式编程，核心思想是：传入的是计算逻辑</span></div></div><div><div><div>7</div></div><div><span>"""</span></div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>compute</span><span>):</span></div></div><div><div><div>9</div></div><div><span>    </span><span># 被计算数据固定为1和2，要计算的逻辑取决于传入的compute函数</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>result </span><span>=</span><span> </span><span>compute</span><span>(</span><span>1</span><span>, </span><span>2</span><span>)</span></div></div><div><div><div>11</div></div><div><span>    </span><span>print</span><span>(result)</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>def</span><span> </span><span>compute1</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>):</span></div></div><div><div><div>15</div></div><div><span>    </span><span>return</span><span><span> x </span><span>+</span><span> y</span></span></div></div><div><div><div>16</div></div><div><span>def</span><span> </span><span>compute2</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>):</span></div></div><div><div><div>17</div></div><div><span>    </span><span>return</span><span><span> x </span><span>-</span><span> y</span></span></div></div><div><div><div>18</div></div><div><span>def</span><span> </span><span>compute3</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>):</span></div></div><div><div><div>19</div></div><div><span>    </span><span>return</span><span><span> x </span><span>*</span><span> y</span></span></div></div><div><div><div>20</div></div><div><span>def</span><span> </span><span>compute4</span><span>(</span><span>x</span><span>,</span><span><span> </span><span>y</span></span><span>):</span></div></div><div><div><div>21</div></div><div><span>    </span><span>return</span><span><span> x </span><span>/</span><span> y</span></span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span># 传入的是compute函数，而非变量和数据</span></div></div><div><div><div>25</div></div><div><span># 传的是函数也就是传的是计算的逻辑</span></div></div><div><div><div>26</div></div><div><span><span>func</span><span>(compute1)</span></span></div></div><div><div><div>27</div></div><div><span><span>func</span><span>(compute2)</span></span></div></div><div><div><div>28</div></div><div><span><span>func</span><span>(compute3)</span></span></div></div><div><div><div>29</div></div><div><span><span>func</span><span>(compute4)</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>如上代码，传递的<code>compute 1 2 3 4</code> 就是4个不同的函数，代表4种不同的计算逻辑。</p><blockquote><p>函数是可以作为参数传递的。</p></blockquote></section><section><h3>lambda匿名函数<a href="#lambda匿名函数"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>lambda</span><span> </span><span>形参列表</span><span><span>: </span><span>函数体</span><span>(一行)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>作用：用来快速实现一个匿名的函数</p><ul>
<li>带有名字的函数是：<code>def 函数名(...) ...</code>
<ul>
<li>有名字可以重复调用</li>
</ul>
</li>
<li>匿名函数就是：<code>lambda x, y: ...</code>
<ul>
<li>没有名字，只是临时用一次</li>
</ul>
</li>
</ul><p>示例</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>def</span><span> </span><span>func</span><span>(</span><span>compute</span><span>):</span></div></div><div><div><div>2</div></div><div><span>    </span><span># 被计算数据固定为1和2，要计算的逻辑取决于传入的compute函数</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>result </span><span>=</span><span> </span><span>compute</span><span>(</span><span>1</span><span>, </span><span>2</span><span>)</span></div></div><div><div><div>4</div></div><div><span>    </span><span>print</span><span>(result)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 调用func4次，完成+-*/</span></div></div><div><div><div>8</div></div><div><span># 这些函数仅用一次，不用于重复使用，所以用lambda快速实现</span></div></div><div><div><div>9</div></div><div><span><span>func</span><span>(</span></span><span>lambda</span><span> </span><span>x</span><span>, </span><span>y</span><span><span>: x </span><span>+</span><span> y)</span></span></div></div><div><div><div>10</div></div><div><span><span>func</span><span>(</span></span><span>lambda</span><span> </span><span>x</span><span>, </span><span>y</span><span><span>: x </span><span>-</span><span> y)</span></span></div></div><div><div><div>11</div></div><div><span><span>func</span><span>(</span></span><span>lambda</span><span> </span><span>x</span><span>, </span><span>y</span><span><span>: x </span><span>*</span><span> y)</span></span></div></div><div><div><div>12</div></div><div><span><span>func</span><span>(</span></span><span>lambda</span><span> </span><span>x</span><span>, </span><span>y</span><span><span>: x </span><span>/</span><span> y)</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>上面调用的4次func，传入的4个不同的计算逻辑</li>
<li>这4个不同的计算逻辑就是4个匿名函数，只用一次</li>
</ul><blockquote><p>在以后的功能开发中，会遇到一些仅需要传入计算逻辑（函数）的函数，就可以用lambda快速实现。</p></blockquote><p>匿名函数也可以有名字，用变量接收即可，如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>chengfa </span><span>=</span><span> </span></span><span>lambda</span><span> </span><span>x</span><span>, </span><span>y</span><span><span>: x </span><span>*</span><span> y</span></span></div></div><div><div><div>2</div></div><div><span>print</span><span><span>(</span><span>chengfa</span><span>(</span></span><span>1</span><span>, </span><span>3</span><span>))</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种写法一般不用，要是想给函数个名字，为何不用<code>def</code></p></section></section>
<section><h2>文件操作<a href="#文件操作"><span>#</span></a></h2><section><h3>文件编码<a href="#文件编码"><span>#</span></a></h3><blockquote><p>计算机只认识0和1，包括内存、硬盘、网线等等这些IT设备，都是只认识0和1二进制。</p></blockquote><p>为了能够存储丰富的文本数据在硬盘里面，我们需要将文本转换为二进制存储。</p><p>动作：</p><ul>
<li>写入：文本 -&gt;转换-&gt;二进制-&gt;存入硬盘</li>
<li>读取：读取硬盘-&gt;二进制-&gt;转换-&gt;文本</li>
</ul><p>转换的动作就是二进制和文本之间的对照关系，这个对照关系，我们称之为编码。</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/23/20250723175655.png" alt="image-20250723175654500" /><figcaption>image-20250723175654500</figcaption></figure><p></p><blockquote><p>如上图，如果用编码表A存储数据，以编码表B解析数据，则会出现问题，无法解析或解析错误。</p><p>所以我们的文件在保持的时候有编码，在用的时候也有编码，要确保是一致的，否则有问题</p></blockquote><p>当前，全球范围内最火的编码是：<code>UTF-8</code> ，这是全球通用编码表，支持全世界范围内绝大多数语言。</p><p>没特殊需求，首选UTF8，同时UTF8也是绝大多数软件的默认编码。</p><p>偶尔我们可能还会见到GBK编码，这个是中文专用编码，在某些老旧软件系统中还有。</p></section><section><h3>文件的打开<a href="#文件的打开"><span>#</span></a></h3><p>Python操作文件，就4个操作：</p><ul>
<li>打开</li>
<li>关闭</li>
<li>读取</li>
<li>写入</li>
</ul><p>打开文件的语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>f </span><span>=</span><span> </span></span><span>open</span><span>(</span><span>file</span><span>, mode, </span><span>encoding</span><span>=</span><span>?</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>参数1，表示文件的路径（在哪）</li>
<li>参数2，工作模式</li>
<li>参数3，编码，无脑UTF-8（有指定编码除外）</li>
</ul><blockquote><p>返回值变量是一个File类型的类对象，其内置有操作方法（类型列表）</p><p>比如：<code>f.close()</code> 调用close函数是需要用<code>f.</code> 点出来的</p></blockquote><p>工作模式：</p><ul>
<li><code>r</code> ，只读</li>
<li><code>w</code> ，覆盖写入</li>
<li><code>a</code> ，追加写入</li>
<li><code>rb</code> ，只读（二进制）</li>
<li><code>wb</code> ，覆盖写入（二进制）</li>
<li><code>ab</code> ，追加写入（二进制）</li>
</ul></section><section><h3>文件关闭<a href="#文件关闭"><span>#</span></a></h3><p>文件关闭和文件打开是<code>配套操作</code></p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>f </span><span>=</span><span> </span></span><span>open</span><span>()</span></div></div><div><div><div>2</div></div><div><span><span>f.</span><span>close</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>调用<code>f.close()</code> 即可关闭文件</p><p>注意：<code>f.close()</code> 还会同步<code>flush</code> 写入缓冲区</p></section><section><h3>文件读取<a href="#文件读取"><span>#</span></a></h3><p>前置要求：需要先打开文件</p><p>假设是：<code>f = open(...)</code></p><section><h4>read([num])方法<a href="#readnum方法"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>content </span><span>=</span><span> f.</span><span>read</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以读取整个文件的内容，返回字符串</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>content </span><span>=</span><span> f.</span><span>read</span><span>(</span></span><span>5</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>读取5个长度的内容，返回作为字符串</p></section><section><h4>readlines方法<a href="#readlines方法"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>lst </span><span>=</span><span> f.</span><span>readlines</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>按照行进行切分，将全部行放入list表中，返回</p></blockquote><blockquote><p>注意：每一行都会在结尾，附带<code>\n</code> 需要自行处理掉</p></blockquote></section><section><h4>readline方法<a href="#readline方法"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>line_str </span><span>=</span><span> f.</span><span>readline</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>调用一次<code>readline</code> 就读取一行，<code>返回作为字符串</code></p><p>如果要读取多行，要自行多次调用</p><blockquote><p>同样每次读取的一行后面会带有<code>\n</code> 需要自行处理掉</p></blockquote></section><section><h4>循环读取<a href="#循环读取"><span>#</span></a></h4><p>方式1</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> line </span><span>in</span><span> f:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span><span>     </span><span># line 就是每一行的字符串</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>方式2</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> line </span><span>in</span><span><span> f.</span><span>readlines</span><span>():</span></span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span><span>     </span><span># line 就是每一行的字符串</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>with open 方式<a href="#with-open-方式"><span>#</span></a></h4><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>with</span><span> </span><span>open</span><span><span>(</span><span>...</span><span>) </span></span><span>as</span><span> f:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这种写法是一种快捷写法，包含了：打开、操作、关闭</p><p>无需写<code>f.close()</code> 会自动关闭文件的。</p></section></section></section>
<section><h2>异常<a href="#异常"><span>#</span></a></h2><section><h3>概念<a href="#概念-2"><span>#</span></a></h3><p>异常称之为：<code>BUG（虫子）</code></p><p>异常：程序在运行的时候出现问题，导致程序无法继续的软件缺陷。</p></section><section><h3>异常捕获<a href="#异常捕获"><span>#</span></a></h3><section><h4>为何<a href="#为何"><span>#</span></a></h4><p>我们无法确保全部代码都是100%OK的</p><p>在</p><ul>
<li>可能出现问题的代码上</li>
<li>做如果出现问题后的预案</li>
</ul><p>这就是异常捕获的意义。</p></section><section><h4>语法<a href="#语法-4"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>try</span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>可能有异常的代码</span></div></div><div><div><div>3</div></div><div><span>except</span><span> 异常类型 </span><span>as</span><span> 变量:</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>出现异常的处理代码</span></div></div><div><div><div>5</div></div><div><span>except</span><span> 异常类型 </span><span>as</span><span> 变量:</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>出现异常的处理代码</span></div></div><div><div><div>7</div></div><div><span>......</span></div></div><div><div><div>8</div></div><div><span>except</span><span> 异常类型 </span><span>as</span><span> 变量:</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>出现异常的处理代码</span></div></div><div><div><div>10</div></div><div><span>else</span><span>:</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>没有异常的处理代码</span></div></div><div><div><div>12</div></div><div><span>finally</span><span>:</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>有没有异常都会执行的代码</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>try</code> 只有在try内部的代码，才会被捕获异常</li>
<li><code>except</code> 是匹配机制，用来匹配特定异常
<ul>
<li>特殊异常类型：<code>Exception</code> 所有异常的父类（顶级异常），任何异常都可以用<code>Exception</code> 抓住</li>
</ul>
</li>
<li><code>else（可选）</code> ，没有异常的时候，会进入else的语句段</li>
<li><code>finally（可选）</code> 有没有异常，都会执行的</li>
</ul></section></section><section><h3>异常的传递性<a href="#异常的传递性"><span>#</span></a></h3><p>如图：</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/23/20250723181042.png" alt="image-20250723181041391" /><figcaption>image-20250723181041391</figcaption></figure><p></p><p>异常会从<code>func01</code>中传递到<code>func02</code> 传递到<code>main</code> 函数中</p><p>即异常会向上（函数层级）不停的传递，直到被捕获为止。</p><p>如果没有被捕获到，则进行报错，停止程序。</p><p>报错的函数链条</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/23/20250723181202.png" alt="image-20250723181202248" /><figcaption>image-20250723181202248</figcaption></figure><p></p><p>如图，异常有传递性，在出现错误后，报错信息里面可以将传递的链条都提示出来</p><p>我们可以根据这个链条找到具体的报错（一般就找最后一行）</p></section></section>
<section><h2>模块&amp;包<a href="#模块包"><span>#</span></a></h2><section><h3>模块<a href="#模块"><span>#</span></a></h3><section><h4>概念<a href="#概念-3"><span>#</span></a></h4><p>模块：一个完整的<code>.py</code>文件，里面包含相关Python代码（含有函数、类、变量等），称之为一个模块。</p><p>简单来说：一个python代码文件就是一个模块。</p></section><section><h4>用途<a href="#用途"><span>#</span></a></h4><p>可以通过<code>import</code>  <code>from</code> 语句，从其它模块（代码文件）中导入相关的函数和变量和类等资源去使用</p><blockquote><p>我们写代码不可能将全部代码都写到一个文件内，可以拆分成多个文件，通过import的方式从其它文件引用功能</p></blockquote></section><section><h4>语法<a href="#语法-5"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span><span>from</span><span><span> 包</span><span>|</span><span>模块] </span></span><span>import</span><span><span> (包</span><span>|</span><span>模块</span><span>|</span><span>函数</span><span>|</span><span>变量</span><span>|</span><span>类</span><span>...</span><span>) [</span></span><span>as</span><span> 别名]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>[]</code>代表可选</li>
<li><code>|</code> 在语法中表示 或</li>
<li><code>()</code> 在语法中表示必写</li>
</ul><p>示例</p><p>代码文件<code>B.py</code> 中有函数：<code>B1</code>  <code>B2</code>  <code>B3</code></p><p>在代码文件A中想要使用这三个函数，可以有如下写法：</p><p>方法1</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> B</span></div></div><div><div><div>2</div></div><div><span><span>B.</span><span>B1</span><span>()</span></span></div></div><div><div><div>3</div></div><div><span><span>B.</span><span>B2</span><span>()</span></span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 或者带有别名</span></div></div><div><div><div>6</div></div><div><span>import</span><span> B </span><span>as</span><span> b</span></div></div><div><div><div>7</div></div><div><span><span>b.</span><span>B1</span><span>()</span></span></div></div><div><div><div>8</div></div><div><span><span>b.</span><span>B2</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>方法2</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> B </span><span>import</span><span> B1</span></div></div><div><div><div>2</div></div><div><span><span>B1</span><span>()</span></span></div></div><div><div><div>3</div></div><div><span># 此时无法使用B2 B3 因为没有导入</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 或者</span></div></div><div><div><div>6</div></div><div><span>from</span><span> B </span><span>import</span><span> B1, B2  </span><span># 此时无法使用B3 因为没有导入</span></div></div><div><div><div>7</div></div><div><span><span>B1</span><span>()</span></span></div></div><div><div><div>8</div></div><div><span><span>B2</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>方法3</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> B </span><span>import</span><span><span> </span><span>*</span></span></div></div><div><div><div>2</div></div><div><span><span>B1</span><span>()</span></span></div></div><div><div><div>3</div></div><div><span><span>B2</span><span>()</span></span></div></div><div><div><div>4</div></div><div><span><span>B3</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>这种写法不建议，因为不能确定B1 B2 B3和模块B的关系</p></blockquote></section></section><section><h3>Python内置变量<a href="#python内置变量"><span>#</span></a></h3><section><h4><code>__name__</code> 变量<a href="#__name__-变量"><span>#</span></a></h4><p><code>__name__</code> 变量是Python自带的，任何一个Python代码文件内部，都是隐式（你不写也有）的附带这个变量。</p><p>功能：</p><ul>
<li>当这个代码文件，作为Python程序执行的时候，此变量记录值是：<code>__main__</code></li>
<li>当这个代码文件，作为模块被其它代码文件导入的时候，此变量记录值是：<code>代码文件名本身</code></li>
</ul><p>基于这个特性，我们可以区分这个文件是执行了还是被导入了。</p><p>常见用法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>'__main__'</span><span>:</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>xxx</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当此文件被作为程序执行，才会执行xxx函数</p><p>如果是作为模块被其它文件导入，上述的<code>if</code> 判断无法成立，则xxx不会执行</p></section><section><h4><code>__all__</code> 变量<a href="#__all__-变量"><span>#</span></a></h4><p>此变量也是隐式存在于每一个代码文件中，默认值是 <code>*</code></p><p>它表示的是，如果文件被<code>from 模块 import *</code> 被导入，则内部的哪些函数可以被<code>*</code> 导入</p><p>默认，是全部</p><p>如果不想要全部的话，可以手动赋值此变量。示例如下：</p><p>文件B</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>__all__</span><span><span> </span><span>=</span><span> [</span></span><span>"B1"</span><span>, </span><span>"B2"</span><span>]</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>def</span><span> </span><span>B1</span><span>():</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>5</div></div><div><span>def</span><span> </span><span>B2</span><span>():</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>...</span></div></div><div><div><div>7</div></div><div><span>def</span><span> </span><span>B3</span><span>():</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>当在文件A中使用语句：<code>from B import *</code> 则只会自动导入B的<code>B1</code> 和<code>B2</code> 2个函数，因为<code>__all__</code> 变量内记录的只有这2个</p><p>如果删除<code>__all__</code> 变量，则可以自动导入全部的函数。</p></section></section><section><h3>包<a href="#包"><span>#</span></a></h3><p>概念：Python的包是一个特殊文件夹，这个文件夹内包含：</p><ol>
<li><code>__init__.py</code>的文件</li>
<li>其它模块（.py）文件</li>
</ol><p>简单来说，包就是管理非常多模块的一个层级。</p><p>比如新建一个包，只需要：</p><ol>
<li>创建一个文件夹，在文件夹内创建<code>__init__.py</code>文件</li>
<li>将其它模块（<code>.py</code>）文件放入此文件夹</li>
</ol><p>此时就得到了一个包。</p><p>示例：</p><p>文件夹<code>my_package</code>内有</p><ul>
<li><code>__init__.py</code>文件</li>
<li>有<code>B.py</code> 模块文件，这个模块内提供<code>B1</code>  <code>B2</code>   <code>B3</code> 三个函数</li>
</ul><p>如果想要使用这三个函数可以：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 方式1（不推荐不好写）</span></div></div><div><div><div>2</div></div><div><span>import</span><span> my_package.B</span></div></div><div><div><div>3</div></div><div><span><span>my_package.B.</span><span>B1</span><span>()</span></span></div></div><div><div><div>4</div></div><div><span><span>my_package.B.</span><span>B2</span><span>()</span></span></div></div><div><div><div>5</div></div><div><span><span>my_package.B.</span><span>B3</span><span>()</span></span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 方式2 推荐好写</span></div></div><div><div><div>8</div></div><div><span>from</span><span> my_package </span><span>import</span><span> B</span></div></div><div><div><div>9</div></div><div><span><span>B.</span><span>B1</span><span>()</span></span></div></div><div><div><div>10</div></div><div><span><span>B.</span><span>B2</span><span>()</span></span></div></div><div><div><div>11</div></div><div><span><span>B.</span><span>B3</span><span>()</span></span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 方式3 也推荐</span></div></div><div><div><div>14</div></div><div><span>from</span><span> my_package.B </span><span>import</span><span> B1</span></div></div><div><div><div>15</div></div><div><span><span>B1</span><span>()    </span></span><span># 此时无法用B2 和B3  因为没导入</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 方式4 不推荐 可读性不好</span></div></div><div><div><div>18</div></div><div><span>from</span><span> my_package </span><span>import</span><span><span> </span><span>*</span></span></div></div><div><div><div>19</div></div><div><span><span>B.</span><span>B1</span><span>()</span></span></div></div><div><div><div>20</div></div><div><span><span>B.</span><span>B2</span><span>()</span></span></div></div><div><div><div>21</div></div><div><span><span>B.</span><span>B3</span><span>()</span></span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span># 方式5 不推荐 可读性不好</span></div></div><div><div><div>24</div></div><div><span>from</span><span> my_package.B </span><span>import</span><span><span> </span><span>*</span></span></div></div><div><div><div>25</div></div><div><span><span>B1</span><span>()</span></span></div></div><div><div><div>26</div></div><div><span><span>B2</span><span>()</span></span></div></div><div><div><div>27</div></div><div><span><span>B3</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>__init__.py</code>文件内置变量<code>__all__</code></p><p>在这个文件内同样有<code>__all__</code> 变量，可以控制<code>from 包 import *</code> 语句中，哪些模块被自动导入。</p><p>示例：</p><p>包<code>my_package</code> 内有 3个模块<code>A.py</code>  <code>B.py</code>  <code>C.py</code> 以及<code>__init__.py</code>文件</p><p>在<code>__init__.py</code> 文件内是空的，则</p><ul>
<li><code>from my_package import *</code>   这个写法，无法使用A、B、C任何的模块</li>
</ul><p>在<code>__init__.py</code> 文件内写上：<code>__all__ = ["A", "B"]</code> 则</p><ul>
<li><code>from my_package import *</code> 这个写法，可以使用A和B2个模块，无法用C这个模块</li>
</ul><section><h4>安装第三方包<a href="#安装第三方包"><span>#</span></a></h4><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pip install 包名称</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果网络不好可以用国内网址，如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>pip install </span><span>-</span><span>i https:</span><span>//</span><span>pypi.tuna.tsinghua.edu.cn</span><span>/</span><span>simple 包名称</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在PyCharm内，通过鼠标点击也可以安装：</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724145440.png" alt="image-20250724145439924" /><figcaption>image-20250724145439924</figcaption></figure><p></p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724145505.png" alt="image-20250724145505005" /><figcaption>image-20250724145505005</figcaption></figure><p></p></section></section></section>
<section><h2>AI大模型开发-Linux重点笔记<a href="#ai大模型开发-linux重点笔记"><span>#</span></a></h2></section>
<section><h2>虚拟机安装<a href="#虚拟机安装"><span>#</span></a></h2><section><h3>VMware安装后的验证<a href="#vmware安装后的验证"><span>#</span></a></h3><p>快捷键：<code>win + r</code> 打开运行窗口</p><p>输入：<code>ncpa.cpl</code>回车，可以看到自己电脑的网络适配器</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/23/20250723181815.png" alt="image-20250723181815442" /><figcaption>image-20250723181815442</figcaption></figure><p></p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/23/20250723181843.png" alt="image-20250723181843181" /><figcaption>image-20250723181843181</figcaption></figure><p></p><blockquote><p>当安装完成后，确认有<code>VMnet1</code> 好<code>VMnet8</code> 2个虚拟网卡</p></blockquote></section><section><h3>Ubuntu系统安装流程<a href="#ubuntu系统安装流程"><span>#</span></a></h3><p>确认有系统安装光盘，在给同学们的资料中：<code>资料/虚拟机/ubuntu-20.04.6-desktop-amd64.iso</code> 这个就是操作系统安装光盘。</p><p>后续流程如下截图过程</p><ol>
<li></li>
</ol><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724115704.png" alt="image-20250724115703577" /><figcaption>image-20250724115703577</figcaption></figure><p></p><ol>
<li></li>
</ol><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724115734.png" alt="image-20250724115734028" /><figcaption>image-20250724115734028</figcaption></figure><p></p><ol>
<li>
<p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724115818.png" alt="image-20250724115817905" /><figcaption>image-20250724115817905</figcaption></figure><p></p>
</li>
<li>
<p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724115849.png" alt="image-20250724115848650" /><figcaption>image-20250724115848650</figcaption></figure><p></p>
</li>
<li>
<p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724115941.png" alt="image-20250724115941127" /><figcaption>image-20250724115941127</figcaption></figure><p></p>
</li>
<li>
<p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724120009.png" alt="image-20250724120008760" /><figcaption>image-20250724120008760</figcaption></figure><p></p>
</li>
<li>
<p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724120025.png" alt="image-20250724120024674" /><figcaption>image-20250724120024674</figcaption></figure><p></p>
</li>
<li>
<p>进入安装流程</p>
<p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724120037.png" alt="image-20250724120036926" /><figcaption>image-20250724120036926</figcaption></figure><p></p>
<p>安装过程全自动化，无需人工干预，等待即可。</p>
</li>
</ol></section><section><h3>安装后打快照<a href="#安装后打快照"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724120622.png" alt="image-20250724120621985" /><figcaption>image-20250724120621985</figcaption></figure><p></p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724120641.png" alt="image-20250724120640936" /><figcaption>image-20250724120640936</figcaption></figure><p></p><p>如图，就做了快照备份，后续有问题可以随时回退</p><blockquote><p>建议每隔一段时间就打个快照</p></blockquote></section><section><h3>检查是否联网<a href="#检查是否联网"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724120930.png" alt="image-20250724120930396" /><figcaption>image-20250724120930396</figcaption></figure><p></p><p>如上图在虚拟机的桌面，右键点击<code>Open in Terminal</code></p><p>然后输入命令：<code>ip addr</code> 可以看到虚拟机的IP地址，如下图</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724121033.png" alt="image-20250724121033724" /><figcaption>image-20250724121033724</figcaption></figure><p></p><p>或者执行命令：<code>ping baidu.com</code></p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724121132.png" alt="image-20250724121132082" /><figcaption>image-20250724121132082</figcaption></figure><p></p><p>能看到和百度网址的延迟毫秒信息即可</p></section><section><h3>问题<a href="#问题"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724120808.png" alt="image-20250724120808481" /><figcaption>image-20250724120808481</figcaption></figure><p></p><p>启动虚拟机，遇到这个，直接点击否即可。</p></section></section>
<section><h2>基础命令<a href="#基础命令"><span>#</span></a></h2><section><h3>命令基础格式<a href="#命令基础格式"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/24/20250724181243.png" alt="image-20250724181242949" /><figcaption>image-20250724181242949</figcaption></figure><p></p><ul>
<li>command 命令本体，必写</li>
<li>options可选选项，控制命令的细节</li>
<li>parameter，可选参数，控制命令的指向目标</li>
</ul></section><section><h3>ls命令<a href="#ls命令"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>ls [</span><span>-</span><span>a </span><span>-</span><span>l </span><span>-</span><span>h] [参数]</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h4>参数的作用<a href="#参数的作用"><span>#</span></a></h4><p>决定ls命令要查看的目标</p><p><code>ls /</code> 查看根目录内容</p><p><code>ls /usr</code> 查看<code>/usr</code>目录内容</p></section><section><h4>-a选项<a href="#-a选项"><span>#</span></a></h4><p>查看全部文件，包括隐藏文件，都显示</p><p>在Linux中以<code>.</code>开头的文件是隐藏文件和隐藏文件夹</p></section><section><h4>-l选项<a href="#-l选项"><span>#</span></a></h4><p>以列表形式查看内容</p><p>不用<code>-l</code> 以平铺形式查看</p></section><section><h4>-h选项<a href="#-h选项"><span>#</span></a></h4><p>以更容易理解的方式显示文件大小（以K M G为单位）</p></section><section><h4>HOME目录<a href="#home目录"><span>#</span></a></h4><p>在Linux系统中每一个用户都有的专属文件夹，有全部操作权限</p><p>默认路径是：<code>/home/用户名</code></p><p>比如用户名：caoyu则路径是：<code>/home/caoyu</code></p></section><section><h4>当前工作目录<a href="#当前工作目录"><span>#</span></a></h4><p>终端软件当前所在的目录，可以通过<code>pwd</code> 命令查看</p></section><section><h4>隐藏文件<a href="#隐藏文件"><span>#</span></a></h4><p>以<code>.</code>开头的文件和文件夹是默认隐藏的需要用<code>ls -a</code> 的模式查看</p></section></section><section><h3>cd命令<a href="#cd命令"><span>#</span></a></h3><p>更改工作目录</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cd 路径</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>绝对、相对路径都可以</li>
</ul></section><section><h3>pwd命令<a href="#pwd命令"><span>#</span></a></h3><p>查看当前的工作目录</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>pwd</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>没有选项、没有参数</p></section><section><h3>相对、绝对路径<a href="#相对绝对路径"><span>#</span></a></h3><ul>
<li><code>相对</code>：以当前所在文件夹为路径起点</li>
<li>绝对：以根目录所在文件夹为路径起点</li>
</ul></section><section><h3>路径符号<a href="#路径符号"><span>#</span></a></h3><ul>
<li><code>.</code> ，当前目录</li>
<li><code>..</code> ，上一<code>级</code>目录</li>
<li><code>~</code> ，用户的家目录</li>
<li><code>-</code> ，上一<code>次</code>的目录</li>
</ul></section><section><h3>mkdir命令<a href="#mkdir命令"><span>#</span></a></h3><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>mkdir [</span><span>-</span><span>p] 路径</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>功能：创建指定文件夹</p><ul>
<li><code>-p</code> 连续创建多层级的文件夹，如mkdir -p 111/222/333</li>
</ul></section><section><h3>touch<a href="#touch"><span>#</span></a></h3><p>功能：创建空文件</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>touch 文件路径</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>cat<a href="#cat"><span>#</span></a></h3><p>功能：查看文件内容</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>cat 文件路径</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>more<a href="#more"><span>#</span></a></h3><p>功能：查看文件内容</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>more 文件路径</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>可以翻页查看，按空格翻页，按<code>q</code> 退出查看</li>
</ul></section><section><h3>cp<a href="#cp"><span>#</span></a></h3><p>功能：复制文件和文件夹</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>cp </span><span>-</span><span>r 参数1 参数2</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>-r</code> 选项用于文件夹复制</li>
<li>参数1 被复制的</li>
<li>参数2 要复制去的地方</li>
</ul></section><section><h3>mv<a href="#mv"><span>#</span></a></h3><p>功能：移动文件和文件夹（Windows中的剪切）</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mv 参数1 参数2</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>参数1 被移动的</li>
<li>参数2 要移动去的地方 如果不存在则改名</li>
<li>不需要<code>-r</code> 选项</li>
</ul></section><section><h3>rm<a href="#rm"><span>#</span></a></h3><p>功能：删除文件和文件夹</p><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>rm [</span><span>-</span><span>r </span><span>-</span><span>f] 参数1 参数2 </span><span>...</span><span> 参数N</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>参数，被删除的文件或文件夹</li>
<li><code>-r</code> 删除文件夹用</li>
<li><code>-f</code> 强制删除（不提示，直接删除）</li>
</ul><p>可以写<code> rm -rf *.txt</code>   <code>*</code> 表示通配符，表示 删除全部以<code>.txt</code>结尾的</p><blockquote><p>注意，不要以root用户执行：rm -rf /*</p></blockquote></section><section><h3>which<a href="#which"><span>#</span></a></h3><p>功能：查看在Linux系统中所有可执行的命令的文件本地所在。</p><blockquote><p>在Linux系统中执行的命令，本身是一个程序，即一个程序文件。</p></blockquote><p>语法</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>which</span><span> </span><span>命令</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>find<a href="#find"><span>#</span></a></h3><p>功能：查找符合条件的文件所在</p><section><h4>按文件名<a href="#按文件名"><span>#</span></a></h4><p>语法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>find</span><span> </span><span>起始路径</span><span> </span><span>-name</span><span> </span><span>"被查找的文件名"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>起始路径如果是<code>/</code> 表示全盘搜索。</p><p>这样需要管理权限，即root权限。</p><p>可以通过执行：</p><ul>
<li><code>sudo su -</code> 切换到root管理员用户去执行，</li>
<li>或</li>
<li><code>sudo find ...</code> 在find命令前加入sudo临时获得管理员权限</li>
</ul></blockquote><p>被查找的文件名，支持通配符，如：</p><ul>
<li><code>find / -name "test*"</code> 是在全盘搜索，以<code>test</code>开头的文件</li>
<li><code>find / -name "*test"</code> 搜索以<code>test</code> 结尾的文件</li>
<li><code>find / -name "*test*"</code> 搜索包含<code>test</code> 的文件</li>
</ul><p>即，<code> * == 任何</code>  的结果都是<code>True</code></p></section><section><h4>按文件大小<a href="#按文件大小"><span>#</span></a></h4><p>语法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>find</span><span> </span><span>起始路径</span><span> </span><span>-size</span><span> </span><span>+</span><span>|</span><span>-n(k</span><span>|</span><span>M</span><span>|</span><span>G</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>+</code> 表示大于，<code>-</code> 表示小于</li>
<li><code>n</code>表示数字</li>
<li><code>k M G</code> 表示大小单位，k KB  M MB  G GB</li>
</ul><p>示例</p><ul>
<li><code>+10k</code> 大于10KB</li>
<li><code>-10M</code> 小于10MB</li>
<li><code>+1G</code> 大于1G</li>
</ul></section></section><section><h3>grep<a href="#grep"><span>#</span></a></h3><p>功能：在指定的内容（输入）中过滤包含关键字的行（仅保留包含关键字的行）</p><p>语法：<code>grep [-n] "关键字" 输入内容</code></p><ul>
<li><code>-n</code> 显示过滤后的内容，在原始输入内容中的行号</li>
</ul><p>示例：</p><ul>
<li><code>grep hello a.txt</code>  在a.txt文件中，过滤hello关键字（仅保留包含hello关键字的行）</li>
<li><code>grep -n hello a.txt</code>  在a.txt文件中，过滤hello关键字（仅保留包含hello关键字的行），并显示行号</li>
</ul><blockquote><p>一般搭配管道符使用，如：<code> ls /usr/bin | grep gst</code> 查看/usr/bin下，带有gst关键字的内容</p></blockquote></section><section><h3>wc<a href="#wc"><span>#</span></a></h3><p>功能：用来统计输入中的如：字节数量、字符数量、单词数量、行数量</p><p>语法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>wc</span><span> [-c </span><span>-m</span><span> </span><span>-l</span><span> </span><span>-w</span><span>] 输入内容</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>-c</code> 统计字节</li>
<li><code>m</code> 统计字符</li>
<li><code>-l</code> 统计行</li>
<li><code>-w</code> 统计单词数</li>
</ul><p>如果不提供选项，则wc本身统计：字节、单词、行数</p><blockquote><p>一般搭配管道符使用</p></blockquote></section><section><h3>管道符<a href="#管道符"><span>#</span></a></h3><p>功能：将管道符的左侧的结果，作为管道符右侧程序的输入。管道符本身是：<code>|</code></p><p>示例</p><ul>
<li><code>ls -l /usr/bin | wc -l</code> 将<code>ls -l /usr/bin</code>的结果作为wc命令的输入，含义就是：
<ul>
<li>统计/usr/bin文件夹下的内容数量</li>
</ul>
</li>
</ul><p>管道符可以写多个，如下：</p><ul>
<li><code>ls -l /usr/bin | grep gst | wc -l</code>
<ul>
<li>将<code>ls -l /usr/bin</code>的结果作为<code>grep gst</code> 的输入</li>
<li>将<code>ls -l /usr/bin | grep gst</code>的结果作为wc -l的输入</li>
<li>含义：统计<code>/usr/bin</code> 下带有gst关键字的内容的数量</li>
</ul>
</li>
</ul></section><section><h3>echo<a href="#echo"><span>#</span></a></h3><p>功能：和Python的print一样，将内容输出到屏幕中。</p><p>语法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>内容</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>反引号 `<a href="#反引号-"><span>#</span></a></h3><p>在Linux中，被`包围的内容，将作为命令去执行。</p><p>如：</p><ul>
<li>
<p><code>echo pwd</code>  将输出pwd字样</p>
</li>
<li>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>`</span><span>pwd</span><span>`</span><span>   </span><span># 将输出pwd运行的结果，即当前所在工作目录</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section><section><h3>tail<a href="#tail"><span>#</span></a></h3><p>功能：查看文件尾部或跟踪文件更改</p><p>语法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tail</span><span> [-f | </span><span>-num]</span><span> </span><span>文件路径</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>-f</code> 表示持续跟踪文件更改，如需退出按<code>ctrl + c</code>退出</li>
<li><code>-num</code> 表示查看文件尾部多少行，默认是<code>-10</code></li>
</ul></section><section><h3>head<a href="#head"><span>#</span></a></h3><p>功能：查看文件头部内容</p><p>语法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>head</span><span> [-num] 文件路径</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>-num</code>  表示查看头部多少行，默认是<code>-10</code></li>
</ul></section><section><h3>重定向符号<a href="#重定向符号"><span>#</span></a></h3><p>含义：将输出的内容重新定向到文件中记录
Linux中有2个：</p><ul>
<li><code>&gt;</code> 将符号左侧内容的结果，重新定向即<code>覆盖</code>写入右侧的文件中</li>
<li><code>&gt;&gt;</code> 将符号左侧内容的结果，重新定向即<code>追加</code>写入右侧的文件中</li>
</ul><blockquote><p>如果右侧提供的文件不存在，则新建</p></blockquote></section><section><h3>vi/vim编辑器<a href="#vivim编辑器"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726120123.png" alt="image-20250726120123230" /><figcaption>image-20250726120123230</figcaption></figure><p></p><p>VI编辑器是命令行下面的<code>类似文本编辑器</code>的程序，可以在命令行下完成文件的编辑。</p><p>有3个工作模式：</p><ol>
<li>命令模式，可以输入各类快捷指令，如删除行、跳转光标等等</li>
<li>输入模式，可以正常输入你想要的内容，即开始编辑文件</li>
<li>底线命令模式，可以完成对整个文件的控制，如保持、退出等</li>
</ol><p>语法：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>vi 文件</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即可进入vi编辑器界面</p><section><h4>命令模式<a href="#命令模式"><span>#</span></a></h4><p>当进入vi编辑器，就是命令模式，此模式下可以通过快捷键完成对文件内容的控制，如下：</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726120344.png" alt="image-20250726120344057" /><figcaption>image-20250726120344057</figcaption></figure><p></p></section><section><h4>输入模式<a href="#输入模式"><span>#</span></a></h4><p>输入模式可以在这个模式下正常编写内容</p><p>如果要进入输入模式，必须在命令模式下，通过快捷键进入：</p><ol>
<li><code>i</code> 在当前光标位置，开始编辑</li>
<li><code>o</code> 在光标下一行开启新行进行编辑</li>
<li><code>O</code> 在光标上一行开启新行进行编辑</li>
<li><code>a</code>在光标右侧开始编辑</li>
<li><code>A</code> 在光标所在行的尾部开始编辑</li>
</ol><blockquote><p>一般就用：<code>i</code> 立刻编辑  或 <code>o</code> 下一行开始编辑</p></blockquote><p>输入模式要回退到命令模式，按<code>esc</code> 即可</p></section><section><h4>底线命令模式<a href="#底线命令模式"><span>#</span></a></h4><p>可以对整个文件做控制。</p><p>进入方式为：在命令模式下，输入<code>:</code> 进入底线命令模式。</p><p>常见的控制指令有：</p><ul>
<li><code>w</code> 保存更改</li>
<li><code>q</code> 退出vi程序</li>
<li><code>!</code> 强制（强制退出、强制保存）</li>
<li><code>set nu</code> 显示文件行号</li>
</ul><p>组合使用：</p><ul>
<li><code>wq</code> 保存并退出</li>
<li><code>q!</code> 忽略更改强制退出</li>
<li><code>wq!</code> 强制保存并退出</li>
</ul></section></section><section><h3>apt<a href="#apt"><span>#</span></a></h3><p>Linux系统的应用商店，在命令行模式下是：<code>apt</code> 命令</p><p>通过这个命令可以联网安装软件</p><p>语法：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>apt</span><span> </span><span>install</span><span> | </span><span>remove</span><span>  </span><span>程序名</span><span> [-y]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>必须联网</p></blockquote><ul>
<li><code>install</code> 安装程序</li>
<li><code>remove</code> 删除程序</li>
<li><code>-y</code> 可选，表示不要提示直接执行</li>
</ul><blockquote><p>此命令需要root权限，普通用户无权执行。</p></blockquote><p>解决方式：</p><ol>
<li>通过<code>sudo su -</code> 切换到root用户执行</li>
<li>通过<code> sudo apt install xxx</code> 以root权限执行这个命令
<ol>
<li>sudo 表示不切换用户，但是此命令以root身份执行</li>
</ol>
</li>
</ol><section><h4>更换apt的网址为国内网站<a href="#更换apt的网址为国内网站"><span>#</span></a></h4><p>专业说法：更换APT程序的源</p><p>国内有很多公司提供了镜像网站，使用和官方的完全一致，但是网络条件好非常多，常见有：阿里云源、163源、清华源等</p><p>步骤：</p><ol>
<li>打开清华源官网：<code>https://mirrors.tuna.tsinghua.edu.cn/help/ubuntu</code></li>
<li>选择Ubuntu版本，我们是<code>20.04</code> ,复制下图红框中的文本内容</li>
<li><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726150202.png" alt="image-20250726150202034" /><figcaption>image-20250726150202034</figcaption></figure></li>
<li>备份原有文件，命令是：<code> sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak</code></li>
<li>编辑文件：<code>sudo vim /etc/apt/sources.list</code>  将原有内容清空，粘贴刚刚在网站复制的文本，保存</li>
<li>执行<code>sudo apt update</code> 即可</li>
<li>后续的<code>apt</code> 命令将从清华大学网站下载内容</li>
</ol></section></section><section><h3>后台运行程序<a href="#后台运行程序"><span>#</span></a></h3><p>命令：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nohup</span><span> </span><span>你要执行的命令</span><span> &gt;&gt; </span><span>日志记录文件</span><span> 2&gt;&amp;1 &amp;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>比如启动ollama命令：<code>ollama serve</code>  这个会前台运行（终端关闭或ctrl + c关闭后，程序会停止）</p><p>我们需要转入后台运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nohup</span><span> </span><span>ollama</span><span> </span><span>serve</span><span> &gt;&gt; </span><span>ollama.log</span><span> 2&gt;&amp;1 &amp;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><code>nohup</code> 表示进入后台执行，linux自带程序</li>
<li><code>&gt;&gt;</code> 将程序运行的日志记录到文件内</li>
<li><code>2&gt;&amp;1 &amp;</code>  将标准输出和标准错误都记录到日志文件内并后台执行</li>
</ul></section><section><h3>WSL 安装<a href="#wsl-安装"><span>#</span></a></h3><p>首先先在Windows系统中完成了<code>WSL</code> 功能的开启</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726154701.png" alt="image-20250726154700832" /><figcaption>image-20250726154700832</figcaption></figure><p></p><blockquote><p>如果这个功能无法开启，请尝试更新Windows系统到最新</p></blockquote><p>通过应用商店图形化安装即可，注意如果网络条件不好，可以用热点，大概消耗500MB流量。</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726160114.png" alt="image-20250726160114403" /><figcaption>image-20250726160114403</figcaption></figure><p></p><p>打开提示这个，需要开启虚拟机平台特性</p><p>需要确认：</p><ol>
<li>
<p>应用和功能中勾选虚拟机平台（默认会自动勾选）</p>
<p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726160311.png" alt="image-20250726160311578" /><figcaption>image-20250726160311578</figcaption></figure><p></p>
<p>某些系统显示是<code>虚拟机平台</code></p>
</li>
<li>
<p>确认开启电脑CPU的虚拟化支持（这个正常也是开启的，因为你能用VMware玩虚拟机，这个功能100%是启用的）</p>
</li>
</ol><p>首次打开需要等待一会，如下图</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726160925.png" alt="image-20250726160838753" /><figcaption>image-20250726160838753</figcaption></figure><p></p><p>之后可以设置用户名</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726160928.png" alt="image-20250726160903956" /><figcaption>image-20250726160903956</figcaption></figure><p></p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726161000.png" alt="image-20250726160959857" /><figcaption>image-20250726160959857</figcaption></figure><p></p><p>自行设置密码，完成后即可使用。</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726161015.png" alt="image-20250726161015213" /><figcaption>image-20250726161015213</figcaption></figure><p></p><p>如图一切完事。</p><p>通过命令提示符安装WSL Ubuntu发行版</p><ol>
<li>打开命令提示符程序，执行<code>wsl --update</code>   即更新到最新版</li>
<li>执行命令查看有那些Linux可以装：<code>wsl --list --online</code></li>
<li>执行命令安装：<code>wsl --install Ubuntu-20.04</code></li>
</ol></section></section>
<section><h2>AI大模型开发-聊天机器人重点笔记<a href="#ai大模型开发-聊天机器人重点笔记"><span>#</span></a></h2></section>
<section><h2>Ollama 的部署<a href="#ollama-的部署"><span>#</span></a></h2><section><h3>Ollama介绍<a href="#ollama介绍"><span>#</span></a></h3><p>Ollama是一个开源软件，主要作用就是：部署大语言模型。</p><p>我们课上主要运行的模型就是：<code>deepseek-r1:1.5b</code></p></section><section><h3>Windows系统<a href="#windows系统"><span>#</span></a></h3><p>找到资料中提供的：<code>Ollama-setup.exe</code> 双击打开后，点击<code>Install</code> 即可。</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726180025.png" alt="image-20250726180025521" /><figcaption>image-20250726180025521</figcaption></figure><p></p><p>安装好后找到<code>Ollama</code> 软件运行，正常会在右下角出现图表，点击图表选择<code>Settings</code></p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/26/20250726180103.png" alt="image-20250726180103518" /><figcaption>image-20250726180103518</figcaption></figure><p></p><p>更改模型的安装目录即可。</p></section><section><h3>MacOS系统<a href="#macos系统"><span>#</span></a></h3><p>略</p></section><section><h3>Linux系统<a href="#linux系统"><span>#</span></a></h3><p>只需要在命令行下执行一条命令，如下：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-fsSL</span><span> </span><span>https://ollama.com/install.sh</span><span> | </span><span>sh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即可自动化完成安装。</p><section><h4>开启系统代理<a href="#开启系统代理"><span>#</span></a></h4><p>使用自己的：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>http_proxy</span><span>=</span><span>"http://ip地址:端口"</span></div></div><div><div><div>2</div></div><div><span>export</span><span> </span><span>https_proxy</span><span>=</span><span>"https://ip地址:端口"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>使用老师的：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>http_proxy</span><span>=</span><span>"http://192.168.40.85:7890"</span></div></div><div><div><div>2</div></div><div><span>export</span><span> </span><span>https_proxy</span><span>=</span><span>"https://192.168.40.85:7890"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>手动安装Ollama<a href="#手动安装ollama"><span>#</span></a></h4><ol>
<li>下载安装包：<code>ollama-linux-amd64.tgz</code> 已经在课堂同步的资料文件夹中有</li>
<li>打开WSL Ubuntu，通过<code>cd /mnt</code> 找到你的Windows系统中存放这个安装包的地方</li>
<li>找到安装包后执行：<code>cp ollama-linux-amd64.tgz ~</code> 将这个安装包从Windows系统复制到Linux系统的用户家目录</li>
<li>回到家目录：<code>cd ~</code></li>
<li>解压Ollama：<code>sudo tar -xvf ollama-linux-amd64.tgz -C /usr</code>    （注意-C是大写的C）</li>
<li>执行：<code>ollama serve</code> 启动
<ol>
<li>或后台启动：<code>nohup ollama serve &gt;&gt; ollama.log 2&gt;&amp;1 &amp;</code></li>
</ol>
</li>
</ol><p>后续就可以正常用了。</p></section></section><section><h3>运行模型<a href="#运行模型"><span>#</span></a></h3><p>无论什么操作系统，都可以在终端中运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>run</span><span> </span><span>deepseek-r1:1.5b</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>即可运行<code>1.5b</code>参数的deepseek-r1蒸馏模型。</p><p>首次运行需要联网下载，大概1.1GB空间。</p></section><section><h3>蒸馏模型<a href="#蒸馏模型"><span>#</span></a></h3><p>我们运行的模式都是蒸馏模型，即非完整模型。</p><p>蒸馏：按照需求进行模型提纯（保留核心网络并进行简化）</p><p>我们选择的<code>1.5b</code>参数就是蒸馏后的（提纯和简化），为了方便低性能设备的运行。</p><p>完整的DeepSeek模型，简单来说显卡不花个<code>20W</code>基本没啥体验。</p><blockquote><p>模型运行是基于GPU（显卡），因为GPU特点：核多（并行计算能力超强）</p><ul>
<li>CPU：核心少，比如<code>i7-13650HX</code> 仅仅有20个核心，这些核心每一个都超强，CPU适合通用计算</li>
<li>GPU（显卡）：核心超多，动辄上千的流处理器（核心），每个核心都垃圾（对比CPU），GPU适合做并行计算</li>
</ul><p>因为模型的神经网络计算，一般来说是量大，而非计算难度高。</p><p>量大的计算，用显卡超爽（核多）</p></blockquote></section></section>
<section><h2>Ollama客户端命令<a href="#ollama客户端命令"><span>#</span></a></h2><p>当安装好Ollama后就可以在命令行中运行ollama命令。</p><section><h3>查看帮助<a href="#查看帮助"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>--help</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以给出命令执行的帮助信息</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728095604.png" alt="image-20250728095604660" /><figcaption>image-20250728095604660</figcaption></figure><p></p></section><section><h3>启动Ollama<a href="#启动ollama"><span>#</span></a></h3><p>前台启动</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>serve</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>后台启动（推荐）</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>nohup</span><span> </span><span>ollama</span><span> </span><span>serve</span><span> &gt;&gt; </span><span>ollama.log</span><span> 2&gt;&amp;1 &amp;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>其它控制命令<a href="#其它控制命令"><span>#</span></a></h3><p>运行模型：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>run</span><span> </span><span>模型名称</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>ollama</span><span> </span><span>run</span><span> </span><span>deepseek-r1:7b</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>查看本地有那些模型</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>list</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>查看当前正在运行的模型</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>ps</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728095738.png" alt="image-20250728095738280" /><figcaption>image-20250728095738280</figcaption></figure><p></p><p>查看模型的详细信息</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>show</span><span> </span><span>模型名称</span></div></div></code></pre><div><div></div><div></div></div></figure></div><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728095810.png" alt="image-20250728095810102" /><p>复制一个模型</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>cp</span><span> </span><span>模型名称</span><span> </span><span>新模型名称</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728095947.png" alt="image-20250728095947681" /><figcaption>image-20250728095947681</figcaption></figure><p></p><p>删除模型（慎用）</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>rm</span><span> </span><span>模型名称</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728100016.png" alt="image-20250728100016432" /><figcaption>image-20250728100016432</figcaption></figure><p></p><p>下载模型</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ollama</span><span> </span><span>pull</span><span> </span><span>模型名称</span></div></div></code></pre><div><div></div><div></div></div></figure></div><blockquote><p>模型名称可以在Ollama官网查询你想要的</p></blockquote><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728100151.png" alt="image-20250728100151509" /><figcaption>image-20250728100151509</figcaption></figure><p></p><p>此命令，仅下载模型，并不会运行。</p></section></section>
<section><h2>ChatBoxAI工具连接Ollama<a href="#chatboxai工具连接ollama"><span>#</span></a></h2><p>我们可以安装ChatboxAI 软件在自己电脑中提供一个对话工具，和我们电脑的Ollama进行连接。</p><p>安装：略（找到安装包，一路下一步即可）</p><section><h3>前置要求<a href="#前置要求"><span>#</span></a></h3><p>使用ChatBoxAI软件之前，<code>必须启动</code>你电脑的Ollama</p><ul>
<li>启动你Windows版的Ollama
<ul>
<li>Windows的Ollama默认无法调用显卡的CUDA计算加速</li>
</ul>
</li>
<li>或</li>
<li><code>WSL Linux版的Ollama都可以用</code>  （推荐使用WSL）
<ul>
<li>Linux下CUDA加速性能更好</li>
</ul>
</li>
</ul></section><section><h3>配置<a href="#配置"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728101339.png" alt="image-20250728101339469" /><figcaption>image-20250728101339469</figcaption></figure><p></p></section><section><h3>使用<a href="#使用"><span>#</span></a></h3><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728101402.png" alt="image-20250728101401778" /><figcaption>image-20250728101401778</figcaption></figure><p></p></section></section>
<section><h2>Streamlit<a href="#streamlit"><span>#</span></a></h2><p>Streamlit是一个Python的第三方库，可以通过很少的代码，开发功能丰富的网页应用。</p><section><h3>安装<a href="#安装"><span>#</span></a></h3><p>打开Windows系统的<code>cmd</code> ，运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>streamlit</span><span> </span><span>-i</span><span> </span><span>https://pypi.tuna.tsinghua.edu.cn/simple</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>测试Streamlit<a href="#测试streamlit"><span>#</span></a></h3><p>打开Windows系统的<code>cmd</code> ，运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>streamlit</span><span> </span><span>hello</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>会自动打开一个网页，可以看到丰富的官方<code>demo</code> 效果。</p><p>看完了就可以关了，没啥用。</p></section><section><h3>运行Python代码<a href="#运行python代码"><span>#</span></a></h3><p>使用streamlit编写的Python代码无法直接在PyCharm中执行，需要用cmd执行。</p><p>打开系统的命令提示符（CMD）程序，执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>streamlit</span><span> </span><span>run</span><span> </span><span>python代码</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>session_state<a href="#session_state"><span>#</span></a></h3><p>Streamlit工具编写的网页会定期刷新，等同于每隔一段时间，重新执行整个Python代码。</p><p>比如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> streamlit </span><span>as</span><span> st</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>st</span><span>...</span><span>..</span></span></div></div><div><div><div>4</div></div><div><span><span>st</span><span>...</span><span>..</span></span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span><span>d </span><span>=</span><span> {}</span></span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>d[key] </span><span>=</span><span> xxx  </span><span># 每次循环的Key 假设永不重复</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我们代码写的无限循环，不停的给字典添加信息，但是这个字典能够记录的信息永远只有1个key</p><p>因为这段代码被Streamlit执行起来，相当于每隔一段时间都定期执行。</p><p>则每一次执行字典<code>d</code> 都会被赋值为空字典。原有记录信息会丢失。</p><p>如果想要在网页运行的过程中存储下来信息，可以用Streamlit内置的字典，这个字典将不会丢失内容。</p><p>即将代码改为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> streamlit </span><span>as</span><span> st</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>st</span><span>...</span><span>..</span></span></div></div><div><div><div>4</div></div><div><span><span>st</span><span>...</span><span>..</span></span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>st.session_state[key] </span><span>=</span><span> xxx  </span><span># 每次循环的Key 假设永不重复</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>st.session_state</code> 就是一个字典，我们用这个字典，记录的内容就不会丢失。</p></section></section>
<section><h2>Python调用Ollama<a href="#python调用ollama"><span>#</span></a></h2><p>Python有一个第三方库叫做：<code>ollama</code></p><p>要注意我们现在电脑有2个Ollama：</p><ul>
<li>WSL Linux中运行的Ollama：<code>软件程序</code></li>
<li>Python的第三方库Ollama</li>
</ul><section><h3>安装Ollama Python库<a href="#安装ollama-python库"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>ollama</span><span> </span><span>-i</span><span> </span><span>https://pypi.tuna.tsinghua.edu.cn/simple</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>使用Python调用Ollama<a href="#使用python调用ollama"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"""</span></div></div><div><div><div>2</div></div><div><span>演示使用Python调用Ollama进行模型对话</span></div></div><div><div><div>3</div></div><div><span>先确保Ollama已经启动</span></div></div><div><div><div>4</div></div><div><span>"""</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>import</span><span> ollama</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 建立和Ollama的连接（代码和Ollama程序）</span></div></div><div><div><div>9</div></div><div><span><span>client </span><span>=</span><span> ollama.</span><span>Client</span><span>(</span></span><span>host</span><span>=</span><span>"http://127.0.0.1:11434"</span><span>)</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 准备用户的提问</span></div></div><div><div><div>12</div></div><div><span><span>prompt </span><span>=</span><span> </span></span><span>input</span><span>(</span><span>"请输入你的问题："</span><span>)</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 封装要提问的格式</span></div></div><div><div><div>15</div></div><div><span># [{"role":"user", "content": "提问的内容"}]</span></div></div><div><div><div>16</div></div><div><span><span>message </span><span>=</span><span> [{</span></span><span>"role"</span><span>: </span><span>"user"</span><span>, </span><span>"content"</span><span>: prompt}]</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span># 调用ollama进行回答</span></div></div><div><div><div>20</div></div><div><span><span>result </span><span>=</span><span> client.</span><span>chat</span><span>(</span></span></div></div><div><div><div>21</div></div><div><span>    </span><span>model</span><span>=</span><span>"deepseek-r1:7b"</span><span>,     </span><span># 模型名</span></div></div><div><div><div>22</div></div><div><span>    </span><span>messages</span><span><span>=</span><span>message            </span></span><span># 封装好的列表（内含提问信息）</span></div></div><div><div><div>23</div></div><div><span>)</span></div></div><div><div><div>24</div></div><div>
</div></div><div><div><div>25</div></div><div><span># 只输出回答信息</span></div></div><div><div><div>26</div></div><div><span>print</span><span>(result[</span><span>'message'</span><span>][</span><span>'content'</span><span>])</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h2>在云平台使用模型<a href="#在云平台使用模型"><span>#</span></a></h2><section><h3>获取云平台API KEY<a href="#获取云平台api-key"><span>#</span></a></h3><p>需要在阿里云百炼平台注册账户，创建应用，创建API KEY</p><p><a href="https://bailian.console.aliyun.com" target="_blank">https://bailian.console.aliyun.com</a></p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728180326.png" alt="image-20250728180325906" /><figcaption>image-20250728180325906</figcaption></figure><p></p><p>如图点击新增应用，选择智能体应用。</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728180408.png" alt="image-20250728180408444" /><figcaption>image-20250728180408444</figcaption></figure><p></p><p>获取API KEY</p><p></p><figure><img src="https://image-set.oss-cn-zhangjiakou.aliyuncs.com/img-out/2025/07/28/20250728180438.png" alt="image-20250728180438383" /><figcaption>image-20250728180438383</figcaption></figure><p></p></section><section><h3>通过LangChain完成Python代码连接阿里云通义模型<a href="#通过langchain完成python代码连接阿里云通义模型"><span>#</span></a></h3><p>pip安装库</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>pip install langchain </span><span>-</span><span>i https:</span><span>//</span><span>pypi.tuna.tsinghua.edu.cn</span><span>/</span><span>simple</span></span></div></div><div><div><div>2</div></div><div><span><span>pip install langchain</span><span>-</span><span>community </span><span>-</span><span>i https:</span><span>//</span><span>pypi.tuna.tsinghua.edu.cn</span><span>/</span><span>simple</span></span></div></div><div><div><div>3</div></div><div><span><span>pip install dashscope </span><span>-</span><span>i https:</span><span>//</span><span>pypi.tuna.tsinghua.edu.cn</span><span>/</span><span>simple</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>示例</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> langchain.chains </span><span>import</span><span> ConversationChain</span></div></div><div><div><div>2</div></div><div><span>from</span><span> langchain.memory </span><span>import</span><span> ConversationBufferMemory</span></div></div><div><div><div>3</div></div><div><span>from</span><span> langchain_community.llms </span><span>import</span><span> Tongyi</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span><span>memory </span><span>=</span><span> </span><span>ConversationBufferMemory</span><span>(</span></span><span>return_message</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>def</span><span> </span><span>get_response</span><span>(</span><span>prompt</span><span>,</span><span><span> </span><span>api_key</span></span><span>):</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>model </span><span>=</span><span> </span><span>Tongyi</span><span>(</span><span>model</span><span>=</span><span>"qwen-max"</span><span>, </span><span>api_key</span><span><span>=</span><span>api_key)</span></span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>chain </span><span>=</span><span> </span><span>ConversationChain</span><span>(</span><span>llm</span><span><span>=</span><span>model, </span></span><span>memory</span><span><span>=</span><span>memory)</span></span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>    </span><span># 发送请求</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>response </span><span>=</span><span> chain.</span><span>invoke</span><span>({</span><span>"input"</span><span>: prompt})</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span>return</span><span> response[</span><span>"response"</span><span>]</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>'__main__'</span><span>:</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>    </span><span>print</span><span><span>(</span><span>get_response</span><span>(</span></span><span>"请python 1-100的输出"</span><span>, </span><span>"sk-fa5b080ac78b4323b13f733b50f1d5c0"</span><span>))</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><blockquote><p>抄一遍即可，无需理解代码含义，因为有很多基础知识还未学，目前仅仅体验一下。</p></blockquote></section><section><h3>Streamlit结合LangChain完成聊天机器人<a href="#streamlit结合langchain完成聊天机器人"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"""</span></div></div><div><div><div>2</div></div><div><span>通过Streamlit完成聊天机器人页面开发</span></div></div><div><div><div>3</div></div><div><span>"""</span></div></div><div><div><div>4</div></div><div><span>import</span><span> streamlit </span><span>as</span><span> st</span></div></div><div><div><div>5</div></div><div><span>import</span><span> langchain_util </span><span>as</span><span> utils</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 添加标题</span></div></div><div><div><div>8</div></div><div><span><span>st.</span><span>title</span><span>(</span></span><span>"黑马聊天机器人（阿里云通义千问模型版）"</span><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 添加分割线</span></div></div><div><div><div>11</div></div><div><span><span>st.</span><span>divider</span><span>()</span></span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 输出第一条消息 机器人欢迎语</span></div></div><div><div><div>15</div></div><div><span>if</span><span> </span><span>"message"</span><span> </span><span>not</span><span> </span><span>in</span><span> st.session_state:</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>st.session_state[</span><span>"message"</span><span><span>] </span><span>=</span><span> [{</span></span><span>"role"</span><span>: </span><span>"assistant"</span><span>, </span><span>"content"</span><span>: </span><span>"你好我是人工智能机器人，有什么可以帮到您!"</span><span>}]</span></div></div><div><div><div>17</div></div><div><span>    </span><span># st.session_state["message"].append({"role": "assistant", "content": "你好我是人工智能机器人，有什么可以帮到您!"})</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span># list.append({"role": "user", "content": "内容"})</span></div></div><div><div><div>20</div></div><div><span># state["message"]  =   [ {"role": "user", "content": "内容"} , {"role": "user", "content": "内容"}]</span></div></div><div><div><div>21</div></div><div><span># 每一个消息的对话分为2部分： 角色，内容</span></div></div><div><div><div>22</div></div><div><span># 机器人  角色：assistant   内容：说的话</span></div></div><div><div><div>23</div></div><div><span># 人     角色：user         内容：说的话</span></div></div><div><div><div>24</div></div><div><span>for</span><span> message </span><span>in</span><span> st.session_state[</span><span>"message"</span><span>]:</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>st.</span><span>chat_message</span><span>(message[</span><span>"role"</span><span><span>]).</span><span>write</span><span>(message[</span></span><span>"content"</span><span>])</span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span># 用户输入 在页面下方添加用户输入栏</span></div></div><div><div><div>28</div></div><div><span><span>prompt </span><span>=</span><span> st.</span><span>chat_input</span><span>()</span></span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>if</span><span> prompt:          </span><span># 如果prompt有内容 表示用户提问</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span># 首先将用户的提问在页面输出</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>st.</span><span>chat_message</span><span>(</span><span>"user"</span><span><span>).</span><span>write</span><span>(prompt)</span></span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>    </span><span># 把用户提问这个对话保存到session_state内</span></div></div><div><div><div>36</div></div><div><span><span>    </span></span><span>st.session_state[</span><span>"message"</span><span><span>].</span><span>append</span><span>({</span></span><span>"role"</span><span>: </span><span>"user"</span><span>, </span><span>"content"</span><span>: prompt})</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>    </span><span># 调用AI回答</span></div></div><div><div><div>39</div></div><div><span>    </span><span>with</span><span><span> st.</span><span>spinner</span><span>(</span></span><span>"正在思考中..."</span><span>):        </span><span># 转圈圈的加载框</span></div></div><div><div><div>40</div></div><div><span><span>        </span></span><span>res </span><span>=</span><span> utils.</span><span>get_response</span><span>(prompt, </span><span>"sk-fa5b080ac78b4323b13f733b50f1d5c0"</span><span>)</span></div></div><div><div><div>41</div></div><div>
</div></div><div><div><div>42</div></div><div><span>    </span><span># 将AI回答信息写到屏幕上</span></div></div><div><div><div>43</div></div><div><span><span>    </span></span><span>st.</span><span>chat_message</span><span>(</span><span>"assistant"</span><span><span>).</span><span>write</span><span>(res)</span></span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span>    </span><span># 将AI回答的消息记录到session_state内</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>st.session_state[</span><span>"message"</span><span><span>].</span><span>append</span><span>({</span></span><span>"role"</span><span>: </span><span>"assistant"</span><span>, </span><span>"content"</span><span>: res})</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><blockquote><p>抄一遍即可</p></blockquote></section></section>
<section><h2>课后作业<a href="#课后作业"><span>#</span></a></h2></section>
<section><h2>Day01<a href="#day01"><span>#</span></a></h2><p>字面量</p><ol>
<li>
<p>写出一个整数字面量 <code>123</code>。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>写出一个浮点数字面量 <code>3.14</code>。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>写出一个字符串字面量 <code>"Hello, World!"</code>。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>（可预习或询问AI，Python布尔类型相关）写出一个布尔字面量 <code>True</code>。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>（可预习或询问AI，Python布尔类型相关）写出一个布尔字面量 <code>False</code>。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>注释
6. 使用单行注释在代码中添加说明：</p><div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>使用多行注释在代码中添加说明：</li>
</ol><div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>
<p>写一段代码，计算两个数的和，并在代码中添加注释说明计算过程。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>写一段代码，使用注释说明变量的作用。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>写一段代码，使用多行注释说明代码文件的功能。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>变量
11. 定义一个变量 <code>x</code> 并赋值为 <code>10</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>12. 定义一个变量 <code>name</code> 并赋值为 <code>"Alice"</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>13. 定义一个变量 <code>is_valid</code> 并赋值为 <code>True</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>14. 定义一个变量 <code>price</code> 并赋值为 <code>99.99</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>15. 将变量 <code>x</code> 的值更新为 <code>20</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>类型转换（<code>int</code>、<code>float</code>、<code>str</code> 三个类型之间）
16. 将字符串 <code>"123"</code> 转换为整数。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>17. 将整数 <code>456</code> 转换为浮点数。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>18. 将浮点数 <code>78.9</code> 转换为字符串。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>19. 将字符串 <code>"3.14"</code> 转换为浮点数。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>20. 将浮点数 <code>100.0</code> 转换为整数。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>运算符
21. 计算两个数的和：<code>a = 5 + 3</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>22. 计算两个数的差：<code>b = 10 - 2</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>23. 计算两个数的乘积：<code>c = 4 * 6</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>24. 计算两个数的商：<code>d = 20 / 5</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>25. 计算两个数的整除结果：<code>e = 21 // 4</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>26. 计算两个数的余数：<code>f = 21 % 4</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>27. 使用<code>+=</code> 计算变量a*3的结果、使用<code>*=</code> 计算a的3次方</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>```python</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>```</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>预习</code>字符串格式化完成</p><ol>
<li>
<p>2种方法拼接字符串：<code>name = "Alice"</code>，<code>age = 25</code>，输出 <code>"Alice is 25 years old."</code>。</p>
<ol>
<li>子需求：将25转换为25.0 （基于类型转换实现）然后在拼接到字符内</li>
</ol>
</li>
<li>
<p>格式化字符串，保留两位小数：<code>price = 99.9891</code>，输出 <code>"The price is $99.99."</code>。</p>
<ol>
<li>子需求，将价格格式化为<code>100</code></li>
</ol>
</li>
<li>
<p>预习后续内容</p>
</li>
</ol></section>
<section><h2>Day02 and 自习日<a href="#day02-and-自习日"><span>#</span></a></h2><ol>
<li>
<p>1个字符串变量，1个浮点数变量，要求使用三种方式将其拼接在一起（+号拼接、%拼接、f""方式）</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>将浮点数12.356 通过精度控制，输出为：<code>12.4</code></p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>将整数 12 和浮点数 12.35   对齐输出</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>通过input语句输入一个浮点数，并将其输出为：整体宽度10，小数精度1</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>三个变量name、age、height，通过<code>input语句</code>输入姓名、年龄、身高并：</p>
<ol>
<li>通过<code>%占位符</code>形式，拼接为：我是xxx、年龄xxx、身高xxx cm</li>
<li>通过<code>f"{}"</code>形式，拼接为：我是xxx、年龄xxx、身高xxx cm</li>
<li>通过<code>+加法符号</code>的形式，拼接为：我是xxx、年龄xxx、身高xxx cm</li>
</ol>
</li>
<li>
<p>通过input语句输入数字，要求浮点数，范围<code>0~1</code>之间，如0.8567</p>
<ol>
<li>
<p>使用”%占位符”形式，输出为<code>85.67%</code></p>
<p>提示：如果需要在字符串中输出<code>%</code> 本身，可以写：<code>%%</code></p>
</li>
<li>
<p>使用f”{}“形式，输出为<code>85.7%</code></p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol>
</li>
<li>
<p>使用  %  占位符和  f-string  格式化字符串，将变量  temperature(温度)  插入到句子中，并显示为两位小数。</p>
<ol>
<li>
<p>变量要求input输入</p>
</li>
<li>
<p>要求输出示例为：<code>当前温度：26.56℃</code></p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol>
</li>
<li>
<p>使用  %  占位符和  f-string  格式化字符串，将变量  price  和  discount  插入到句子中，并计算折扣后的价格。</p>
<ol>
<li>示例输入：price = 100.0  discount = 0.2  ，要求input输入</li>
<li>示例输出：<code>Original Price: $100.00, Discount: 20.00%, Final Price: $80.00</code></li>
</ol>
</li>
</ol><p>if判断</p><ol>
<li>
<p>编写代码，判断一个数是否为正数，如果是正数则打印“正数”，否则打印“非正数”。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>输入一个年份，判断是否为闰年（能被4整除且不能被100整除，或者能被400整除）。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>输入一个字符，判断是否为大写字母，如果是则打印“大写字母”，否则打印“不是大写字母”。</p>
</li>
<li>
<p>输入两个数，判断第一个数是否大于第二个数，如果是则打印“第一个数更大”，否则打印“第一个数不大于第二个数”。</p>
</li>
<li>
<p>输入一个成绩（0-100），判断成绩等级：90以上为A，80-89为B，70-79为C，60-69为D，60以下为F。</p>
</li>
<li>
<p>输入一个字符串，判断是否为空字符串，如果是则打印“字符串为空”，否则打印“字符串不为空”。</p>
</li>
<li>
<p>输入一个数字，判断是否为偶数，如果是则打印“偶数”，否则打印“奇数”。</p>
</li>
<li>
<p>输入一个数字，判断是否在1到10之间（包括1和10），如果是则打印“在范围内”，否则打印“不在范围内”。</p>
</li>
<li>
<p>输入一个月份（1-12），判断该月有多少天（考虑平年和闰年）。</p>
</li>
<li>
<p>输入一个用户密码，判断密码长度是否大于8位，如果是则打印“密码强度高”，否则打印“密码强度低”。</p>
</li>
</ol><p>if嵌套判断</p><ol>
<li>
<p>输入一个数字，判断是否为正数，如果是正数，再判断是否大于10，如果是则打印“大于10的正数”，否则打印“小于等于10的正数”；如果不是正数，打印“非正数”。</p>
</li>
<li>
<p>输入一个成绩（0-100），判断成绩等级：如果成绩大于等于90，打印“A”；如果成绩在70到89之间，打印“B”；如果成绩在60到69之间，打印“C”；如果成绩小于60，打印“D”。如果输入的成绩不在0到100之间，打印“无效成绩”。</p>
</li>
<li>
<p>输入一个年份和一个月份，判断该月有多少天（考虑平年和闰年）。</p>
</li>
<li>
<p>输入一个字符，判断是否为字母，如果是字母，再判断是否为大写字母，如果是大写字母，打印“大写字母”，如果是小写字母，打印“小写字母”；如果不是字母，打印“非字母字符”。</p>
</li>
<li>
<p>输入三个数字，判断第一个数字是否大于第二个数字，如果是，再判断第一个数字是否大于第三个数字，如果是，打印“第一个数字最大”，否则打印“第三个数字最大”；如果第一个数字不大于第二个数字，判断第二个数字是否大于第三个数字，如果是，打印“第二个数字最大”，否则打印“第三个数字最大”。</p>
</li>
<li>
<p>输入一个数字，判断是否为正数，如果是正数，再判断是否为偶数，如果是偶数，打印“正偶数”，否则打印“正奇数”；如果不是正数，判断是否为负数，如果是负数，再判断是否为偶数，如果是偶数，打印“负偶数”，否则打印“负奇数”；如果既不是正数也不是负数，打印“零”。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>输入一个用户年龄和是否购买会员（布尔值），判断用户是否可以享受折扣：如果年龄大于等于60岁或者购买了会员，打印“可以享受折扣”；否则，打印“不可以享受折扣”。</p>
</li>
<li>
<p>输入一个数字，判断是否在1到10之间（包括1和10），如果是，再判断是否为偶数，如果是偶数，打印“在范围内且为偶数”，否则打印“在范围内且为奇数”；如果不在范围内，打印“不在范围内”。</p>
</li>
<li>
<p>输入一个用户输入的密码和确认密码，判断密码是否符合要求（长度大于8位）并且两次输入是否一致：如果密码长度大于8位且两次输入一致，打印“密码设置成功”；如果密码长度不足8位，打印“密码长度不足”；如果两次输入不一致，打印“两次输入不一致”。</p>
</li>
<li>
<p>输入一个用户输入的用户名和密码，判断用户名是否为“admin”且密码是否为“123456”，如果是，打印“登录成功”；如果不是，判断用户名是否为“admin”，如果是，打印“密码错误”；如果不是，打印“用户名错误”。</p>
</li>
<li>
<p>将课堂中的猜数字游戏，改为<code>给与5次机会</code>，通过if判断嵌套完成</p>
</li>
<li>
<p>将课堂中的猜数字游戏，换一种写法（以尽量少嵌套为主）</p>
</li>
<li>
<p>将课堂中的猜数字游戏，用while循环完成无限次数的猜测，随机数字范围1-100</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>预习（不强制要求写完）循环语句，尝试完成：</p><p>6 个 <code>for</code> 循环练习题</p><ol>
<li>使用 <code>for</code> 循环打印出从 1 到 10 的所有整数。</li>
<li>使用 <code>for</code> 循环计算从 1 到 100 的所有整数的和。</li>
<li>使用 <code>for</code> 循环打印出从 1 到 50 的所有偶数。</li>
<li>使用 <code>for</code> 循环打印出从 1 到 30 的所有奇数。</li>
<li>使用 <code>for</code> 循环遍历一个列表 <code>[10, 20, 30, 40, 50]</code>，并打印出每个元素的平方。</li>
<li>使用<code>for</code>  循环完成猜数字游戏，给10次猜测机会，数字范围<code>1 ~ 100</code></li>
</ol><p>6 个 <code>while</code> 循环练习题</p><ol>
<li>使用 <code>while</code> 循环打印出从 1 到 10 的所有整数。</li>
<li>使用 <code>while</code> 循环计算从 1 到 100 的所有整数的和。</li>
<li>使用 <code>while</code> 循环打印出从 1 到 50 的所有偶数。</li>
<li>使用 <code>while</code> 循环打印出从 1 到 30 的所有奇数。</li>
<li>使用 <code>while</code> 循环遍历一个列表 <code>[10, 20, 30, 40, 50]</code>，并打印出每个元素的平方。</li>
<li>使用<code>while</code>  循环完成猜数字游戏，给10次猜测机会，数字范围<code>1 ~ 100</code></li>
</ol><p>预习（不强制要求写完）函数，尝试完成</p><p>6个函数练习题</p><ol>
<li>编写一个函数 <code>add_numbers</code>，接收两个参数 <code>a</code> 和 <code>b</code>，返回它们的和。</li>
<li>编写一个函数 <code>is_even</code>，接收一个整数参数 <code>num</code>，如果该数是偶数则返回 <code>True</code>，否则返回 <code>False</code>。</li>
<li>编写一个函数 <code>greet</code>，接收一个字符串参数 <code>name</code>，返回一个欢迎消息，例如 <code>"Hello, Alice!"</code>。</li>
<li>编写一个函数 <code>factorial</code>，接收一个整数参数 <code>n</code>，返回该数的阶乘（<code>n!</code>）。</li>
<li>编写一个函数 <code>max_of_three</code>，接收三个整数参数 <code>a</code>、<code>b</code> 和 <code>c</code>，返回其中的最大值。</li>
<li>尝试完成课件中的：黑马ATM案例</li>
</ol></section>
<section><h2>Day03<a href="#day03"><span>#</span></a></h2><ol>
<li>
<p>使用for循环打印1-100之间的奇数</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>使用while循环打印1-100之间的偶数</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>使用for循环生成如下图形</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>使用while循环生成如上图形</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>找出所有三位数中的水仙花数。水仙花数是一个三位数，它的每一位数字的立方和等于它本身。例如，153是一个水仙花数，因为 1^3 + 5^3 + 3^3 = 153。使用for或while解决</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>使用 for 循环或 while 循环计算1到100的总和，并打印结果。</p>
</li>
<li>
<p>使用while或for循环生成如下图形：</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>进阶，使用input输入任意<code>奇数</code>，然后输出对应的图形，如：</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>将上一题的需求封装为函数，每次调用函数传入一个奇数数值，然后函数会打印出来对应的图形。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>实现一个函数，接收3个数字的传入，通过print语句输出最大的那个数字</p>
</li>
<li>
<p>实现一个函数，接收3个数字的传入，通过return返回最小的那个数字</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>总结目前的知识点以思维导图的形式</p>
</li>
</ol><blockquote><p>要求，所有函数给出规范注释</p></blockquote><p>练习：</p><ol>
<li>创建一个列表存储5个学生姓名
<ol>
<li>通过print语句打印第三个学生的姓名（正向和反向下标都用一次）</li>
</ol>
</li>
<li>创建一个双层嵌套列表（二维矩阵），取出任意一个元素</li>
</ol></section>
<section><h2>Day04<a href="#day04"><span>#</span></a></h2><p>列表（List）</p><ol>
<li>
<p>创建一个包含5个整数的列表，并打印出来。</p>
</li>
<li>
<p>计算列表的长度。</p>
</li>
<li>
<p>从列表中提取第2到第4个元素。</p>
</li>
<li>
<p>向列表中追加一个元素。</p>
</li>
<li>
<p>对列表进行排序。</p>
</li>
<li>
<p>删除列表中的第一个元素。</p>
</li>
<li>
<p>合并两个列表。</p>
</li>
<li>
<p>找出列表中的最大值和最小值。</p>
</li>
<li>
<p>反转列表的顺序。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>元组（Tuple）
11. 创建一个包含5个整数的元组，并打印出来。</p><ol>
<li>
<p>计算元组的长度。</p>
</li>
<li>
<p>从元组中提取第2到第4个元素。</p>
</li>
<li>
<p>尝试修改元组中的一个元素（观察是否报错）。</p>
</li>
<li>
<p>合并两个元组。</p>
</li>
<li>
<p>找出元组中的最大值和最小值。</p>
</li>
<li>
<p>创建一个嵌套元组并访问其中的元素。</p>
</li>
<li>
<p>使用<code>count()</code>方法统计元组中某个元素的出现次数。</p>
</li>
<li>
<p>使用<code>index()</code>方法查找元组中某个元素的索引。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>集合（Set）
21. 创建一个包含5个整数的集合，并打印出来。</p><ol>
<li>
<p>计算集合的长度。</p>
</li>
<li>
<p>向集合中添加一个元素。</p>
</li>
<li>
<p>从集合中移除一个元素。</p>
</li>
<li>
<p>合并两个集合。</p>
</li>
<li>
<p>找出两个集合的交集。</p>
</li>
<li>
<p>找出两个集合的并集。</p>
</li>
<li>
<p>找出两个集合的差集。</p>
</li>
<li>
<p>检查一个元素是否在集合中。</p>
</li>
<li>
<p>清空集合。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>字符串（String）
31. 创建一个字符串并打印出来。</p><ol>
<li>
<p>计算字符串的长度。</p>
</li>
<li>
<p>将字符串转换为大写。</p>
</li>
<li>
<p>将字符串转换为小写。</p>
</li>
<li>
<p>检查字符串是否以某个字符开头。</p>
</li>
<li>
<p>检查字符串是否以某个字符结尾。</p>
</li>
<li>
<p>替换字符串中的某个字符。</p>
</li>
<li>
<p>分割字符串为列表。</p>
</li>
<li>
<p>检查字符串是否只包含字母。</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ol><p>字典（Dictionary）
41. 创建一个包含3个键值对的字典，并打印出来。
42. 计算字典的长度。
43. 访问字典中的某个键对应的值。
44. 向字典中添加一个键值对。
45. 从字典中移除一个键值对。
46. 遍历字典的所有键。
47. 遍历字典的所有值。
48. 遍历字典的所有键值对。</p><div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div><p>列表（List）</p><ol>
<li>
<p>列表元素查找</p>
<p>编写一个程序，使用<code>max()</code>函数找出列表中的最大值。</p>
</li>
<li>
<p>列表元素移除</p>
<p>编写一个程序，使用<code>list()</code>函数将元组转换为列表，并使用<code>remove()</code>方法移除列表中的特定元素。</p>
</li>
<li>
<p>列表长度计算</p>
<p>编写一个程序，使用<code>len()</code>函数计算列表中元素的数量。</p>
</li>
</ol><p>元组（Tuple）
4. 元组元素访问</p><p>编写一个程序，使用索引访问元组中的元素，并使用<code>min()</code>函数找出元组中的最小值。</p><ol>
<li>
<p>元组元素转换</p>
<p>编写一个程序，将列表转换为元组，并使用<code>str()</code>函数将元组转换为字符串。</p>
</li>
</ol><p>集合（Set）
6. 集合元素去重</p><p>编写一个程序，使用<code>set()</code>函数将列表转换为集合，去除列表中的重复元素。</p><ol>
<li>
<p>集合元素排序</p>
<p>编写一个程序，使用<code>sorted()</code>函数对集合中的元素进行排序，并设置<code>reverse=True</code>以逆序排列。</p>
</li>
</ol><p>字符串（String）
8. 字符串长度计算</p><p>编写一个程序，使用<code>len()</code>函数计算字符串的长度。</p><ol>
<li>
<p>字符串转换</p>
<p>编写一个程序，使用<code>str()</code>函数将数字转换为字符串，并使用<code>sorted()</code>函数对字符串中的字符进行排序。</p>
</li>
</ol><p>字典（Dictionary）
10. 字典元素访问</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>编写一个程序，创建一个字典，并使用`for`循环遍历字典中的所有键值对。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>编写一个函数（名字func），接收2个数字和一个函数
<ol>
<li>要求用接收的函数，对接收的2个数字 进行计算，计算逻辑任意  要求使用lambda</li>
</ol>
</li>
</ol></section>
<section><h2>Day05<a href="#day05"><span>#</span></a></h2><ol>
<li>
<p>编写一个程序，可以生成10000条数据到<code>data.txt</code> 文件中，数据格式要求：</p>
<ol>
<li>
<p>循环10000次，每次写入1条数据</p>
</li>
<li>
<p>每一条数据都是随机生成的零散数据拼接成如下的格式</p>
</li>
</ol>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
<p>要求：</p>
<ul>
<li>
<p>id不可重复</p>
</li>
<li>
<p>姓名随机从一个姓名list中随机抽取，可以重复</p>
<div><figure><figcaption></figcaption><pre><code></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>年龄随机10~35的范围</p>
</li>
<li>
<p>身高随机160~190 CM的范围</p>
</li>
<li>
<p>体重随机70~120  KG的范围</p>
</li>
<li>
<p>性别，随机男或女</p>
</li>
<li>
<p>城市随机，可以重复</p>
</li>
</ul>
</li>
<li>
<p>读取<code>data.txt</code> 统计如下需求：</p>
<ol>
<li>年龄3个档，每个档的人数
<ol>
<li>10~18岁档</li>
<li>18~26档</li>
<li>26~35档</li>
</ol>
</li>
<li>身高大于等于170和小于170各自的人数</li>
<li>计算体重BMI（自行问AI）指数超过28和小于等于28的人数</li>
<li>提示，这些统计都需要用字典（key就是分组，value是数据）</li>
</ol>
</li>
<li>
<p>编写Python目前学到的全部语法的思维导图</p>
</li>
</ol></section>
<section><h2>Day06 （Linux）<a href="#day06-linux"><span>#</span></a></h2><ol>
<li>
<p>请说出 <code>/</code> 在Linux系统中作为路径符的两种作用</p>
<ol>
<li>根目录（root），在Linux有有且仅有一个根目录，称之为<code>/</code></li>
<li>路径层次分隔符，如<code>abc/edf</code> 含义：abc文件夹中有edf</li>
</ol>
</li>
<li>
<p>请写出路径：在根目录下有home文件夹，home文件夹内有itheima文件夹，itheima文件夹里面有test文件夹，test文件夹内有hello.txt文件，请写出hello.txt文件的绝对路径</p>
<ol>
<li><code>/home/itheima/test/hello.txt</code></li>
<li>如果当前工作目录在<code>/home</code> ，请基于此，写出hello.txt文件的相对路径
<ol>
<li><code>itheima/test/hello.txt</code></li>
<li><code>./itheima/test/hello.txt</code></li>
</ol>
</li>
</ol>
</li>
<li>
<p>请写出如下符号在路径中的作用：</p>
<ol>
<li><code>~</code>：当前登陆用户的HOME目录</li>
<li><code>.</code> 当前所在目录</li>
<li><code>..</code> 上一<code>级</code>目录 （向上）</li>
<li><code>-</code> 上一<code>次</code>的目录（回退）</li>
</ol>
</li>
<li>
<p>请根据描述写出命令：</p>
<ol>
<li>
<p>以平铺形式，查看根目录有啥</p>
<p><code>ls /</code></p>
</li>
<li>
<p>以列表形式，并查看隐藏内容，看看用户的HOME目录有什么（要求必须使用<code>~</code>符号）</p>
<p><code>ls -la ~</code></p>
</li>
<li>
<p>以列表形式，并查看隐藏内容，并以更友好的形式展示文件大小，查看根目录内有什么</p>
<p><code>ls -lah /</code></p>
</li>
</ol>
</li>
<li>
<p>查看当前工作目录的命令是：<code>pwd</code></p>
</li>
<li>
<p><code>cd -</code> 代表含义是：返回上一次去过的文件夹</p>
</li>
<li>
<p>相对路径是以<code>当前所在工作目录</code>为基准的路径表达形式（填空）</p>
</li>
<li>
<p>绝对路径是以<code>根目录</code> 为基准的路径表达形式（填空）</p>
<ol>
<li>
<p>请举例说明绝对路径和相对路径的区别（不可以用课上说的 中国-&gt;广东-&gt;深圳 这个案例）</p>
<p>绝对：我要喝  饮料-&gt;碳酸饮料-&gt;可口可乐</p>
<p>相对：我要喝 可口可乐</p>
</li>
</ol>
</li>
<li>
<p>切换工作目录到，上一级目录内的test文件夹，请写出命令：</p>
<p><code>cd ../test</code></p>
</li>
<li>
<p>切换工作目录到上一级的上一级的test文件夹，请写出命令：</p>
<p><code>cd ../../test</code></p>
</li>
<li>
<p>切换到当前目录内的Desktop文件夹，请写出命令（必须使用<code>.</code> 符号）：</p>
<p><code>cd ./Desktop</code></p>
</li>
<li>
<p>假设当前目录在<code>/usr/local</code> ，请在用户家目录创建文件夹<code>666</code> ，要求必须使用<code>..</code> 符号</p>
<p><code>mkdir ../../home/用户名/666</code></p>
</li>
<li>
<p>请在用户的家目录创建：<code>111/222/333</code> 连续层级目录</p>
<p><code>mkdir -p ~/111/222/333</code></p>
</li>
<li>
<p>在用户的家目录创建1.txt文件</p>
<p><code>touch ~/1.txt</code></p>
</li>
<li>
<p>通过图形化，修改1.txt，随意填入内容</p>
</li>
<li>
<p>通过cat命令和more命令查看1.txt的内容</p>
<ol>
<li><code>cat ~/1.txt</code></li>
<li><code>more /home/用户名/1.txt</code></li>
</ol>
</li>
</ol><p>预习：</p><ol>
<li>cp、mv、rm</li>
<li>which、find</li>
<li>管道符</li>
<li>重定向符</li>
<li>vi编辑器部分</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/devops/cicd%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E4%B8%8E%E9%83%A8%E7%BD%B2%E5%AE%9E%E6%88%98/</id>
      <title type="text">CI/CD 持续集成与部署实战</title>
      <published>2026-09-11T00:00:00.000Z</published>
      <updated>2026-09-11T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/devops/cicd%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E4%B8%8E%E9%83%A8%E7%BD%B2%E5%AE%9E%E6%88%98/"/>
      <summary type="text">CI/CD 流水线设计与实战，涵盖 GitHub Actions 核心语法、缓存与密钥管理、质量门禁、Docker 镜像构建推送、蓝绿与金丝雀发布策略</summary>
      <content type="html"><![CDATA[<p>本站就是一条 CI/CD 流水线的产物：本地 <code>git push origin master</code>，GitHub Actions 拉代码、装依赖、构建静态站点，再通过 SFTP 把产物推到自建服务器，全程不登服务器、不手敲部署命令。这篇文章把这条真实链路拆开讲透，再抽象成通用的 CI/CD 方法论。</p>
<section><h2>一、概念理清：CI、CD 与流水线<a href="#一概念理清cicd-与流水线"><span>#</span></a></h2><section><h3>1.1 三个概念的区别<a href="#11-三个概念的区别"><span>#</span></a></h3><p>三个词都带 CD，含义完全不同，是最容易答错的面试题之一。</p>

<table><thead><tr><th>概念</th><th>英文</th><th>核心动作</th><th>触发时机</th><th>产物去向</th><th>人工介入</th></tr></thead><tbody><tr><td>持续集成</td><td>Continuous Integration</td><td>频繁合并代码回主干，自动跑检查与测试</td><td>每次 push / PR</td><td>测试报告、构建产物</td><td>无</td></tr><tr><td>持续交付</td><td>Continuous Delivery</td><td>在 CI 之上自动部署到类生产环境并验证</td><td>每次合并通过</td><td>可随时发布的制品</td><td>上线前点一次批准</td></tr><tr><td>持续部署</td><td>Continuous Deployment</td><td>制品通过全部门禁后自动发布到生产</td><td>每次合并通过</td><td>生产环境</td><td>无</td></tr></tbody></table><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>开发提交 push</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>-&gt; CI：代码检查 -&gt; 单元测试 -&gt; 构建</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>-&gt; 产出制品（artifact / 镜像）</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>-&gt; 自动部署测试环境 -&gt; 自动化验收测试</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>持续交付：... -&gt; 等待人工批准 -&gt; 生产</span></div></div><div><div><div>7</div></div><div><span>持续部署：... -&gt; 门禁全绿 -&gt; 直接进生产</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>一句话概括：CI 关注“代码合进去能不能用”，持续交付关注“随时可发布”，持续部署关注“已经发出去了”。持续部署的前提是自动化测试足够可靠、监控和回滚足够快，否则就是裸奔。</p></section><section><h3>1.2 流水线解决什么问题<a href="#12-流水线解决什么问题"><span>#</span></a></h3><ul>
<li><strong>人为失误</strong>。手工部署是 SSH 登录、git pull、装依赖、构建、重启进程，中间任何一步打错字、走错目录、忘清缓存都会变成事故。人类在重复劳动面前的错误率是稳定的，交给机器即可。</li>
<li><strong>环境差异</strong>。“在我机器上是好的”本质是构建环境不可复现：Node 版本、系统库、本地全局包、<code>.env</code> 里别人没有的一行配置。流水线把构建固定在干净的、声明式的机器上。</li>
<li><strong>反馈太慢</strong>。一次提交等一整天才发现编译不过，修复成本指数上升。CI 把发现问题的时间从小时级压到分钟级。</li>
</ul><p>顺带解决的还有：交付过程可审计（谁、何时、把哪个 commit 发上了线）、部署可重复（同一个 commit 结果一致）、新人上手成本低（部署方式写在 YAML 里，而不是老员工脑子里）。</p></section></section>
<section><h2>二、一条健康流水线的组成部分<a href="#二一条健康流水线的组成部分"><span>#</span></a></h2><section><h3>2.1 阶段划分与耗时预算<a href="#21-阶段划分与耗时预算"><span>#</span></a></h3>

<table><thead><tr><th>阶段</th><th>目的</th><th>常见耗时</th><th>失败时的动作</th></tr></thead><tbody><tr><td>代码检查 lint / typecheck</td><td>拦住低级错误与风格分歧</td><td>10s ~ 2min</td><td>立即失败，不进入测试</td></tr><tr><td>单元测试</td><td>验证逻辑正确性，覆盖率兜底</td><td>30s ~ 5min</td><td>失败即停</td></tr><tr><td>构建 build</td><td>产出可部署制品</td><td>1min ~ 10min</td><td>失败即停</td></tr><tr><td>制品归档 artifact</td><td>让后续阶段与回滚有据可依</td><td>10s ~ 1min</td><td>极少失败</td></tr><tr><td>部署测试环境</td><td>验证能否在真实环境跑起来</td><td>30s ~ 5min</td><td>自动回滚到上一版</td></tr><tr><td>自动化测试 E2E / Smoke</td><td>端到端验证关键链路</td><td>2min ~ 20min</td><td>阻断生产部署</td></tr><tr><td>部署生产</td><td>发布</td><td>30s ~ 5min</td><td>触发回滚与告警</td></tr></tbody></table><p>理想状态下，从 push 到“知道有没有问题”应在 10 分钟以内。超过这个阈值，开发者就会切去做别的事，反馈循环断裂。</p></section><section><h3>2.2 设计原则<a href="#22-设计原则"><span>#</span></a></h3><ul>
<li><strong>快速反馈优先</strong>：最快、最容易失败的检查放最前。lint 挂了没必要跑 5 分钟单测。</li>
<li><strong>失败即停 fail fast</strong>：默认行为即如此；除非检查彼此独立且并行成本低，不要用 <code>continue-on-error</code> 吞掉失败。</li>
<li><strong>可重复 reproducible</strong>：同一 commit 任何时候跑出的制品应一致。锁文件必须提交，基础镜像用精确 tag 而不是 <code>latest</code>。</li>
<li><strong>幂等 idempotent</strong>：部署脚本跑一次和跑三次结果一样。“先删目录再建”这种半途失败会留下残骸的写法要避免。</li>
<li><strong>一切代码化 pipeline as code</strong>：流水线定义进仓库、跟着分支走，能 review、能回滚。网页上点出来的 Jenkins Job 是反面教材。</li>
<li><strong>最小权限</strong>：流水线默认只拿它能拿到的权限，多余的显式关掉。</li>
</ul></section></section>
<section><h2>三、GitHub Actions 核心概念<a href="#三github-actions-核心概念"><span>#</span></a></h2>

<table><thead><tr><th>概念</th><th>含义</th><th>类比</th></tr></thead><tbody><tr><td>workflow</td><td>一个 YAML 文件定义的自动化流程</td><td>一条流水线</td></tr><tr><td>job</td><td>workflow 内的一组 step，跑在同一个 runner 上</td><td>流水线里的一个阶段</td></tr><tr><td>step</td><td>job 内的最小执行单元，一条命令或一个 action</td><td>命令行的一行</td></tr><tr><td>action</td><td>可复用的封装单元，本地目录或远程仓库</td><td>一个函数库</td></tr><tr><td>runner</td><td>执行 job 的机器，GitHub 托管或自托管</td><td>一台构建机</td></tr></tbody></table><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>repo/</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>.github/workflows/</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>deploy.yml          # 主部署流水线</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>ci.yml              # PR 质量门禁</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>docker-publish.yml  # 镜像构建推送</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>每个 <code>.yml</code> 文件就是一个独立 workflow，彼此默认并行、互不阻塞。命名建议动词开头、语义化。多个 workflow 的协作有三种方式：靠触发条件天然分工（<code>ci.yml</code> 跑 <code>pull_request</code>，<code>deploy.yml</code> 只跑 <code>push: branches: [master]</code>）；用 <code>workflow_run</code> 在一个 workflow 完成后触发另一个；用 <code>workflow_call</code> 把公共逻辑抽成可复用 workflow。</p></section>
<section><h2>四、触发器详解<a href="#四触发器详解"><span>#</span></a></h2><section><h3>4.1 常用事件<a href="#41-常用事件"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>on</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>push</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>branches</span><span>: [</span><span>master</span><span>, </span><span>"release/**"</span><span>]</span></div></div><div><div><div>4</div></div><div><span>  </span><span>pull_request</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>branches</span><span>: [</span><span>master</span><span>]</span></div></div><div><div><div>6</div></div><div><span>    </span><span>types</span><span>: [</span><span>opened</span><span>, </span><span>synchronize</span><span>, </span><span>reopened</span><span>]</span></div></div><div><div><div>7</div></div><div><span>  </span><span>schedule</span><span>:</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>- </span><span>cron</span><span>: </span><span>"0 2 * * *"</span></div></div><div><div><div>9</div></div><div><span>  </span><span>workflow_dispatch</span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>inputs</span><span>:</span></div></div><div><div><div>11</div></div><div><span>      </span><span>environment</span><span>: { </span><span>type</span><span>: </span><span>choice</span><span>, </span><span>options</span><span>: [</span><span>staging</span><span>, </span><span>production</span><span>] }</span></div></div><div><div><div>12</div></div><div><span>  </span><span>release</span><span>:</span></div></div><div><div><div>13</div></div><div><span>    </span><span>types</span><span>: [</span><span>published</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>push</code> 最常用，往往配合 <code>branches</code> 限定主干或发布分支。<code>pull_request</code> 用于质量门禁，注意<strong>来自 fork 的 PR 拿不到仓库 secrets</strong>（这是安全设计，不是 bug）。<code>schedule</code> 用 cron 跑定时任务，比如每天凌晨的依赖漏洞扫描，<strong>GitHub 用的是 UTC</strong>，写 <code>0 2 * * *</code> 实际是北京时间上午 10 点。<code>workflow_dispatch</code> 提供手动按钮，适合“一键回滚”“手动发布生产”这类需要人决策的场景。<code>release</code> 与 <code>push: tags</code> 用于版本发布。</p></section><section><h3>4.2 分支与路径过滤<a href="#42-分支与路径过滤"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>on</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>push</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>branches-ignore</span><span>: [</span><span>"dependabot/**"</span><span>]</span></div></div><div><div><div>4</div></div><div><span>    </span><span>paths</span><span>: [</span><span>"src/**"</span><span>, </span><span>"package.json"</span><span>, </span><span>"pnpm-lock.yaml"</span><span>]</span></div></div><div><div><div>5</div></div><div><span>    </span><span>paths-ignore</span><span>: [</span><span>"**.md"</span><span>, </span><span>"docs/**"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>paths</code> 是白名单、<code>paths-ignore</code> 是黑名单，二者不能在同一事件下同时使用。一个坑：<strong>一次提交同时包含代码和文档改动时，<code>paths-ignore</code> 不会拦下这次运行</strong>——只要有任何一条改动命中 <code>paths</code> 就触发。路径过滤是省成本的第一手段，文档、图片的改动不该烧构建时长。</p></section><section><h3>4.3 并发控制与防重复触发<a href="#43-并发控制与防重复触发"><span>#</span></a></h3><p><code>concurrency.group</code> 是并发锁的键，写法是 <code>concurrency: { group: deploy-blog, cancel-in-progress: false }</code>。同一 group 内默认排队等待前一个结束；加上 <code>cancel-in-progress: true</code> 则直接取消正在跑的那个。</p><p>两种情况要分开处理：CI 类（lint / test）用 <code>cancel-in-progress: true</code>，连续 push 三次只跑最后一次，前两次已无人关心；<strong>部署类必须用 <code>false</code></strong>，部署跑到一半被取消很可能留下半新半旧的状态，同时用固定的 <code>group: deploy-production</code> 保证同一环境串行部署。其他防重复手段：<code>if: github.actor != 'dependabot[bot]'</code> 跳过机器人，<code>github.event.pull_request.draft == false</code> 跳过草稿 PR。</p></section></section>
<section><h2>五、Job 与 Step<a href="#五job-与-step"><span>#</span></a></h2><section><h3>5.1 runs-on 选型<a href="#51-runs-on-选型"><span>#</span></a></h3><ul>
<li><code>ubuntu-latest</code>：最便宜、启动最快（通常 10 秒内）、生态最全，绝大多数场景首选。</li>
<li><code>windows-latest</code>：构建 .NET、MSI、原生 Windows 应用时用，启动慢，计费是 Linux 的 2 倍。</li>
<li><code>macos-latest</code>：构建 iOS / macOS 必需，计费是 Linux 的 10 倍，能不用就不用。</li>
<li>自托管 runner：需要访问内网、需要更强硬件、或想省成本时用。代价是自己维护（系统更新、磁盘清理），以及最要命的一条：<strong>fork 来的 PR 能在你的机器上执行任意代码</strong>，除非配了环境与审批。</li>
</ul></section><section><h3>5.2 needs 依赖与并行执行<a href="#52-needs-依赖与并行执行"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>deploy</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>needs</span><span>: [</span><span>lint</span><span>, </span><span>test</span><span>]       </span><span># lint / test 两个 job 并行跑，都成功才轮到 deploy</span></div></div><div><div><div>4</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>5</div></div><div><span>    </span><span>steps</span><span>: [{ </span><span>run</span><span>: </span><span>echo deploy</span><span> }]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>没有 <code>needs</code> 关系的 job 默认并行；加上 <code>needs</code> 就形成 DAG，<code>deploy</code> 会等 <code>lint</code> 与 <code>test</code> 都成功才开始。也能用 <code>needs.lint.outputs.xxx</code> 在 job 间传数据。</p></section><section><h3>5.3 条件执行<a href="#53-条件执行"><span>#</span></a></h3><p>常用条件：<code>github.ref</code>、<code>github.event_name</code>、<code>success()</code> / <code>failure()</code> / <code>always()</code>、<code>needs.job.result</code>。<code>failure()</code> 常用来做“失败了才发通知”“失败了才上传日志”，形如 <code>if: failure()</code>；只在主干部署用 <code>if: github.ref == 'refs/heads/master' &amp;&amp; github.event_name == 'push'</code>。</p></section><section><h3>5.4 matrix 矩阵构建<a href="#54-matrix-矩阵构建"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>test</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>${{ matrix.os }}</span></div></div><div><div><div>4</div></div><div><span>    </span><span>strategy</span><span>:</span></div></div><div><div><div>5</div></div><div><span>      </span><span>fail-fast</span><span>: </span><span>false</span></div></div><div><div><div>6</div></div><div><span>      </span><span>matrix</span><span>:</span></div></div><div><div><div>7</div></div><div><span>        </span><span>os</span><span>: [</span><span>ubuntu-latest</span><span>, </span><span>windows-latest</span><span>]</span></div></div><div><div><div>8</div></div><div><span>        </span><span>node</span><span>: [</span><span>20</span><span>, </span><span>22</span><span>]</span></div></div><div><div><div>9</div></div><div><span>        </span><span>exclude</span><span>: [{ </span><span>os</span><span>: </span><span>windows-latest</span><span>, </span><span>node</span><span>: </span><span>20</span><span> }]</span></div></div><div><div><div>10</div></div><div><span>        </span><span>include</span><span>: [{ </span><span>os</span><span>: </span><span>ubuntu-latest</span><span>, </span><span>node</span><span>: </span><span>22</span><span>, </span><span>coverage</span><span>: </span><span>true</span><span> }]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>matrix</code> 把组合自动展开成多个并行 job，用一份 YAML 覆盖“多 Node 版本 x 多操作系统”。<code>exclude</code> 剔除不需要的组合，<code>include</code> 追加额外变量。<code>fail-fast</code> 默认 <code>true</code>（任一组合失败就取消其余），跑兼容性验证时设为 <code>false</code>，一次性看到全部平台的失败情况。</p></section></section>
<section><h2>六、常用官方 action 实战<a href="#六常用官方-action-实战"><span>#</span></a></h2><section><h3>6.1 checkout 与 setup-node<a href="#61-checkout-与-setup-node"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/checkout@v4</span></div></div><div><div><div>2</div></div><div><span>        </span><span>with</span><span>: { </span><span>fetch-depth</span><span>: </span><span>0</span><span> }        </span><span># 需要 git 历史（如生成 changelog）时设为 0</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/setup-node@v4</span></div></div><div><div><div>4</div></div><div><span>        </span><span>with</span><span>: { </span><span>node-version</span><span>: </span><span>22</span><span>, </span><span>cache</span><span>: </span><span>pnpm</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>checkout</code> 默认 <code>fetch-depth: 1</code> 浅克隆，速度快；需要 <code>git describe</code>、生成 changelog 或做差异分析时要显式设 <code>0</code>。<code>setup-node</code> 的 <code>cache</code> 支持 <code>npm</code> / <code>yarn</code> / <code>pnpm</code>，缓存包管理器全局 store，key 默认基于锁文件哈希，这一行通常能省掉 30% 到 70% 的安装时间。</p></section><section><h3>6.2 actions/cache 自定义缓存<a href="#62-actionscache-自定义缓存"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/cache@v4</span></div></div><div><div><div>2</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>3</div></div><div><span>          </span><span>path</span><span>: </span><span>|</span></div></div><div><div><div>4</div></div><div><span><span>            </span></span><span>node_modules/.vite</span></div></div><div><div><div>5</div></div><div><span><span>            </span></span><span>~/.local/share/pnpm/store</span></div></div><div><div><div>6</div></div><div><span>          </span><span>key</span><span>: </span><span>${{ runner.os }}-build-${{ hashFiles('pnpm-lock.yaml') }}-${{ github.sha }}</span></div></div><div><div><div>7</div></div><div><span>          </span><span>restore-keys</span><span>: </span><span>${{ runner.os }}-build-</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>缓存 key 的设计核心是<strong>分层降级</strong>：第一级带 <code>github.sha</code>，精确命中“这个 commit 曾构建过”（重跑场景）；第二级 <code>restore-keys</code> 去掉 sha，命中“同一锁文件的任意一次构建”；第三级只带操作系统兜底。提升命中率的关键：把稳定的因子放前面（<code>runner.os</code> -&gt; 语言版本 -&gt; 锁文件哈希 -&gt; sha）；锁文件是缓存的锚点，它一变依赖缓存必然失效，这是正确行为，不要绕过；缓存路径要精确，跨平台缓存整个 <code>node_modules</code> 经常因二进制平台相关而出错，更稳妥的是缓存包管理器 store 或构建工具自己的缓存目录。缓存有 10GB 上限，且 7 天不访问会被清理。</p></section><section><h3>6.3 artifact 上传与下载<a href="#63-artifact-上传与下载"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/upload-artifact@v4</span></div></div><div><div><div>2</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>3</div></div><div><span>          </span><span>name</span><span>: </span><span>dist-${{ github.sha }}</span></div></div><div><div><div>4</div></div><div><span>          </span><span>path</span><span>: </span><span>dist/</span></div></div><div><div><div>5</div></div><div><span>          </span><span>retention-days</span><span>: </span><span>30</span></div></div><div><div><div>6</div></div><div><span>      </span><span># 另一个 job 中</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/download-artifact@v4</span></div></div><div><div><div>8</div></div><div><span>        </span><span>with</span><span>: { </span><span>name</span><span>: </span><span>"dist-${{ github.sha }}"</span><span>, </span><span>path</span><span>: </span><span>dist</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>artifact 是<strong>跨 job 传递文件的唯一官方方式</strong>（同一 job 内的 step 共享文件系统，不需要它）。<code>retention-days</code> 可以用来保存“上一版制品”以实现回滚，是最省事的方案。</p></section><section><h3>6.4 发布与镜像构建<a href="#64-发布与镜像构建"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>softprops/action-gh-release@v2</span></div></div><div><div><div>2</div></div><div><span>        </span><span>if</span><span>: </span><span>startsWith(github.ref, 'refs/tags/v')</span></div></div><div><div><div>3</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>docker/build-push-action@v6</span></div></div><div><div><div>4</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>5</div></div><div><span>          </span><span>context</span><span>: </span><span>.</span></div></div><div><div><div>6</div></div><div><span>          </span><span>push</span><span>: </span><span>true</span></div></div><div><div><div>7</div></div><div><span>          </span><span>platforms</span><span>: </span><span>linux/amd64,linux/arm64</span></div></div><div><div><div>8</div></div><div><span>          </span><span>tags</span><span>: </span><span>${{ steps.meta.outputs.tags }}</span></div></div><div><div><div>9</div></div><div><span>          </span><span>cache-from</span><span>: </span><span>type=gha</span></div></div><div><div><div>10</div></div><div><span>          </span><span>cache-to</span><span>: </span><span>type=gha,mode=max</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>cache-from: type=gha</code> 用的是 Actions 自带的缓存后端，专为 Docker 层缓存设计，比手工管 <code>actions/cache</code> 更贴合。</p></section></section>
<section><h2>七、环境变量与密钥管理<a href="#七环境变量与密钥管理"><span>#</span></a></h2><section><h3>7.1 env、vars、secrets 的区别与优先级<a href="#71-envvarssecrets-的区别与优先级"><span>#</span></a></h3>

<table><thead><tr><th>类型</th><th>定义位置</th><th>是否加密</th><th>典型用途</th><th>能否出现在日志</th></tr></thead><tbody><tr><td><code>env</code></td><td>workflow / job / step 三级</td><td>否</td><td>普通配置、路径、版本号</td><td>可以</td></tr><tr><td><code>vars</code></td><td>仓库/组织/环境变量面板</td><td>否</td><td>非敏感部署参数（域名、区域）</td><td>可以</td></tr><tr><td><code>secrets</code></td><td>仓库/组织/环境变量面板</td><td>是</td><td>密码、token、私钥</td><td>会被自动打码</td></tr></tbody></table><p>作用域覆盖顺序由窄到宽生效：<strong>step 级 env &gt; job 级 env &gt; workflow 级 env</strong>。<code>secrets</code> 与 <code>vars</code> 需要用 <code>${{ }}</code> 表达式取值注入。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>env</span><span>: { </span><span>NODE_ENV</span><span>: </span><span>production</span><span> }      </span><span># workflow 级，被下面 job 级覆盖</span></div></div><div><div><div>2</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>deploy</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>env</span><span>: { </span><span>TARGET</span><span>: </span><span>production</span><span> }   </span><span># job 级，覆盖同名 workflow 变量</span></div></div><div><div><div>5</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>- </span><span>run</span><span>: </span><span>echo "部署到 $TARGET"</span></div></div><div><div><div>7</div></div><div><span>        </span><span>env</span><span>: { </span><span>DEPLOY_HOST</span><span>: </span><span>"${{ secrets.DEPLOY_HOST }}"</span><span> }   </span><span># step 级</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>7.2 GITHUB_TOKEN、权限与审批门禁<a href="#72-github_token权限与审批门禁"><span>#</span></a></h3><p>每次运行都会自动获得 <code>GITHUB_TOKEN</code>，默认权限在仓库设置里可配（历史默认读写全开，现在新仓库默认只读）。显式声明权限是最佳实践，也是安全审计最容易挑出来的问题。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>permissions</span><span>: { </span><span>contents</span><span>: </span><span>read</span><span> }   </span><span># workflow 顶层：保守基线</span></div></div><div><div><div>2</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>publish</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>permissions</span><span>: { </span><span>contents</span><span>: </span><span>read</span><span>, </span><span>packages</span><span>: </span><span>write</span><span>, </span><span>id-token</span><span>: </span><span>write</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在 Settings -&gt; Environments 里创建 <code>staging</code>、<code>production</code>，可配置必需审批人（人工点击才继续）、允许部署的分支、以及该环境专属 secrets。这是用 GitHub 原生能力实现“持续交付”的那道人工闸门。</p><p>部署 job 上声明环境即接入这道门禁：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>  </span><span>deploy-prod</span><span>:</span></div></div><div><div><div>2</div></div><div><span>    </span><span>environment</span><span>: { </span><span>name</span><span>: </span><span>production</span><span>, </span><span>url</span><span>: </span><span>https://sakura-hu.top</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>7.3 OIDC 免密钥访问云厂商<a href="#73-oidc-免密钥访问云厂商"><span>#</span></a></h3><p>把云厂商 AccessKey 存成长期 secret 是常见反模式：一旦泄露，攻击者可以一直用。OIDC 让 GitHub 给 job 签发短期 token，云平台验证签发者与仓库身份后临时发放凭证，仓库里不再存任何云凭证。</p><p>用法是 <code>aws-actions/configure-aws-credentials</code> 传 <code>role-to-assume</code>（形如 <code>arn:aws:iam::123456789012:role/github-actions-deploy</code>）与 <code>aws-region</code>，并给 job 声明 <code>permissions: { id-token: write }</code>。</p><p>AWS、GCP、Azure、阿里云、腾讯云都已支持。</p></section><section><h3>7.4 常见误用<a href="#74-常见误用"><span>#</span></a></h3><ul>
<li><strong>把密钥硬编码进 YAML</strong>。即使删掉，它也已躺在 git 历史里。密钥一旦入库，唯一正确的处理是立即轮换。</li>
<li><strong>在 PR 里打印 secrets</strong>。<code>echo ${{ secrets.X }}</code> 会写进日志，虽然 GitHub 会打码，但打码可以被编码、切片绕过，不要依赖它。</li>
<li><strong><code>pull_request_target</code> 的陷阱</strong>。这个触发器能在有 secrets 的上下文里运行。一旦你在这个 workflow 里 checkout 并执行 PR 的代码（比如装了对方改过的依赖），就等于把 secrets 交给任意陌生人。要用必须配环境审批，且绝不执行 PR 提供的代码。</li>
<li><strong>把 secrets 传给第三方 action</strong>。第三方 action 能读走你上下文里的所有东西，用之前先看源码。</li>
</ul></section></section>
<section><h2>八、实战一：静态博客自动部署<a href="#八实战一静态博客自动部署"><span>#</span></a></h2><p>目标：<code>git push origin master</code> 之后，站点自动构建并同步到自建服务器，无需人工介入。</p><section><h3>8.1 完整 workflow<a href="#81-完整-workflow"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>name</span><span>: </span><span>Deploy Blog</span></div></div><div><div><div>2</div></div><div><span>on</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>push</span><span>: { </span><span>branches</span><span>: [</span><span>master</span><span>] }</span></div></div><div><div><div>4</div></div><div><span>  </span><span>workflow_dispatch</span><span>:</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>concurrency</span><span>: { </span><span>group</span><span>: </span><span>deploy-blog</span><span>, </span><span>cancel-in-progress</span><span>: </span><span>false</span><span> }</span></div></div><div><div><div>7</div></div><div><span>permissions</span><span>: { </span><span>contents</span><span>: </span><span>read</span><span> }</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>10</div></div><div><span>  </span><span>build-and-deploy</span><span>:</span></div></div><div><div><div>11</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>12</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/checkout@v4</span></div></div><div><div><div>14</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>pnpm/action-setup@v4</span></div></div><div><div><div>15</div></div><div><span>        </span><span>with</span><span>: { </span><span>version</span><span>: </span><span>9</span><span> }</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/setup-node@v4</span></div></div><div><div><div>17</div></div><div><span>        </span><span>with</span><span>: { </span><span>node-version</span><span>: </span><span>22</span><span>, </span><span>cache</span><span>: </span><span>pnpm</span><span> }</span></div></div><div><div><div>18</div></div><div><span><span>      </span></span><span>- </span><span>run</span><span>: </span><span>pnpm install --frozen-lockfile</span></div></div><div><div><div>19</div></div><div><span>        </span><span>env</span><span>: { </span><span>CI</span><span>: </span><span>"1"</span><span> }</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>- </span><span>run</span><span>: </span><span>pnpm build</span></div></div><div><div><div>21</div></div><div><span>        </span><span>env</span><span>: { </span><span>CI</span><span>: </span><span>"1"</span><span> }</span></div></div><div><div><div>22</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>校验产物</span></div></div><div><div><div>23</div></div><div><span>        </span><span>run</span><span>: </span><span>|</span></div></div><div><div><div>24</div></div><div><span><span>          </span></span><span>test -d dist || { echo "dist 不存在"; exit 1; }</span></div></div><div><div><div>25</div></div><div><span><span>          </span></span><span>test -f dist/index.html || { echo "缺少 index.html"; exit 1; }</span></div></div><div><div><div>26</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>通过 SFTP 上传到服务器</span></div></div><div><div><div>27</div></div><div><span>        </span><span>uses</span><span>: </span><span>wlixcc/SFTP-Deploy-Action@v1.2.5</span></div></div><div><div><div>28</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>29</div></div><div><span>          </span><span>server</span><span>: </span><span>${{ secrets.DEPLOY_HOST }}</span></div></div><div><div><div>30</div></div><div><span>          </span><span>username</span><span>: </span><span>${{ secrets.DEPLOY_USER }}</span></div></div><div><div><div>31</div></div><div><span>          </span><span>password</span><span>: </span><span>${{ secrets.DEPLOY_PASS }}</span></div></div><div><div><div>32</div></div><div><span>          </span><span>port</span><span>: </span><span>22</span></div></div><div><div><div>33</div></div><div><span>          </span><span>local_path</span><span>: </span><span>./dist/*</span></div></div><div><div><div>34</div></div><div><span>          </span><span>remote_path</span><span>: </span><span>/www/wwwroot/sakura-hu.top/</span></div></div><div><div><div>35</div></div><div><span>          </span><span>sftp_only</span><span>: </span><span>true</span></div></div><div><div><div>36</div></div><div><span>          </span><span>delete_remote_files</span><span>: </span><span>false</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>8.2 secrets 配置与增量同步<a href="#82-secrets-配置与增量同步"><span>#</span></a></h3><p>在 Settings -&gt; Secrets and variables -&gt; Actions 新建三个 Repository secrets：</p>

<table><thead><tr><th>Secret 名</th><th>含义</th><th>示例</th></tr></thead><tbody><tr><td><code>DEPLOY_HOST</code></td><td>服务器地址或域名</td><td><code>sakura-hu.top</code></td></tr><tr><td><code>DEPLOY_USER</code></td><td>SSH 用户名</td><td><code>deploy</code></td></tr><tr><td><code>DEPLOY_PASS</code></td><td>SSH 密码</td><td>强密码</td></tr></tbody></table><p>更稳妥的是用 SSH 密钥：公钥放服务器 <code>~/.ssh/authorized_keys</code>，私钥存成 <code>DEPLOY_KEY</code>，action 里换成 <code>key: ${{ secrets.DEPLOY_KEY }}</code>；密码方案配置简单但没法轮换和精细授权。给部署单独建受限用户、只授予站点目录写权限，比用 root 安全得多。</p><p><code>delete_remote_files: false</code> 表示只覆盖同名文件、不删服务器上多余文件：好处是本地产物缺了某文件时旧文件还在，不会硬性 404；坏处是删掉的文章会一直留在服务器上。对带 hash 文件名的静态站点（Astro 产出 <code>index.a1b2c3.js</code> 这类名字），全量上传代价可观——每次构建资源 hash 都可能变，几百个小文件逐个走 SFTP，单次部署要几分钟。要做增量同步，<code>rsync</code> 更合适，它按“大小 + 修改时间”判断差异：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>写入部署私钥并增量同步</span></div></div><div><div><div>2</div></div><div><span>        </span><span>run</span><span>: </span><span>|</span></div></div><div><div><div>3</div></div><div><span><span>          </span></span><span>mkdir -p ~/.ssh &amp;&amp; chmod 700 ~/.ssh</span></div></div><div><div><div>4</div></div><div><span><span>          </span></span><span>printf '%s' "${{ secrets.DEPLOY_KEY }}" &gt; ~/.ssh/key &amp;&amp; chmod 600 ~/.ssh/key</span></div></div><div><div><div>5</div></div><div><span><span>          </span></span><span>ssh-keyscan ${{ secrets.DEPLOY_HOST }} &gt;&gt; ~/.ssh/known_hosts 2&gt;/dev/null</span></div></div><div><div><div>6</div></div><div><span><span>          </span></span><span>rsync -avz --delete --exclude='.well-known/' -e "ssh -i ~/.ssh/key" \</span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>./dist/ ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/www/wwwroot/sakura-hu.top/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>--delete</code> 是开启的：用了 rsync 就该做完整镜像同步，保证“服务器状态 = 构建产物”，避免残留文件造成诡异问题（比如旧 service worker 缓存住旧页面）。<code>--exclude</code> 用来保护服务器上证书校验之类的目录；<code>-z</code> 开压缩，静态站点压缩率很高。</p></section><section><h3>8.3 变体：同步到对象存储与 CDN<a href="#83-变体同步到对象存储与-cdn"><span>#</span></a></h3><p>托管在对象存储（COS / OSS / S3 / R2）时把最后一步换掉即可。要点是<strong>静态资源长缓存、HTML 短缓存</strong>。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>rclone</span><span> </span><span>sync</span><span> </span><span>./dist/_astro</span><span> </span><span>oss:my-bucket/_astro</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>--header-upload</span><span> </span><span>"Cache-Control: public, max-age=31536000, immutable"</span></div></div><div><div><div>3</div></div><div><span>rclone</span><span> </span><span>sync</span><span> </span><span>./dist</span><span> </span><span>oss:my-bucket</span><span> </span><span>--exclude</span><span> </span><span>"_astro/**"</span><span> </span><span>\</span></div></div><div><div><div>4</div></div><div><span>  </span><span>--header-upload</span><span> </span><span>"Cache-Control: public, max-age=300"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>sync</code> 会删除目标端多余文件（双向对齐，保证远端与构建产物一致），<code>copy</code> 只增不删，远端还存着别的东西时用 <code>copy</code>。</p></section></section>
<section><h2>九、实战二：构建 Docker 镜像并推送镜像仓库<a href="#九实战二构建-docker-镜像并推送镜像仓库"><span>#</span></a></h2><section><h3>9.1 多阶段 Dockerfile<a href="#91-多阶段-dockerfile"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>FROM</span><span> node:22-alpine </span><span>AS</span><span> builder</span></div></div><div><div><div>2</div></div><div><span>WORKDIR</span><span> /app</span></div></div><div><div><div>3</div></div><div><span>RUN</span><span> corepack enable</span></div></div><div><div><div>4</div></div><div><span>COPY</span><span> package.json pnpm-lock.yaml ./</span></div></div><div><div><div>5</div></div><div><span>RUN</span><span> pnpm install --frozen-lockfile</span></div></div><div><div><div>6</div></div><div><span>COPY</span><span> . .</span></div></div><div><div><div>7</div></div><div><span>RUN</span><span> pnpm build</span></div></div><div><div><div>8</div></div><div><span>FROM</span><span> nginx:1.27-alpine </span><span>AS</span><span> runner</span></div></div><div><div><div>9</div></div><div><span>COPY</span><span> --from=builder /app/dist /usr/share/nginx/html</span></div></div><div><div><div>10</div></div><div><span>HEALTHCHECK</span><span> --interval=30s --timeout=3s --retries=3 \</span></div></div><div><div><div>11</div></div><div><span>  </span><span>CMD</span><span> wget -qO- http://127.0.0.1/ || exit 1</span></div></div><div><div><div>12</div></div><div><span>CMD</span><span> [</span><span>"nginx"</span><span>, </span><span>"-g"</span><span>, </span><span>"daemon off;"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>构建工具链只存在于构建阶段，运行镜像里完全没有，体积更小、攻击面更小。<strong>不变的层放前面</strong>（先 <code>COPY</code> 锁文件装依赖，最后才 <code>COPY . .</code>），改业务代码时依赖层能命中缓存。</p></section><section><h3>9.2 构建并推送的 workflow<a href="#92-构建并推送的-workflow"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>name</span><span>: </span><span>Build and Push Image</span></div></div><div><div><div>2</div></div><div><span>on</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>push</span><span>: { </span><span>branches</span><span>: [</span><span>master</span><span>], </span><span>tags</span><span>: [</span><span>"v*"</span><span>] }</span></div></div><div><div><div>4</div></div><div><span>permissions</span><span>: { </span><span>contents</span><span>: </span><span>read</span><span>, </span><span>packages</span><span>: </span><span>write</span><span> }</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>7</div></div><div><span>  </span><span>docker</span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>9</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/checkout@v4</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>docker/setup-buildx-action@v3</span></div></div><div><div><div>12</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>docker/login-action@v3</span></div></div><div><div><div>13</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>14</div></div><div><span>          </span><span>registry</span><span>: </span><span>ghcr.io</span></div></div><div><div><div>15</div></div><div><span>          </span><span>username</span><span>: </span><span>${{ github.actor }}</span></div></div><div><div><div>16</div></div><div><span>          </span><span>password</span><span>: </span><span>${{ secrets.GITHUB_TOKEN }}</span></div></div><div><div><div>17</div></div><div><span><span>      </span></span><span>- </span><span>id</span><span>: </span><span>meta</span></div></div><div><div><div>18</div></div><div><span>        </span><span>uses</span><span>: </span><span>docker/metadata-action@v5</span></div></div><div><div><div>19</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>20</div></div><div><span>          </span><span>images</span><span>: </span><span>ghcr.io/${{ github.repository }}</span></div></div><div><div><div>21</div></div><div><span>          </span><span>tags</span><span>: </span><span>|</span></div></div><div><div><div>22</div></div><div><span><span>            </span></span><span>type=semver,pattern={{version}}</span></div></div><div><div><div>23</div></div><div><span><span>            </span></span><span>type=sha,prefix=sha-,format=short</span></div></div><div><div><div>24</div></div><div><span><span>            </span></span><span>type=raw,value=latest,enable={{is_default_branch}}</span></div></div><div><div><div>25</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>docker/build-push-action@v6</span></div></div><div><div><div>26</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>27</div></div><div><span>          </span><span>context</span><span>: </span><span>.</span></div></div><div><div><div>28</div></div><div><span>          </span><span>push</span><span>: </span><span>true</span></div></div><div><div><div>29</div></div><div><span>          </span><span>platforms</span><span>: </span><span>linux/amd64,linux/arm64</span></div></div><div><div><div>30</div></div><div><span>          </span><span>tags</span><span>: </span><span>${{ steps.meta.outputs.tags }}</span></div></div><div><div><div>31</div></div><div><span>          </span><span>cache-from</span><span>: </span><span>type=gha</span></div></div><div><div><div>32</div></div><div><span>          </span><span>cache-to</span><span>: </span><span>type=gha,mode=max</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>推 Docker Hub 只需把 <code>registry</code> 换成 <code>docker.io</code>、<code>images</code> 换成 <code>用户名/仓库名</code>，密码用 <code>DOCKERHUB_TOKEN</code>。</p></section><section><h3>9.3 标签策略<a href="#93-标签策略"><span>#</span></a></h3>

<table><thead><tr><th>标签</th><th>生成规则</th><th>用途</th><th>是否适合回滚</th></tr></thead><tbody><tr><td><code>sha-abc1234</code></td><td>每次提交</td><td>精确锁定某个 commit 的镜像</td><td>是（最可靠）</td></tr><tr><td><code>1.4.2</code></td><td>打 tag 时</td><td>语义化版本发布</td><td>是</td></tr><tr><td><code>1.4</code></td><td>打 tag 时</td><td>跟随次版本</td><td>是（会移动）</td></tr><tr><td><code>latest</code></td><td>默认分支</td><td>方便本地拉取</td><td>否（会被覆盖）</td></tr></tbody></table><p>生产部署<strong>永远不要用 <code>latest</code></strong>，用 <code>sha-</code> 或语义化版本，回滚时把 tag 换回上一版即可。注意跨平台构建（<code>linux/amd64,linux/arm64</code>）走 QEMU 模拟，耗时是单平台的好几倍；只有 x86 服务器时去掉 <code>platforms</code> 能显著提速。</p></section><section><h3>9.4 服务器拉取新镜像并重启<a href="#94-服务器拉取新镜像并重启"><span>#</span></a></h3><p>最轻量的做法是服务器上放一个脚本，由流水线通过 SSH 触发：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/usr/bin/env bash</span></div></div><div><div><div>2</div></div><div><span>set</span><span> </span><span>-euo</span><span> </span><span>pipefail</span></div></div><div><div><div>3</div></div><div><span>IMAGE</span><span>=</span><span>"ghcr.io/owner/blog:sha-</span><span><span>${1</span><span>:?</span></span><span>usage</span><span>:</span><span> </span><span>deploy</span><span>.</span><span>sh</span><span> </span><span>&lt;sha&gt;</span><span>}</span><span>"</span></div></div><div><div><div>4</div></div><div><span>echo</span><span> </span><span>"</span><span>$GHCR_TOKEN</span><span>"</span><span> | </span><span>docker</span><span> </span><span>login</span><span> </span><span>ghcr.io</span><span> </span><span>-u</span><span> </span><span>owner</span><span> </span><span>--password-stdin</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>pull</span><span> </span><span>"</span><span>$IMAGE</span><span>"</span></div></div><div><div><div>6</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span><span> </span><span>--no-deps</span><span> </span><span>--force-recreate</span><span> </span><span>blog</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>for</span><span> </span><span>i</span><span> </span><span>in</span><span> $(</span><span>seq</span><span> </span><span>1</span><span> </span><span>15</span><span>); </span><span>do</span></div></div><div><div><div>9</div></div><div><span>  </span><span>curl</span><span> </span><span>-fsS</span><span> </span><span>-m</span><span> </span><span>3</span><span> </span><span>http://127.0.0.1:8080/healthz</span><span> &gt;</span><span>/dev/null</span><span> &amp;&amp; </span><span>exit</span><span> </span><span>0</span></div></div><div><div><div>10</div></div><div><span>  </span><span>sleep</span><span> </span><span>2</span></div></div><div><div><div>11</div></div><div><span>done</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>echo</span><span> </span><span>"健康检查失败，回滚"</span></div></div><div><div><div>14</div></div><div><span>PREV</span><span><span>=</span><span>$(</span></span><span>cat</span><span> </span><span>/opt/blog/.previous-image</span><span>)</span></div></div><div><div><div>15</div></div><div><span>sed</span><span> </span><span>-i</span><span> </span><span>"s|image:.*|image: </span><span><span>${</span><span>PREV</span><span>}</span></span><span>|"</span><span> </span><span>docker-compose.yml</span></div></div><div><div><div>16</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span><span> </span><span>--no-deps</span><span> </span><span>--force-recreate</span><span> </span><span>blog</span></div></div><div><div><div>17</div></div><div><span>exit</span><span> </span><span>1</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>appleboy/ssh-action@v1</span></div></div><div><div><div>2</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>3</div></div><div><span>          </span><span>host</span><span>: </span><span>${{ secrets.DEPLOY_HOST }}</span></div></div><div><div><div>4</div></div><div><span>          </span><span>username</span><span>: </span><span>${{ secrets.DEPLOY_USER }}</span></div></div><div><div><div>5</div></div><div><span>          </span><span>key</span><span>: </span><span>${{ secrets.DEPLOY_KEY }}</span></div></div><div><div><div>6</div></div><div><span>          </span><span>script</span><span>: </span><span>/opt/blog/deploy.sh ${{ github.sha }}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>脚本把成功部署的镜像名写进 <code>.previous-image</code>，回滚时读出来用——这就是最简单可靠的一键回滚。</p></section></section>
<section><h2>十、实战三：Node/前端项目的质量门禁<a href="#十实战三node前端项目的质量门禁"><span>#</span></a></h2><p>目标：PR 上自动跑 lint / 类型检查 / 单测 / 构建，结果以评论回贴，只有全绿才允许部署。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>name</span><span>: </span><span>CI</span></div></div><div><div><div>2</div></div><div><span>on</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>pull_request</span><span>: { </span><span>branches</span><span>: [</span><span>master</span><span>] }</span></div></div><div><div><div>4</div></div><div><span>  </span><span>push</span><span>: { </span><span>branches</span><span>: [</span><span>master</span><span>] }</span></div></div><div><div><div>5</div></div><div><span>concurrency</span><span>: { </span><span>group</span><span>: </span><span>"ci-${{ github.ref }}"</span><span>, </span><span>cancel-in-progress</span><span>: </span><span>true</span><span> }</span></div></div><div><div><div>6</div></div><div><span>permissions</span><span>: { </span><span>contents</span><span>: </span><span>read</span><span>, </span><span>pull-requests</span><span>: </span><span>write</span><span> }</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>9</div></div><div><span>  </span><span>check</span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>11</div></div><div><span>    </span><span>strategy</span><span>:</span></div></div><div><div><div>12</div></div><div><span>      </span><span>fail-fast</span><span>: </span><span>false</span></div></div><div><div><div>13</div></div><div><span>      </span><span>matrix</span><span>: { </span><span>task</span><span>: [</span><span>lint</span><span>, </span><span>typecheck</span><span>, </span><span>test</span><span>, </span><span>build</span><span>] }</span></div></div><div><div><div>14</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>15</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/checkout@v4</span></div></div><div><div><div>16</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>pnpm/action-setup@v4</span></div></div><div><div><div>17</div></div><div><span>        </span><span>with</span><span>: { </span><span>version</span><span>: </span><span>9</span><span> }</span></div></div><div><div><div>18</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/setup-node@v4</span></div></div><div><div><div>19</div></div><div><span>        </span><span>with</span><span>: { </span><span>node-version</span><span>: </span><span>22</span><span>, </span><span>cache</span><span>: </span><span>pnpm</span><span> }</span></div></div><div><div><div>20</div></div><div><span><span>      </span></span><span>- </span><span>run</span><span>: </span><span>pnpm install --frozen-lockfile</span></div></div><div><div><div>21</div></div><div><span><span>      </span></span><span>- </span><span>run</span><span>: </span><span>pnpm ${{ matrix.task }}</span></div></div><div><div><div>22</div></div><div><span><span>      </span></span><span>- </span><span>if</span><span>: </span><span>matrix.task == 'test'</span></div></div><div><div><div>23</div></div><div><span>        </span><span>uses</span><span>: </span><span>codecov/codecov-action@v5</span></div></div><div><div><div>24</div></div><div><span>        </span><span>with</span><span>: { </span><span>files</span><span>: </span><span>./coverage/lcov.info</span><span>, </span><span>fail_ci_if_error</span><span>: </span><span>false</span><span> }</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span>  </span><span>comment</span><span>:</span></div></div><div><div><div>27</div></div><div><span>    </span><span>needs</span><span>: [</span><span>check</span><span>]</span></div></div><div><div><div>28</div></div><div><span>    </span><span>if</span><span>: </span><span>always() &amp;&amp; github.event_name == 'pull_request'</span></div></div><div><div><div>29</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>30</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>31</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>marocchino/sticky-pull-request-comment@v2</span></div></div><div><div><div>32</div></div><div><span>        </span><span>with</span><span>: { </span><span>header</span><span>: </span><span>ci-result</span><span>, </span><span>message</span><span>: </span><span>"CI 结果：${{ needs.check.result }}"</span><span> }</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>  </span><span>deploy</span><span>:</span></div></div><div><div><div>35</div></div><div><span>    </span><span>needs</span><span>: [</span><span>check</span><span>]</span></div></div><div><div><div>36</div></div><div><span>    </span><span>if</span><span>: </span><span>github.ref == 'refs/heads/master' &amp;&amp; github.event_name == 'push'</span></div></div><div><div><div>37</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>38</div></div><div><span>    </span><span>steps</span><span>: [{ </span><span>run</span><span>: </span><span>echo "全部检查通过，开始部署"</span><span> }]</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>设计要点：用 <code>matrix</code> 让四个检查<strong>并行</strong>，总耗时约等于最慢的那个而不是四个之和（每个组合仍独立串行）；<code>comment</code> 用 <code>if: always()</code>，某个检查失败时评论依然会贴上结果，而不是什么都不说；<code>deploy</code> 用 <code>needs: [check]</code> 卡住——<code>check</code> 矩阵中任一组合失败，部署 job 直接跳过，这是“质量门禁”最直接的实现；覆盖率上传用 <code>fail_ci_if_error: false</code>，覆盖率服务偶发抽风不该阻断主流程，若把覆盖率当硬指标就改成 <code>true</code> 并加阈值检查。</p></section>
<section><h2>十一、部署策略<a href="#十一部署策略"><span>#</span></a></h2><section><h3>11.1 四种策略对比<a href="#111-四种策略对比"><span>#</span></a></h3>

<table><thead><tr><th>策略</th><th>原理</th><th>停机</th><th>回滚速度</th><th>资源成本</th><th>适用场景</th></tr></thead><tbody><tr><td>停机部署</td><td>停旧版，发新版</td><td>有（秒级到分钟级）</td><td>快</td><td>1 倍</td><td>内部系统、可接受短暂不可用</td></tr><tr><td>滚动更新</td><td>逐批替换实例，新的就绪后再下线旧的</td><td>无</td><td>中</td><td>1 倍多</td><td>K8s 默认，无状态服务</td></tr><tr><td>蓝绿部署</td><td>两套完整环境，流量一次性切换</td><td>无</td><td>极快（切回旧环境）</td><td>2 倍</td><td>关键业务、需要秒级回滚</td></tr><tr><td>金丝雀发布</td><td>新版本只接 1% ~ 10% 流量，观察后逐步放量</td><td>无</td><td>快（流量切回旧版）</td><td>1.x 倍</td><td>大流量、降低发布风险</td></tr></tbody></table><p>蓝绿的核心是<strong>两套环境 + 一个流量开关</strong>：新版部署到绿色环境 -&gt; 跑冒烟测试 -&gt; 负载均衡把流量从蓝色切到绿色 -&gt; 观察后保留蓝色作为回滚目标，回滚就是再切一次。</p><p>金丝雀的核心是<strong>按比例放量 + 指标自动决策</strong>：导入 1% 流量 -&gt; 观察错误率、延迟、核心业务指标 -&gt; 正常则 5% -&gt; 25% -&gt; 50% -&gt; 100%，任一步恶化就自动回滚。相比蓝绿，它多了“在小流量下发现测试覆盖不到的问题”的能力，代价是需要完善的指标监控与流量切分。实践中两者组合：蓝绿做底层发布单元，金丝雀做流量层策略。</p></section><section><h3>11.2 健康检查与就绪探针<a href="#112-健康检查与就绪探针"><span>#</span></a></h3><p>切换流量的前提是新版本真的能服务。存活探针（liveness）判断进程是否还在，失败就重启；就绪探针（readiness）判断能否接流量，失败就从负载均衡摘除但不重启。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>    </span><span>readinessProbe</span><span>:</span></div></div><div><div><div>2</div></div><div><span>      </span><span>httpGet</span><span>: { </span><span>path</span><span>: </span><span>/healthz</span><span>, </span><span>port</span><span>: </span><span>8080</span><span> }</span></div></div><div><div><div>3</div></div><div><span>      </span><span>initialDelaySeconds</span><span>: </span><span>5</span></div></div><div><div><div>4</div></div><div><span>      </span><span>periodSeconds</span><span>: </span><span>5</span></div></div><div><div><div>5</div></div><div><span>    </span><span>livenessProbe</span><span>:</span></div></div><div><div><div>6</div></div><div><span>      </span><span>httpGet</span><span>: { </span><span>path</span><span>: </span><span>/livez</span><span>, </span><span>port</span><span>: </span><span>8080</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>/healthz</code> 不应只检查进程存活，还应检查下游依赖（数据库、缓存）。<strong>流水线的部署脚本必须把健康检查当门禁</strong>：部署完成 -&gt; 轮询健康端点最多 N 次 -&gt; 全部失败就自动回滚并让 job 失败。没有这一步，所谓自动部署只是自动把故障推上线。</p></section><section><h3>11.3 数据库迁移的处理原则<a href="#113-数据库迁移的处理原则"><span>#</span></a></h3><p>数据库是 CI/CD 里最难原子化的部分——代码可以两套并存，schema 不能。三条原则：</p><ol>
<li><strong>向前兼容</strong>。先上兼容新旧两版的 schema 变更，再上使用新结构的代码。</li>
<li><strong>先迁移后发布</strong>。迁移脚本独立执行，且要能在旧版本应用还在跑时执行而不出错（新增可空列、新增表、加索引都符合）。</li>
<li><strong>可回滚的迁移</strong>。删表、删列、改类型必须拆成多次发布。回滚应用代码时数据库不应该需要回滚，因为迁移本身向前兼容。</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>发布 1：新增 new_status 列（可空）-&gt; 代码双写 status 和 new_status</span></div></div><div><div><div>2</div></div><div><span>发布 2：数据迁移脚本回填 new_status</span></div></div><div><div><div>3</div></div><div><span>发布 3：代码只读 new_status</span></div></div><div><div><div>4</div></div><div><span>发布 4：删除旧列 status</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用了 ORM 自动建表的话，务必关掉生产环境的 <code>synchronize</code> / <code>auto-migrate</code>，迁移必须是一次性脚本且能被审批。</p></section></section>
<section><h2>十二、回滚与故障处理<a href="#十二回滚与故障处理"><span>#</span></a></h2><p>回滚能多快，取决于上一版制品还在不在。容器镜像每个版本有独立 tag，回滚就是改 tag 重启；静态站点在服务器保留 <code>releases/</code> 与 <code>current</code> 两个目录，用软链接切换；artifact 配合 <code>retention-days: 90</code>，需要时手动下载重新部署。</p><p>一键回滚的最小可行版本是三条命令：拉上一个镜像 tag -&gt; 重启容器 -&gt; 健康检查。做成独立 workflow 用 <code>workflow_dispatch</code> 触发：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>name</span><span>: </span><span>Rollback</span></div></div><div><div><div>2</div></div><div><span>on</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>workflow_dispatch</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>inputs</span><span>:</span></div></div><div><div><div>5</div></div><div><span>      </span><span>version</span><span>: { </span><span>description</span><span>: </span><span>"回滚到的镜像 tag（留空则回滚上一版）"</span><span>, </span><span>default</span><span>: </span><span>""</span><span> }</span></div></div><div><div><div>6</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>7</div></div><div><span>  </span><span>rollback</span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>9</div></div><div><span>    </span><span>environment</span><span>: </span><span>production</span></div></div><div><div><div>10</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>appleboy/ssh-action@v1</span></div></div><div><div><div>12</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>13</div></div><div><span>          </span><span>host</span><span>: </span><span>${{ secrets.DEPLOY_HOST }}</span></div></div><div><div><div>14</div></div><div><span>          </span><span>username</span><span>: </span><span>${{ secrets.DEPLOY_USER }}</span></div></div><div><div><div>15</div></div><div><span>          </span><span>key</span><span>: </span><span>${{ secrets.DEPLOY_KEY }}</span></div></div><div><div><div>16</div></div><div><span>          </span><span>script</span><span>: </span><span>/opt/blog/rollback.sh ${{ github.event.inputs.version }}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>它挂在 <code>environment: production</code> 上，只有被授权的审批人能触发——回滚是高权限操作，值得单独一道门。</p><p>失败通知的价值在于缩短“故障被发现”的时间。邮件是 GitHub 原生能力、零成本但容易被忽略；企业微信 / 钉钉机器人群 webhook 一个 curl 就能打通；Slack 用官方 action 或 webhook。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>name</span><span>: </span><span>失败通知</span></div></div><div><div><div>2</div></div><div><span>        </span><span>if</span><span>: </span><span>failure()</span></div></div><div><div><div>3</div></div><div><span>        </span><span>run</span><span>: </span><span>|</span></div></div><div><div><div>4</div></div><div><span><span>          </span></span><span>curl -sS -X POST "${{ secrets.WECOM_WEBHOOK }}" \</span></div></div><div><div><div>5</div></div><div><span><span>            </span></span><span>-H "Content-Type: application/json" \</span></div></div><div><div><div>6</div></div><div><span><span>            </span></span><span>-d "{\"msgtype\":\"markdown\",\"markdown\":{\"content\":\"**部署失败** ${{ github.repository }} @ ${{ github.sha }} [日志](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\"}}"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>通知里一定要带跳转到运行日志的链接，否则收到消息的人还要自己去翻，等于没通知。另一个原则是只在状态变化时通知，失败就一直刷屏的机器人很快会被所有人静音。</p></section>
<section><h2>十三、流水线优化<a href="#十三流水线优化"><span>#</span></a></h2><ol>
<li><strong>提高缓存命中率</strong>。key 分层、稳定因子放前、缓存包管理器 store 而不是 <code>node_modules</code>。命中率能在日志里看到（<code>Cache restored from key: xxx</code>），低于 70% 基本可以判断 key 设计有问题。</li>
<li><strong>并行化拆分</strong>。把线性串联的检查改成并行 job。但每次拆分会多一次 checkout + install，单个 job 只有 30 秒时拆开反而更慢——用装依赖那一步的耗时来权衡。</li>
<li><strong>只在必要时跑重任务</strong>。<code>paths</code> 过滤让文档改动不触发构建；<code>if</code> 让 PR 只跑测试不跑部署。</li>
<li><strong>用 matrix 代替重复 YAML</strong>。同一套步骤跑 4 个 Node 版本写一份 matrix 就行，不要复制粘贴四遍 job。</li>
<li><strong>用 composite action 抽公共步骤</strong>。把“安装 pnpm + setup-node + install”这段每个 job 都重复的样板抽成本地 action。</li>
<li><strong>自托管 runner 的取舍</strong>。省钱的场景（私有仓库按分钟计费）和内网访问场景值得上，但要考虑维护成本、单点故障与安全风险。个人项目用托管 runner 的免费额度通常足够。</li>
<li><strong>减少不必要的 setup</strong>。不需要 Node 的 job 别写 <code>setup-node</code>，纯粹拖慢启动。</li>
</ol></section>
<section><h2>十四、其他 CI/CD 工具对比<a href="#十四其他-cicd-工具对比"><span>#</span></a></h2>

<table><thead><tr><th>工具</th><th>上手成本</th><th>生态</th><th>自托管难度</th><th>适合场景</th></tr></thead><tbody><tr><td>GitHub Actions</td><td>低</td><td>极大（Marketplace 上万 action）</td><td>中（需自托管 runner）</td><td>代码在 GitHub，开源首选</td></tr><tr><td>GitLab CI/CD</td><td>低</td><td>大</td><td>低（与 GitLab 一体化）</td><td>代码在 GitLab，要完整 DevOps 平台</td></tr><tr><td>Jenkins</td><td>高</td><td>极大但老化</td><td>高（自己维护 Master + Agent）</td><td>老牌企业、强定制、内网环境</td></tr><tr><td>CircleCI</td><td>中</td><td>大</td><td>不支持（纯 SaaS）</td><td>追求构建速度，配置简洁</td></tr><tr><td>Drone</td><td>中</td><td>中</td><td>低（容器化，很轻）</td><td>想自建一套轻量流水线</td></tr><tr><td>Argo CD</td><td>中</td><td>大</td><td>低（K8s 原生）</td><td>K8s 集群的 GitOps 声明式部署</td></tr><tr><td>Tekton</td><td>高</td><td>中</td><td>低（K8s CRD）</td><td>想把 CI/CD 也当 K8s 资源的平台团队</td></tr></tbody></table><p><strong>Jenkins Pipeline as Code</strong>：用 <code>Jenkinsfile</code>（声明式或脚本式 Groovy）把流水线定义进仓库，思路与 Actions 的 YAML 一致，但语法更灵活也更难写对。优势是插件生态和“什么都能干”，劣势是插件质量参差、升级容易炸、维护成本高。</p><p><strong>Argo CD 与 GitOps</strong>：核心思想是<strong>以 Git 仓库中的声明式配置为唯一事实来源</strong>，集群实际状态由控制器持续向 Git 对齐。它和传统“CI 推部署”相反——CI 只负责构建镜像并更新 Git 里的 manifest（改镜像 tag），Argo CD 监测到变化后主动拉取并同步。好处是部署状态可审计（Git 历史即部署历史）、回滚就是 revert 一个 commit、集群被手动改乱也能自动纠偏。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>传统 push 模式：CI -&gt; 直接调用 K8s API 部署</span></div></div><div><div><div>2</div></div><div><span>GitOps pull 模式：CI -&gt; 改 Git manifest 里的镜像 tag -&gt; Argo CD 监测差异 -&gt; 同步到集群</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>Tekton</strong>：把每一步定义成 K8s CRD（Task、Pipeline、PipelineRun），跑在 Pod 里。灵活性极高，代价是 YAML 冗长、学习曲线陡，适合已有成熟 K8s 平台的团队。</p><p>选型建议：<strong>默认选代码托管平台自带的 CI/CD</strong>，只有当它满足不了需求（内网、特殊硬件、合规）时才考虑 Jenkins 或自建方案；K8s 环境上 GitOps 值得单独评估。</p></section>
<section><h2>十五、安全与合规<a href="#十五安全与合规"><span>#</span></a></h2><p><strong>依赖漏洞扫描</strong>。Dependabot 配置即可自动开 PR 升级有漏洞的依赖：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>version</span><span>: </span><span>2</span></div></div><div><div><div>2</div></div><div><span>updates</span><span>:</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>- { </span><span>package-ecosystem</span><span>: </span><span>npm</span><span>, </span><span>directory</span><span>: </span><span>/</span><span>, </span><span>schedule</span><span>: { </span><span>interval</span><span>: </span><span>weekly</span><span> } }</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>- { </span><span>package-ecosystem</span><span>: </span><span>github-actions</span><span>, </span><span>directory</span><span>: </span><span>/</span><span>, </span><span>schedule</span><span>: { </span><span>interval</span><span>: </span><span>weekly</span><span> } }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>镜像层面用 Trivy，SAST 用免费的 CodeQL：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>aquasecurity/trivy-action@0.28.0</span></div></div><div><div><div>2</div></div><div><span>        </span><span>with</span><span>:</span></div></div><div><div><div>3</div></div><div><span>          </span><span>image-ref</span><span>: </span><span>ghcr.io/${{ github.repository }}:sha-${{ github.sha }}</span></div></div><div><div><div>4</div></div><div><span>          </span><span>severity</span><span>: </span><span>"CRITICAL,HIGH"</span></div></div><div><div><div>5</div></div><div><span>          </span><span>exit-code</span><span>: </span><span>"1"</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>github/codeql-action/init@v3</span></div></div><div><div><div>7</div></div><div><span>        </span><span>with</span><span>: { </span><span>languages</span><span>: </span><span>javascript-typescript</span><span> }</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>github/codeql-action/analyze@v3</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>供应链安全</strong>。第三方 action 本质上是能在你流水线里执行任意代码的程序，几条硬规则：固定版本（<code>actions/checkout@v4</code> 里的 <code>v4</code> 是可移动 tag，严格做法是固定到完整 commit SHA，Dependabot 会自动帮你更新）；只引入可信 action，用前看一眼 <code>action.yml</code> 和源码；限制 <code>GITHUB_TOKEN</code> 权限；不要把 secrets 暴露给 fork PR。</p><p><strong>审计日志</strong>。Actions 的运行记录本身就是审计日志：谁触发的、什么时间、部署了哪个 commit。把它同步到内部系统，或在部署后往 release 里写一条记录，能让“线上这个版本对应哪个 commit”随时可查。</p></section>
<section><h2>十六、面试高频问题<a href="#十六面试高频问题"><span>#</span></a></h2><p><strong>Q1：CI 和 CD 的区别？</strong>
CI 是持续集成：频繁把代码合并到主干，每次合并自动跑构建与测试，尽早发现集成问题。CD 有两个含义——持续交付是每次合并后自动部署到类生产环境、验证通过后等待人工批准上线；持续部署则连人工批准也省掉。三者是递进关系。</p><p><strong>Q2：如何保证部署的原子性？</strong>
分层次答。制品层面：每次构建产出唯一标识的不可变制品（如 sha 镜像），部署时整体替换而不是原地改文件。切换层面：用蓝绿或滚动更新，让“新版本完全就绪”和“流量切过去”之间没有中间态；K8s 的 <code>RollingUpdate</code> 靠 readiness 探针保证新 Pod 就绪才接流量。数据库层面做不到严格原子，靠向前兼容的多次发布规避。</p><p><strong>Q3：数据库迁移怎么在 CI/CD 里做？</strong>
核心是“代码和 schema 不能同时大改”。原则是向前兼容、先迁移后发代码、不可逆操作拆多次发布。具体用 expand-contract 模式：先加新列并双写，回填数据，切读，最后删旧列。迁移脚本单独执行且可审计，禁止应用启动时自动同步 schema。</p><p><strong>Q4：密钥怎么管？</strong>
不用明文、不入库、不打印。具体：仓库或环境级 secrets 存储，通过环境变量注入；云上优先用 OIDC 换短期凭证而不是长期 AccessKey；最小权限，每个环境独立密钥；定期轮换；CI 日志里避免 echo 密钥。如果不小心提交进 git，立即轮换而不是只删 commit。</p><p><strong>Q5：Jenkins 和 GitHub Actions 的差异？</strong>
Actions 是托管 SaaS，配置是仓库内 YAML，与代码一起版本化，生态是 Marketplace 的复用 action，上手快、维护成本低，缺点是内网资源场景要自托管 runner、深度定制能力有限。Jenkins 是自建服务，用 Groovy 写 Pipeline，插件生态庞大、几乎能做任何事，适合内网和高度定制，但插件兼容、版本升级、Master 高可用都要自己扛。一句话：新项目默认 Actions，内网或强定制才考虑 Jenkins。</p><p><strong>Q6：怎么做灰度发布？</strong>
三步说。一是流量切分：在网关或 Service Mesh 层按比例 / 按用户特征把一部分流量路由到新版本。二是部署与观察：先放 1%~10% 流量，观察错误率、P95 延迟和核心业务指标，设定自动回滚阈值。三是渐进放量：指标正常就按 5% -&gt; 25% -&gt; 50% -&gt; 100% 扩大，任一步异常立刻切回旧版本。前提是可观测性到位——没有指标就没有灰度的判断依据。</p></section>
<section><h2>十七、速查表<a href="#十七速查表"><span>#</span></a></h2><section><h3>17.1 触发器与并发<a href="#171-触发器与并发"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>on</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>push</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>branches</span><span>: [</span><span>master</span><span>, </span><span>"release/**"</span><span>]</span></div></div><div><div><div>4</div></div><div><span>    </span><span>tags</span><span>: [</span><span>"v*"</span><span>]</span></div></div><div><div><div>5</div></div><div><span>    </span><span>paths</span><span>: [</span><span>"src/**"</span><span>, </span><span>"package.json"</span><span>]</span></div></div><div><div><div>6</div></div><div><span>  </span><span>pull_request</span><span>: { </span><span>branches</span><span>: [</span><span>master</span><span>] }</span></div></div><div><div><div>7</div></div><div><span>  </span><span>schedule</span><span>:</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>- </span><span>cron</span><span>: </span><span>"0 2 * * *"</span><span>        </span><span># UTC，北京时间为 10:00</span></div></div><div><div><div>9</div></div><div><span>  </span><span>workflow_dispatch</span><span>:</span></div></div><div><div><div>10</div></div><div><span>    </span><span>inputs</span><span>:</span></div></div><div><div><div>11</div></div><div><span>      </span><span>env</span><span>: { </span><span>type</span><span>: </span><span>choice</span><span>, </span><span>options</span><span>: [</span><span>staging</span><span>, </span><span>production</span><span>] }</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>concurrency</span><span>:</span></div></div><div><div><div>14</div></div><div><span>  </span><span>group</span><span>: </span><span>${{ github.workflow }}-${{ github.ref }}</span></div></div><div><div><div>15</div></div><div><span>  </span><span>cancel-in-progress</span><span>: </span><span>true</span><span>     </span><span># 部署类改 false</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>17.2 Job 骨架<a href="#172-job-骨架"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>permissions</span><span>: { </span><span>contents</span><span>: </span><span>read</span><span> }</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>jobs</span><span>:</span></div></div><div><div><div>4</div></div><div><span>  </span><span>build</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>6</div></div><div><span>    </span><span>strategy</span><span>: { </span><span>matrix</span><span>: { </span><span>node</span><span>: [</span><span>20</span><span>, </span><span>22</span><span>] } }</span></div></div><div><div><div>7</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/checkout@v4</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/setup-node@v4</span></div></div><div><div><div>10</div></div><div><span>        </span><span>with</span><span>: { </span><span>node-version</span><span>: </span><span>"${{ matrix.node }}"</span><span>, </span><span>cache</span><span>: </span><span>npm</span><span> }</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>- </span><span>run</span><span>: </span><span>npm ci &amp;&amp; npm run build</span></div></div><div><div><div>12</div></div><div><span>        </span><span>env</span><span>: { </span><span>API_BASE</span><span>: </span><span>"${{ vars.API_BASE }}"</span><span>, </span><span>TOKEN</span><span>: </span><span>"${{ secrets.TOKEN }}"</span><span> }</span></div></div><div><div><div>13</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/upload-artifact@v4</span></div></div><div><div><div>14</div></div><div><span>        </span><span>with</span><span>: { </span><span>name</span><span>: </span><span>"dist-${{ matrix.node }}"</span><span>, </span><span>path</span><span>: </span><span>dist/</span><span> }</span></div></div><div><div><div>15</div></div><div><span>  </span><span>deploy</span><span>:</span></div></div><div><div><div>16</div></div><div><span>    </span><span>needs</span><span>: </span><span>build</span></div></div><div><div><div>17</div></div><div><span>    </span><span>if</span><span>: </span><span>github.ref == 'refs/heads/master'</span></div></div><div><div><div>18</div></div><div><span>    </span><span>environment</span><span>: </span><span>production</span></div></div><div><div><div>19</div></div><div><span>    </span><span>runs-on</span><span>: </span><span>ubuntu-latest</span></div></div><div><div><div>20</div></div><div><span>    </span><span>steps</span><span>:</span></div></div><div><div><div>21</div></div><div><span><span>      </span></span><span>- </span><span>uses</span><span>: </span><span>actions/download-artifact@v4</span></div></div><div><div><div>22</div></div><div><span>        </span><span>with</span><span>: { </span><span>name</span><span>: </span><span>dist-22</span><span>, </span><span>path</span><span>: </span><span>dist</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>17.3 常见坑一览<a href="#173-常见坑一览"><span>#</span></a></h3>

<table><thead><tr><th>现象</th><th>原因</th><th>解法</th></tr></thead><tbody><tr><td>多行 run 被压成一行</td><td>YAML 里 <code>run:</code> 没加 <code>|</code></td><td>写成 <code>run: |</code> 后接缩进的多行内容</td></tr><tr><td>secrets 在 PR 里是空的</td><td>fork PR 不传 secrets</td><td>用环境审批，或谨慎使用 <code>pull_request_target</code></td></tr><tr><td>部署脚本莫名被取消</td><td><code>concurrency</code> 配了 <code>cancel-in-progress: true</code></td><td>部署类改为 <code>false</code>，并用独立 group</td></tr><tr><td>缓存永远不命中</td><td>key 里混了每次都变的因子（sha、时间戳）</td><td>把 <code>hashFiles</code> 放前段，sha 只做精确匹配层</td></tr><tr><td>改文档也跑全量流水线</td><td>没配 <code>paths</code> / <code>paths-ignore</code></td><td>加上路径过滤</td></tr><tr><td>定时任务时间不对</td><td>cron 用 UTC</td><td>按 +8 小时换算</td></tr><tr><td><code>git describe</code> 报错</td><td>默认浅克隆</td><td><code>checkout</code> 加 <code>fetch-depth: 0</code></td></tr><tr><td>上传 artifact 报 name 重复</td><td>多 job 用了同一个 name</td><td>name 里带 <code>${{ matrix.x }}</code> 区分</td></tr><tr><td>部署后页面 404</td><td>全量上传缺文件，或 rsync 删了构建不完整的内容</td><td>构建后先校验 <code>index.html</code> 存在再同步</td></tr><tr><td><code>latest</code> 回滚不了</td><td>标签被覆盖</td><td>生产用 <code>sha-</code> 或语义化版本</td></tr><tr><td>自托管 runner 卡死</td><td>磁盘满 / 无清理</td><td>定期清 docker 与 workspace，设 <code>timeout-minutes</code></td></tr><tr><td>权限报 403</td><td><code>GITHUB_TOKEN</code> 默认只读</td><td>显式写 <code>permissions: packages: write</code> 等</td></tr></tbody></table></section></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/devops/dockercompose%E5%A4%9A%E5%AE%B9%E5%99%A8%E7%BC%96%E6%8E%92/</id>
      <title type="text">Docker Compose 多容器编排实战</title>
      <published>2026-09-11T00:00:00.000Z</published>
      <updated>2026-09-11T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/devops/dockercompose%E5%A4%9A%E5%AE%B9%E5%99%A8%E7%BC%96%E6%8E%92/"/>
      <summary type="text">Docker Compose 多容器编排指南，涵盖 compose.yaml 语法、服务依赖与健康检查、环境变量管理，以及前后端数据库一键启动的完整实战</summary>
      <content type="html"><![CDATA[<section><h2>1. 为什么需要 Compose<a href="#1-为什么需要-compose"><span>#</span></a></h2><p>一个正经项目很少只有一个容器：前端要 Nginx 托管静态资源，后端跑 Node 或 Spring，数据存 PostgreSQL，缓存用 Redis。全靠 <code>docker run</code> 手动拉起，大概是这样：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>network</span><span> </span><span>create</span><span> </span><span>shop-net</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>shop-db</span><span> </span><span>--network</span><span> </span><span>shop-net</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>-e</span><span> </span><span>POSTGRES_PASSWORD=secret</span><span> </span><span>-v</span><span> </span><span>shop-db-data:/var/lib/postgresql/data</span><span> </span><span>\</span></div></div><div><div><div>4</div></div><div><span>  </span><span>postgres:16-alpine</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>shop-api</span><span> </span><span>--network</span><span> </span><span>shop-net</span><span> </span><span>\</span></div></div><div><div><div>6</div></div><div><span>  </span><span>-e</span><span> </span><span>DATABASE_URL=postgres://app:secret@shop-db:5432/shop</span><span> </span><span>\</span></div></div><div><div><div>7</div></div><div><span>  </span><span>-p</span><span> </span><span>3000:3000</span><span> </span><span>sakura/shop-api:1.0.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这套写法的痛点：</p><ul>
<li><strong>顺序靠人肉保证</strong>。数据库没起来，后端就连不上然后退出，得盯着日志等重试。</li>
<li><strong>参数散落在终端历史里</strong>。换机器、换同事就得重新拼，没法版本管理。</li>
<li><strong>状态不可复现</strong>。网络名、卷名随手起，删掉重来全散了。</li>
<li><strong>没有单一可信来源</strong>。改一个端口要在多处同步。</li>
</ul><p>Compose 把这一堆命令抽象成声明式 YAML：你只描述想要什么（有哪些服务、用什么镜像、依赖谁、挂什么卷），创建网络、按序启动、注入变量这些”怎么做”交给 <code>docker compose up</code>。好处是这份 YAML 能进 Git、能 review、能被 CI 复用——新人 clone 后一条命令就得到与你一致的环境。</p><p><strong>Compose 与 Kubernetes 的定位区别</strong></p><p>二者不是替代关系，是不同层次的工具。</p>

<table><thead><tr><th>维度</th><th>Docker Compose</th><th>Kubernetes</th></tr></thead><tbody><tr><td>定位</td><td>单机多容器编排</td><td>集群级容器编排</td></tr><tr><td>学习曲线</td><td>低，一个 YAML 文件</td><td>高，概念众多</td></tr><tr><td>部署目标</td><td>本地开发、CI、单机小型生产</td><td>多节点大规模生产</td></tr><tr><td>高可用</td><td>单机，无跨节点自愈</td><td>副本调度、自愈、滚动更新</td></tr><tr><td>伸缩</td><td><code>--scale</code> 有限，受单机约束</td><td>HPA 自动伸缩</td></tr><tr><td>网络</td><td>单主机 bridge</td><td>CNI 插件，跨节点组网</td></tr><tr><td>存储</td><td>命名卷 / 绑定挂载</td><td>PV / PVC / StorageClass</td></tr><tr><td>典型场景</td><td>本地开发、边缘、中小项目</td><td>云原生生产、微服务集群</td></tr></tbody></table><p>结论：开发环境无脑用 Compose，生产看规模。单机够用、没有多节点诉求的项目，Compose 上生产完全可行；等需要多副本、滚动发布、跨节点容灾时再迁 K8s，服务划分思路还能直接复用。</p></section>
<section><h2>2. 核心概念<a href="#2-核心概念"><span>#</span></a></h2><ul>
<li><strong>项目（project）</strong>：一组关联服务的集合，是 Compose 的操作单元。默认取当前目录名，可用顶层 <code>name</code> 或 <code>-p</code> 指定。项目名决定容器、网络、卷的命名前缀。</li>
<li><strong>服务（service）</strong>：一个抽象应用组件，描述用什么镜像、怎么配置、依赖谁。服务不是容器，而是容器的模板。</li>
<li><strong>容器（container）</strong>：服务实例化后的运行体。一个服务默认跑一个容器，<code>--scale</code> 可拉起多个。</li>
<li><strong>网络（network）</strong>：服务间通信的虚拟网络，同网服务可用服务名互访。</li>
<li><strong>卷（volume）</strong>：持久化数据的载体，容器删了数据还在，数据库靠它。</li>
</ul><p><strong>compose.yaml 与 docker-compose.yml 的历史</strong></p><p>早期 Compose 是独立 Python 工具，命令为 <code>docker-compose</code>（连字符），默认文件名 <code>docker-compose.yml</code>。Compose V2 用 Go 重写，以插件形式集成进 Docker CLI，命令变成 <code>docker compose</code>（空格）。</p><ul>
<li><strong>新项目用 <code>compose.yaml</code></strong>，这是官方当前首选。</li>
<li><code>docker-compose.yml</code> 仍被支持，属向后兼容，旧项目不必急着改名。</li>
<li>Compose 按 <code>compose.yaml</code>、<code>compose.yml</code>、<code>docker-compose.yaml</code>、<code>docker-compose.yml</code> 顺序查找，命中即用；同目录只保留一个。</li>
<li>命令统一用 V2 的 <code>docker compose</code>，旧的 <code>docker-compose</code> 二进制已进入维护模式。</li>
</ul></section>
<section><h2>3. compose.yaml 完整结构<a href="#3-composeyaml-完整结构"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>name</span><span>: </span><span>my-project</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>services</span><span>:</span></div></div><div><div><div>4</div></div><div><span>  </span><span>web</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>image</span><span>: </span><span>nginx:1.27-alpine</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>networks</span><span>:</span></div></div><div><div><div>8</div></div><div><span>  </span><span>frontend</span><span>:</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>volumes</span><span>:</span></div></div><div><div><div>11</div></div><div><span>  </span><span>db-data</span><span>:</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span>configs</span><span>:</span></div></div><div><div><div>14</div></div><div><span>  </span><span>nginx-conf</span><span>:</span></div></div><div><div><div>15</div></div><div><span>    </span><span>file</span><span>: </span><span>./nginx/nginx.conf</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span>secrets</span><span>:</span></div></div><div><div><div>18</div></div><div><span>  </span><span>db-password</span><span>:</span></div></div><div><div><div>19</div></div><div><span>    </span><span>file</span><span>: </span><span>./secrets/db-password.txt</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>关于 version 字段</strong></p><p>老配置顶部常写着 <code>version: "3.8"</code>。这个字段<strong>已经废弃（obsolete）</strong>，Compose V2 会忽略它，执行 <code>docker compose config</code> 时甚至打印一条警告。它的历史作用是区分文件格式版本，而 V2 CLI 已统一格式，所以新项目不要写。</p>

<table><thead><tr><th>字段</th><th>作用</th><th>常用度</th></tr></thead><tbody><tr><td><code>name</code></td><td>指定项目名，影响资源命名前缀</td><td>常用</td></tr><tr><td><code>services</code></td><td>定义所有服务，唯一必填字段</td><td>必填</td></tr><tr><td><code>networks</code></td><td>自定义网络，做隔离与分组</td><td>常用</td></tr><tr><td><code>volumes</code></td><td>命名卷，做数据持久化</td><td>常用</td></tr><tr><td><code>configs</code></td><td>注入非敏感配置文件</td><td>按需</td></tr><tr><td><code>secrets</code></td><td>注入敏感信息，不进环境变量</td><td>按需</td></tr><tr><td><code>include</code></td><td>引入其他 compose 文件做模块化</td><td>进阶</td></tr><tr><td><code>x-*</code></td><td>自定义扩展字段，配合 YAML 锚点</td><td>进阶</td></tr></tbody></table><p>省略 <code>networks</code> 时，Compose 自动创建 <code>项目名_default</code> 网络，把所有服务接进去。</p></section>
<section><h2>4. 服务定义详解<a href="#4-服务定义详解"><span>#</span></a></h2><p><strong>image 与 build</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>build</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>context</span><span>: </span><span>./backend</span><span>        </span><span># 构建上下文</span></div></div><div><div><div>5</div></div><div><span>      </span><span>dockerfile</span><span>: </span><span>Dockerfile</span><span>    </span><span># 默认即为 Dockerfile</span></div></div><div><div><div>6</div></div><div><span>      </span><span>args</span><span>:                     </span><span># 构建期变量，对应 Dockerfile 的 ARG</span></div></div><div><div><div>7</div></div><div><span>        </span><span>NODE_ENV</span><span>: </span><span>production</span></div></div><div><div><div>8</div></div><div><span>      </span><span>target</span><span>: </span><span>runtime</span><span>           </span><span># 多阶段构建指定阶段</span></div></div><div><div><div>9</div></div><div><span>    </span><span>image</span><span>: </span><span>sakura/shop-api:1.0.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>context</code> 是构建上下文，Dockerfile 的 <code>COPY</code> 只能引用其内部文件；<code>args</code> 是<strong>构建期</strong>变量，不进容器运行时；<code>target</code> 指定多阶段构建停在哪个阶段（生产用 runtime、开发用 development）。<code>image</code> 与 <code>build</code> 同现时，<code>image</code> 给构建产物打标签，方便 <code>docker compose push</code>。</p><p><strong>container_name</strong></p><p>指定容器名便于 <code>docker exec -it shop-web sh</code>，但<strong>代价是不能 <code>--scale</code></strong>（会命名冲突），多副本场景不要设它。</p><p><strong>ports 与 expose</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>web</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>nginx:1.27-alpine</span></div></div><div><div><div>4</div></div><div><span>    </span><span>ports</span><span>:</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>- </span><span>"8080:80"</span><span>              </span><span># 宿主机 8080 → 容器 80</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>- </span><span>"127.0.0.1:9000:9000"</span><span>  </span><span># 只绑本机回环</span></div></div><div><div><div>7</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>8</div></div><div><span>    </span><span>image</span><span>: </span><span>sakura/shop-api:1.0.0</span></div></div><div><div><div>9</div></div><div><span>    </span><span>expose</span><span>:</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>- </span><span>"3000"</span><span>                 </span><span># 仅内部网络可见，不映射宿主机</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>ports</code> 映射到宿主机；<code>expose</code> 只声明端口供同网服务访问。只该被内部调用的后端用 <code>expose</code> 更安全。</p><p><strong>environment 与 env_file</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>NODE_ENV</span><span>: </span><span>production</span></div></div><div><div><div>5</div></div><div><span>      </span><span>DATABASE_URL</span><span>: </span><span>postgres://app:${POSTGRES_PASSWORD:-secret}@db:5432/shop</span></div></div><div><div><div>6</div></div><div><span>    </span><span>env_file</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>- </span><span>.env</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>environment</code> 直接写键值对并支持插值；<code>env_file</code> 从文件批量读取。列表形式 <code>- KEY=VALUE</code> 与映射形式 <code>KEY: VALUE</code> 等价。</p><p><strong>command 与 entrypoint</strong></p><p><code>command</code> 覆盖镜像 <code>CMD</code>（默认参数），<code>entrypoint</code> 覆盖 <code>ENTRYPOINT</code>（可执行入口），最终命令由二者拼接。例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>cache</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>redis:7-alpine</span></div></div><div><div><div>4</div></div><div><span>    </span><span>command</span><span>: [</span><span>"redis-server"</span><span>, </span><span>"--appendonly"</span><span>, </span><span>"yes"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>推荐列表（exec form）而非字符串，避免 shell 解析带来的信号处理问题。</p><p><strong>restart 策略对比</strong></p>

<table><thead><tr><th>策略</th><th>行为</th><th>适用</th></tr></thead><tbody><tr><td><code>no</code></td><td>不自动重启（默认）</td><td>一次性任务、调试</td></tr><tr><td><code>always</code></td><td>无论退出码都重启</td><td>长驻服务，但手动 stop 也会被拉起</td></tr><tr><td><code>on-failure</code></td><td>仅非 0 退出时重启</td><td>偶发失败的任务</td></tr><tr><td><code>on-failure:3</code></td><td>非 0 退出最多重启 3 次</td><td>限制重启，避免死循环</td></tr><tr><td><code>unless-stopped</code></td><td>始终重启，除非被手动 stop</td><td><strong>生产最常用</strong></td></tr></tbody></table></section>
<section><h2>5. 依赖与启动顺序<a href="#5-依赖与启动顺序"><span>#</span></a></h2><p><strong>depends_on 的两种语法</strong></p><p>短语法只表达启动顺序：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>depends_on</span><span>:</span></div></div><div><div><div>4</div></div><div><span><span>      </span></span><span>- </span><span>db</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>- </span><span>cache</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>长语法可带条件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>depends_on</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>db</span><span>:</span></div></div><div><div><div>5</div></div><div><span>        </span><span>condition</span><span>: </span><span>service_healthy</span></div></div><div><div><div>6</div></div><div><span>        </span><span>restart</span><span>: </span><span>true</span></div></div><div><div><div>7</div></div><div><span>      </span><span>cache</span><span>:</span></div></div><div><div><div>8</div></div><div><span>        </span><span>condition</span><span>: </span><span>service_started</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>condition</th><th>含义</th></tr></thead><tbody><tr><td><code>service_started</code></td><td>容器<strong>已启动</strong>（默认行为，不等就绪）</td></tr><tr><td><code>service_healthy</code></td><td>通过健康检查，<strong>真正就绪</strong></td></tr><tr><td><code>service_completed_successfully</code></td><td>容器<strong>成功退出</strong>（退出码 0），适合迁移任务</td></tr></tbody></table><p><strong>为什么 depends_on 不等于就绪等待</strong></p><p>这是最常见的坑。默认 <code>depends_on</code> 只保证<strong>启动顺序</strong>：先 <code>docker run</code> db，再轮到 api。但”已启动”和”服务可用”是两回事——PostgreSQL 启动后还要初始化数据目录、启动进程、监听 5432，可能耗时数秒。这期间 api 去连库会直接 <code>connection refused</code> 退出，于是出现经典现象：<strong>第一次 <code>up</code> 挂，再 <code>up</code> 一次就好</strong>。正解是 depends_on + healthcheck 组合：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>db</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>postgres:16-alpine</span></div></div><div><div><div>4</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>5</div></div><div><span>      </span><span>POSTGRES_USER</span><span>: </span><span>app</span></div></div><div><div><div>6</div></div><div><span>      </span><span>POSTGRES_PASSWORD</span><span>: </span><span>secret</span></div></div><div><div><div>7</div></div><div><span>      </span><span>POSTGRES_DB</span><span>: </span><span>shop</span></div></div><div><div><div>8</div></div><div><span>    </span><span>healthcheck</span><span>:</span></div></div><div><div><div>9</div></div><div><span>      </span><span>test</span><span>: [</span><span>"CMD-SHELL"</span><span>, </span><span>"pg_isready -U app -d shop"</span><span>]</span></div></div><div><div><div>10</div></div><div><span>      </span><span>interval</span><span>: </span><span>10s</span></div></div><div><div><div>11</div></div><div><span>      </span><span>timeout</span><span>: </span><span>5s</span></div></div><div><div><div>12</div></div><div><span>      </span><span>retries</span><span>: </span><span>5</span></div></div><div><div><div>13</div></div><div><span>      </span><span>start_period</span><span>: </span><span>30s</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>16</div></div><div><span>    </span><span>image</span><span>: </span><span>sakura/shop-api:1.0.0</span></div></div><div><div><div>17</div></div><div><span>    </span><span>depends_on</span><span>:</span></div></div><div><div><div>18</div></div><div><span>      </span><span>db</span><span>:</span></div></div><div><div><div>19</div></div><div><span>        </span><span>condition</span><span>: </span><span>service_healthy</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>api 会等到 db 健康检查通过才启动，从根上消除竞态。</p><p><strong>healthcheck 参数与写法</strong></p>

<table><thead><tr><th>参数</th><th>含义</th><th>默认值</th></tr></thead><tbody><tr><td><code>test</code></td><td>检查命令</td><td>无（不写即无检查）</td></tr><tr><td><code>interval</code></td><td>检查间隔</td><td><code>30s</code></td></tr><tr><td><code>timeout</code></td><td>单次超时</td><td><code>30s</code></td></tr><tr><td><code>retries</code></td><td>连续失败多少次判 unhealthy</td><td><code>3</code></td></tr><tr><td><code>start_period</code></td><td>启动宽限期，期间失败不计入 retries</td><td><code>0s</code></td></tr></tbody></table><p><code>test</code> 有两种写法：<code>["CMD", "curl", "-f", "..."]</code> 直接执行命令，不经 shell；<code>["CMD-SHELL", "pg_isready -U app &amp;&amp; ..."]</code> 交给 shell，支持管道与 <code>&amp;&amp;</code>。<code>start_period</code> 很关键，慢启动的 Java 应用通常给 30s 到 60s，避免没起来就被判 unhealthy 反复重启。</p></section>
<section><h2>6. 网络<a href="#6-网络"><span>#</span></a></h2><p>不定义 <code>networks</code> 时，Compose 自动建一个 <code>项目名_default</code> bridge 网络，全部服务接入，服务间用<strong>服务名</strong>互访：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>DATABASE_URL</span><span>: </span><span>postgres://app:secret@db:5432/shop</span></div></div><div><div><div>5</div></div><div><span>      </span><span>REDIS_URL</span><span>: </span><span>redis://cache:6379</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这就是 Compose 最省心之处——<strong>服务名即 DNS 名</strong>，内置 DNS 把服务名解析到对应容器 IP，重启换 IP 也不影响。</p><p>默认网络是扁平的，所有服务互相可见。生产里通常分层隔离：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>web</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>networks</span><span>: [</span><span>frontend</span><span>]</span></div></div><div><div><div>4</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>networks</span><span>: [</span><span>frontend</span><span>, </span><span>backend</span><span>]</span></div></div><div><div><div>6</div></div><div><span>  </span><span>db</span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>networks</span><span>: [</span><span>backend</span><span>]</span></div></div><div><div><div>8</div></div><div><span>  </span><span>cache</span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>networks</span><span>: [</span><span>backend</span><span>]</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>networks</span><span>:</span></div></div><div><div><div>12</div></div><div><span>  </span><span>frontend</span><span>:</span></div></div><div><div><div>13</div></div><div><span>  </span><span>backend</span><span>:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样 web 只能访问 api，碰不到 db 和 cache；db 与 cache 完全不可从外部网络访问；api 是唯一桥接点，这是最小权限原则在网络层的落地。容器访问宿主机则通过 <code>extra_hosts</code> 加上 <code>host.docker.internal:host-gateway</code>。</p></section>
<section><h2>7. 数据持久化<a href="#7-数据持久化"><span>#</span></a></h2><p>容器删除后可写层数据即丢，要 survive 重建必须落到容器之外：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>db</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>postgres:16-alpine</span></div></div><div><div><div>4</div></div><div><span>    </span><span>volumes</span><span>:</span></div></div><div><div><div>5</div></div><div><span><span>      </span></span><span>- </span><span>db-data:/var/lib/postgresql/data</span><span>      </span><span># named volume，Docker 管理</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>- </span><span>./init:/docker-entrypoint-initdb.d:ro</span><span> </span><span># bind mount，宿主机路径，只读</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>- </span><span>/var/lib/postgresql/wal</span><span>               </span><span># 匿名卷，自动命名</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>volumes</span><span>:</span></div></div><div><div><div>10</div></div><div><span>  </span><span>db-data</span><span>:</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>类型</th><th>写法</th><th>数据位置</th><th>适用</th></tr></thead><tbody><tr><td>named volume</td><td><code>db-data:/path</code></td><td>Docker 管理目录</td><td>数据库、需 Docker 管理的数据</td></tr><tr><td>bind mount</td><td><code>./data:/path</code></td><td>宿主机指定路径</td><td>代码热重载、配置、日志</td></tr><tr><td>匿名卷</td><td><code>/path</code></td><td>Docker 自动命名</td><td>临时屏蔽镜像内目录</td></tr></tbody></table><p>生产里<strong>数据库一律用 named volume</strong>：性能更好，跨平台一致，备份有标准工具。bind mount 更适合挂源码和宿主机已有目录。数据库持久化写法如下，其中 <code>${POSTGRES_PASSWORD:?...}</code> 表示<strong>变量必须提供，否则直接报错退出</strong>，防止默认密码上线：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>db</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>postgres:16-alpine</span></div></div><div><div><div>4</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>5</div></div><div><span>      </span><span>POSTGRES_PASSWORD</span><span>: </span><span>${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}</span></div></div><div><div><div>6</div></div><div><span>    </span><span>volumes</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>- </span><span>db-data:/var/lib/postgresql/data</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>volumes</span><span>:</span></div></div><div><div><div>10</div></div><div><span>  </span><span>db-data</span><span>:</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>卷的备份与迁移</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 备份：把 db-data 内容打包到当前目录</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>-v</span><span> </span><span>shop_db-data:/data</span><span> </span><span>-v</span><span> </span><span>"</span><span>$PWD</span><span>":/backup</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>alpine</span><span> </span><span>tar</span><span> </span><span>czf</span><span> </span><span>/backup/db-data-backup.tar.gz</span><span> </span><span>-C</span><span> </span><span>/data</span><span> </span><span>.</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 恢复：解包到一个新卷</span></div></div><div><div><div>6</div></div><div><span>docker</span><span> </span><span>volume</span><span> </span><span>create</span><span> </span><span>shop_db-data-restore</span></div></div><div><div><div>7</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>-v</span><span> </span><span>shop_db-data-restore:/data</span><span> </span><span>-v</span><span> </span><span>"</span><span>$PWD</span><span>":/backup</span><span> </span><span>\</span></div></div><div><div><div>8</div></div><div><span>  </span><span>alpine</span><span> </span><span>tar</span><span> </span><span>xzf</span><span> </span><span>/backup/db-data-backup.tar.gz</span><span> </span><span>-C</span><span> </span><span>/data</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>生产更推荐数据库自带的逻辑备份（<code>pg_dump</code>、<code>mysqldump</code>），格式更稳、可跨版本迁移；卷级物理备份只适合整机迁移或快速回滚。</p></section>
<section><h2>8. 环境变量与配置管理<a href="#8-环境变量与配置管理"><span>#</span></a></h2><p>Compose 默认读取项目根目录的 <code>.env</code> 做变量插值。它和服务的 <code>env_file</code> 是两回事：<code>.env</code> 给 Compose 自己用、参与 <code>${VAR}</code> 插值；<code>env_file</code> 把变量注入到<strong>某个服务的容器</strong>里。</p><div><figure><figcaption><span>.env</span></figcaption><pre><code><div><div><div>1</div></div><div><span>POSTGRES_PASSWORD</span><span>=</span><span>change-me-in-production</span></div></div><div><div><div>2</div></div><div><span>APP_PORT</span><span>=</span><span>8080</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>语法</th><th>含义</th></tr></thead><tbody><tr><td><code>${VAR}</code></td><td>直接取值，未定义则替换为空</td></tr><tr><td><code>${VAR:-default}</code></td><td>未定义或为空时用默认值</td></tr><tr><td><code>${VAR-default}</code></td><td>未定义时用默认值（空串也算已定义）</td></tr><tr><td><code>${VAR:?err}</code></td><td>未定义或为空时<strong>报错退出</strong>，适合必填项</td></tr><tr><td><code>${VAR:+alt}</code></td><td>已定义时用 alt</td></tr><tr><td><code>$$VAR</code></td><td>转义，输出字面量 <code>$VAR</code></td></tr></tbody></table><p><strong>多环境与 -f 叠加</strong></p><p>不同环境的差异不必复制整份文件，Compose 默认会自动读取 <code>compose.override.yaml</code> 叠加到 <code>compose.yaml</code> 上：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># compose.override.yaml —— 开发专用覆盖</span></div></div><div><div><div>2</div></div><div><span>services</span><span>:</span></div></div><div><div><div>3</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>4</div></div><div><span>    </span><span>build</span><span>:</span></div></div><div><div><div>5</div></div><div><span>      </span><span>target</span><span>: </span><span>development</span></div></div><div><div><div>6</div></div><div><span>    </span><span>command</span><span>: </span><span>npm run dev</span></div></div><div><div><div>7</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>8</div></div><div><span>      </span><span>NODE_ENV</span><span>: </span><span>development</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可显式叠加任意文件：<code>docker compose -f compose.yaml -f compose.prod.yaml up -d</code>。规则是<strong>后者覆盖前者的同名标量字段，列表字段合并</strong>，所以生产覆盖文件只写差异项。验证合并结果用 <code>docker compose -f ... config</code>。</p><p><strong>不要把密钥写进仓库</strong></p><ul>
<li><code>.env</code> 只放非敏感默认值，真实密钥由 CI 变量或部署环境注入。</li>
<li><code>.gitignore</code> 加上 <code>.env</code>、<code>*.local</code>、<code>secrets/</code>，并提供 <code>.env.example</code> 入库作模板。</li>
<li>极敏感凭据用顶层 <code>secrets</code> 以文件挂载方式注入，而非环境变量——环境变量会出现在 <code>docker inspect</code> 和子进程环境里，容易泄漏。</li>
</ul></section>
<section><h2>9. 完整实战：前后端全栈一键启动<a href="#9-完整实战前后端全栈一键启动"><span>#</span></a></h2><p>一个可直接运行的全栈项目：React 前端（构建为静态资源交给 Nginx）+ Node 后端 + PostgreSQL + Redis。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>shop/</span></div></div><div><div><div>2</div></div><div><span>├── compose.yaml</span></div></div><div><div><div>3</div></div><div><span>├── .env</span></div></div><div><div><div>4</div></div><div><span>├── frontend/          # React + Dockerfile</span></div></div><div><div><div>5</div></div><div><span>├── backend/           # Node + Dockerfile</span></div></div><div><div><div>6</div></div><div><span>└── init/</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>└── 01-schema.sql</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>name</span><span>: </span><span>shop</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>services</span><span>:</span></div></div><div><div><div>4</div></div><div><span>  </span><span>web</span><span>:</span></div></div><div><div><div>5</div></div><div><span>    </span><span>build</span><span>:</span></div></div><div><div><div>6</div></div><div><span>      </span><span>context</span><span>: </span><span>./frontend</span></div></div><div><div><div>7</div></div><div><span>      </span><span>args</span><span>:</span></div></div><div><div><div>8</div></div><div><span>        </span><span>NODE_ENV</span><span>: </span><span>production</span></div></div><div><div><div>9</div></div><div><span>    </span><span>image</span><span>: </span><span>sakura/shop-web:1.0.0</span></div></div><div><div><div>10</div></div><div><span>    </span><span>ports</span><span>:</span></div></div><div><div><div>11</div></div><div><span><span>      </span></span><span>- </span><span>"${APP_PORT:-8080}:80"</span></div></div><div><div><div>12</div></div><div><span>    </span><span>depends_on</span><span>:</span></div></div><div><div><div>13</div></div><div><span>      </span><span>api</span><span>:</span></div></div><div><div><div>14</div></div><div><span>        </span><span>condition</span><span>: </span><span>service_healthy</span></div></div><div><div><div>15</div></div><div><span>    </span><span>networks</span><span>: [</span><span>frontend</span><span>]</span></div></div><div><div><div>16</div></div><div><span>    </span><span>restart</span><span>: </span><span>unless-stopped</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>19</div></div><div><span>    </span><span>build</span><span>:</span></div></div><div><div><div>20</div></div><div><span>      </span><span>context</span><span>: </span><span>./backend</span></div></div><div><div><div>21</div></div><div><span>      </span><span>target</span><span>: </span><span>runtime</span></div></div><div><div><div>22</div></div><div><span>    </span><span>image</span><span>: </span><span>sakura/shop-api:1.0.0</span></div></div><div><div><div>23</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>24</div></div><div><span>      </span><span>NODE_ENV</span><span>: </span><span>production</span></div></div><div><div><div>25</div></div><div><span>      </span><span>DATABASE_URL</span><span>: </span><span>postgres://app:${POSTGRES_PASSWORD:?required}@db:5432/shop</span></div></div><div><div><div>26</div></div><div><span>      </span><span>REDIS_URL</span><span>: </span><span>redis://cache:6379</span></div></div><div><div><div>27</div></div><div><span>    </span><span>expose</span><span>:</span></div></div><div><div><div>28</div></div><div><span><span>      </span></span><span>- </span><span>"3000"</span></div></div><div><div><div>29</div></div><div><span>    </span><span>depends_on</span><span>:</span></div></div><div><div><div>30</div></div><div><span>      </span><span>db</span><span>:</span></div></div><div><div><div>31</div></div><div><span>        </span><span>condition</span><span>: </span><span>service_healthy</span></div></div><div><div><div>32</div></div><div><span>      </span><span>cache</span><span>:</span></div></div><div><div><div>33</div></div><div><span>        </span><span>condition</span><span>: </span><span>service_healthy</span></div></div><div><div><div>34</div></div><div><span>    </span><span>healthcheck</span><span>:</span></div></div><div><div><div>35</div></div><div><span>      </span><span>test</span><span>: [</span><span>"CMD"</span><span>, </span><span>"curl"</span><span>, </span><span>"-f"</span><span>, </span><span>"http://localhost:3000/health"</span><span>]</span></div></div><div><div><div>36</div></div><div><span>      </span><span>interval</span><span>: </span><span>10s</span></div></div><div><div><div>37</div></div><div><span>      </span><span>timeout</span><span>: </span><span>3s</span></div></div><div><div><div>38</div></div><div><span>      </span><span>retries</span><span>: </span><span>5</span></div></div><div><div><div>39</div></div><div><span>      </span><span>start_period</span><span>: </span><span>20s</span></div></div><div><div><div>40</div></div><div><span>    </span><span>networks</span><span>: [</span><span>frontend</span><span>, </span><span>backend</span><span>]</span></div></div><div><div><div>41</div></div><div><span>    </span><span>restart</span><span>: </span><span>unless-stopped</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>  </span><span>db</span><span>:</span></div></div><div><div><div>44</div></div><div><span>    </span><span>image</span><span>: </span><span>postgres:16-alpine</span></div></div><div><div><div>45</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>46</div></div><div><span>      </span><span>POSTGRES_USER</span><span>: </span><span>app</span></div></div><div><div><div>47</div></div><div><span>      </span><span>POSTGRES_PASSWORD</span><span>: </span><span>${POSTGRES_PASSWORD:?required}</span></div></div><div><div><div>48</div></div><div><span>      </span><span>POSTGRES_DB</span><span>: </span><span>shop</span></div></div><div><div><div>49</div></div><div><span>    </span><span>volumes</span><span>:</span></div></div><div><div><div>50</div></div><div><span><span>      </span></span><span>- </span><span>db-data:/var/lib/postgresql/data</span></div></div><div><div><div>51</div></div><div><span><span>      </span></span><span>- </span><span>./init:/docker-entrypoint-initdb.d:ro</span></div></div><div><div><div>52</div></div><div><span>    </span><span>healthcheck</span><span>:</span></div></div><div><div><div>53</div></div><div><span>      </span><span>test</span><span>: [</span><span>"CMD-SHELL"</span><span>, </span><span>"pg_isready -U app -d shop"</span><span>]</span></div></div><div><div><div>54</div></div><div><span>      </span><span>interval</span><span>: </span><span>10s</span></div></div><div><div><div>55</div></div><div><span>      </span><span>timeout</span><span>: </span><span>5s</span></div></div><div><div><div>56</div></div><div><span>      </span><span>retries</span><span>: </span><span>5</span></div></div><div><div><div>57</div></div><div><span>      </span><span>start_period</span><span>: </span><span>20s</span></div></div><div><div><div>58</div></div><div><span>    </span><span>networks</span><span>: [</span><span>backend</span><span>]</span></div></div><div><div><div>59</div></div><div><span>    </span><span>restart</span><span>: </span><span>unless-stopped</span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span>  </span><span>cache</span><span>:</span></div></div><div><div><div>62</div></div><div><span>    </span><span>image</span><span>: </span><span>redis:7-alpine</span></div></div><div><div><div>63</div></div><div><span>    </span><span>command</span><span>: [</span><span>"redis-server"</span><span>, </span><span>"--appendonly"</span><span>, </span><span>"yes"</span><span>]</span></div></div><div><div><div>64</div></div><div><span>    </span><span>volumes</span><span>:</span></div></div><div><div><div>65</div></div><div><span><span>      </span></span><span>- </span><span>cache-data:/data</span></div></div><div><div><div>66</div></div><div><span>    </span><span>healthcheck</span><span>:</span></div></div><div><div><div>67</div></div><div><span>      </span><span>test</span><span>: [</span><span>"CMD"</span><span>, </span><span>"redis-cli"</span><span>, </span><span>"ping"</span><span>]</span></div></div><div><div><div>68</div></div><div><span>      </span><span>interval</span><span>: </span><span>10s</span></div></div><div><div><div>69</div></div><div><span>      </span><span>timeout</span><span>: </span><span>3s</span></div></div><div><div><div>70</div></div><div><span>      </span><span>retries</span><span>: </span><span>5</span></div></div><div><div><div>71</div></div><div><span>    </span><span>networks</span><span>: [</span><span>backend</span><span>]</span></div></div><div><div><div>72</div></div><div><span>    </span><span>restart</span><span>: </span><span>unless-stopped</span></div></div><div><div><div>73</div></div><div>
</div></div><div><div><div>74</div></div><div><span>networks</span><span>:</span></div></div><div><div><div>75</div></div><div><span>  </span><span>frontend</span><span>:</span></div></div><div><div><div>76</div></div><div><span>  </span><span>backend</span><span>:</span></div></div><div><div><div>77</div></div><div>
</div></div><div><div><div>78</div></div><div><span>volumes</span><span>:</span></div></div><div><div><div>79</div></div><div><span>  </span><span>db-data</span><span>:</span></div></div><div><div><div>80</div></div><div><span>  </span><span>cache-data</span><span>:</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>配套的 <code>.env</code> 只需 <code>POSTGRES_PASSWORD=change-me-in-production</code> 与 <code>APP_PORT=8080</code>。逐个服务解释：</p><ul>
<li><strong>web</strong>：从 <code>frontend/Dockerfile</code> 多阶段构建，产物由容器内 Nginx 托管，映射到宿主机 <code>APP_PORT</code>。只接入 <code>frontend</code> 网络，碰不到数据库；依赖 api 健康检查通过才启动，避免一打开就 502。</li>
<li><strong>api</strong>：<code>target: runtime</code> 使用生产阶段。<code>DATABASE_URL</code>、<code>REDIS_URL</code> 直接写服务名 <code>db</code>、<code>cache</code>，靠内置 DNS 解析。用 <code>expose</code> 而非 <code>ports</code>，因为不该被公网直连；健康检查命中 <code>/health</code>。</li>
<li><strong>db</strong>：数据落在 named volume <code>db-data</code>，容器重建不丢；<code>init/</code> 挂进初始化目录，<strong>首次启动</strong>自动执行 <code>01-schema.sql</code> 建表；<code>pg_isready</code> 保证真正就绪。</li>
<li><strong>cache</strong>：Redis 开 AOF 持久化，数据落 <code>cache-data</code> 卷，<code>redis-cli ping</code> 返回 PONG 即健康。</li>
<li><strong>networks</strong>：<code>frontend</code> 面向外部，<code>backend</code> 是数据层，通过 api 桥接，最小暴露。</li>
<li><strong>volumes</strong>：两个命名卷承载有状态数据，独立于容器生命周期。</li>
</ul><p>启动与验证：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>up</span><span> </span><span>-d</span><span> </span><span>--build</span><span>      </span><span># 构建并后台启动</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>ps</span><span>                 </span><span># 确认均为 healthy</span></div></div><div><div><div>3</div></div><div><span>curl</span><span> </span><span>-I</span><span> </span><span>http://localhost:8080</span><span>     </span><span># 验证前端</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>compose</span><span> </span><span>exec</span><span> </span><span>api</span><span> </span><span>curl</span><span> </span><span>-f</span><span> </span><span>http://localhost:3000/health</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>10. 常用命令<a href="#10-常用命令"><span>#</span></a></h2>

<table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>docker compose up -d</code></td><td>后台启动全部服务</td></tr><tr><td><code>docker compose up -d --build</code></td><td>强制重建镜像后启动</td></tr><tr><td><code>docker compose down</code></td><td>停止并删除容器、网络</td></tr><tr><td><code>docker compose down -v</code></td><td><strong>同时删除命名卷</strong>（数据一并清空）</td></tr><tr><td><code>docker compose ps</code></td><td>列出服务状态</td></tr><tr><td><code>docker compose logs -f api</code></td><td>实时跟踪某服务日志</td></tr><tr><td><code>docker compose logs --tail=100 web</code></td><td>只看最近 100 行</td></tr><tr><td><code>docker compose exec api sh</code></td><td>在运行中的容器开会话</td></tr><tr><td><code>docker compose run --rm api npm test</code></td><td>一次性容器跑命令，跑完即删</td></tr><tr><td><code>docker compose build --no-cache</code></td><td>不用缓存重建</td></tr><tr><td><code>docker compose pull</code></td><td>拉取最新镜像</td></tr><tr><td><code>docker compose restart api</code></td><td>重启指定服务</td></tr><tr><td><code>docker compose config</code></td><td>输出合并后的最终配置</td></tr><tr><td><code>docker compose top</code></td><td>查看服务内进程</td></tr><tr><td><code>docker compose stats</code></td><td>实时资源占用</td></tr></tbody></table><p>几个必须提醒的点：</p><ul>
<li><strong><code>down -v</code> 会删数据卷</strong>，本地清环境很方便，生产上等于毁库，执行前务必确认。</li>
<li><code>up -d --build</code> 与 <code>up -d</code> 的区别是是否重建镜像。改了 Dockerfile 或构建上下文代码却没带 <code>--build</code>，跑的还是旧镜像。</li>
<li><code>logs -f</code> 配 <code>--tail</code> 更好用：<code>--tail=200 -f</code> 既看历史又跟新日志。</li>
<li><code>run</code> 与 <code>exec</code> 的区别：<code>run</code> 启动<strong>新容器</strong>执行（默认不映射端口），<code>exec</code> 在<strong>已运行的容器</strong>里执行；一次性任务用 <code>run --rm</code>。</li>
</ul></section>
<section><h2>11. 开发与生产的差异<a href="#11-开发与生产的差异"><span>#</span></a></h2><p>开发要改代码立刻生效，用绑定挂载 + 开发模式：</p><div><figure><figcaption><span>compose.override.yaml</span></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>build</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>target</span><span>: </span><span>development</span></div></div><div><div><div>5</div></div><div><span>    </span><span>command</span><span>: </span><span>npm run dev</span></div></div><div><div><div>6</div></div><div><span>    </span><span>volumes</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>- </span><span>./backend:/app</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>- </span><span>/app/node_modules</span><span>   </span><span># 匿名卷遮蔽，防止宿主机空目录覆盖容器内依赖</span></div></div><div><div><div>9</div></div><div><span>    </span><span>ports</span><span>:</span></div></div><div><div><div>10</div></div><div><span><span>      </span></span><span>- </span><span>"3000:3000"</span></div></div><div><div><div>11</div></div><div><span>    </span><span>environment</span><span>:</span></div></div><div><div><div>12</div></div><div><span>      </span><span>NODE_ENV</span><span>: </span><span>development</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>- /app/node_modules</code> 这一行别漏：它用匿名卷遮蔽容器内目录，防止宿主机空目录或不同平台安装的二进制覆盖容器依赖，否则经常报模块找不到。</p><p>生产则用构建好的<strong>不可变镜像</strong>，把源码挂进去是反模式；资源限制、日志轮转等差异项写进 <code>compose.prod.yaml</code>，用 <code>-f</code> 叠加。</p><p>调试工具、一次性任务不该在正常启动时被拉起，用 <code>profiles</code> 打标签，默认 <code>up</code> 时不会启动：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>adminer</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>adminer:4</span></div></div><div><div><div>4</div></div><div><span>    </span><span>profiles</span><span>: [</span><span>"debug"</span><span>]</span></div></div><div><div><div>5</div></div><div><span>    </span><span>ports</span><span>:</span></div></div><div><div><div>6</div></div><div><span><span>      </span></span><span>- </span><span>"8081:8080"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>激活用 <code>docker compose --profile debug up -d</code> 或 <code>docker compose --profile tools run --rm migrate</code>。一份文件同时服务开发、调试、运维多种场景，比维护多份文件清爽。</p></section>
<section><h2>12. 生产注意事项<a href="#12-生产注意事项"><span>#</span></a></h2><p><strong>资源限制</strong>：Compose V2 中 <code>deploy.resources</code> 下的 <code>limits</code> 会<strong>直接生效</strong>（不需要 swarm 模式）。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>deploy</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>resources</span><span>:</span></div></div><div><div><div>5</div></div><div><span>        </span><span>limits</span><span>:</span></div></div><div><div><div>6</div></div><div><span>          </span><span>cpus</span><span>: </span><span>"1.0"</span></div></div><div><div><div>7</div></div><div><span>          </span><span>memory</span><span>: </span><span>512M</span></div></div><div><div><div>8</div></div><div><span>        </span><span>reservations</span><span>:</span></div></div><div><div><div>9</div></div><div><span>          </span><span>memory</span><span>: </span><span>256M</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>老写法 <code>mem_limit</code>、<code>cpus</code> 仍可用，但 <code>deploy.resources</code> 是推荐格式，也便于日后迁移 K8s。单机下 <code>reservations</code> 意义有限，主要靠 <code>limits</code> 防内存泄漏拖垮整机。</p><p><strong>日志驱动与大小限制</strong>：默认 <code>json-file</code> 驱动<strong>不会自动清理</strong>，日志会一直涨到撑爆磁盘，生产必须加限制。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>api</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>logging</span><span>:</span></div></div><div><div><div>4</div></div><div><span>      </span><span>driver</span><span>: </span><span>json-file</span></div></div><div><div><div>5</div></div><div><span>      </span><span>options</span><span>:</span></div></div><div><div><div>6</div></div><div><span>        </span><span>max-size</span><span>: </span><span>"10m"</span></div></div><div><div><div>7</div></div><div><span>        </span><span>max-file</span><span>: </span><span>"3"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>含义是单文件最大 10MB、最多保留 3 个，滚动覆盖。也可用默认就压缩轮转的 <code>local</code> 驱动，或把日志发往 ELK、Loki 等集中式系统。</p><p><strong>重启策略与镜像版本固定</strong>：生产统一 <code>restart: unless-stopped</code>；<strong>绝不使用 <code>latest</code> 标签</strong>——<code>postgres:latest</code> 某天 <code>pull</code> 下来一个不兼容新版本就可能炸库，固定到 <code>postgres:16.3-alpine</code> 这样具体版本；自建镜像用语义化 tag（<code>sakura/shop-api:1.0.0</code>）。</p><p><strong>健康检查必配</strong>：每个长驻服务都该有 <code>healthcheck</code>，它是 <code>depends_on</code> 的条件来源，也是外部监控判断存活的依据。没有健康检查的服务崩溃可能悄无声息地持续几小时。</p></section>
<section><h2>13. 从 Compose 到 Kubernetes<a href="#13-从-compose-到-kubernetes"><span>#</span></a></h2><p><strong>什么时候该升级</strong></p><ul>
<li>单机资源撑不住，需要<strong>多节点</strong>横向扩展。</li>
<li>需要<strong>多副本 + 负载均衡</strong>，而不只是单实例。</li>
<li>需要<strong>滚动更新、灰度发布、自动回滚</strong>。</li>
<li>需要<strong>自动扩缩容</strong>（按 CPU/内存/QPS 增减副本）。</li>
<li>需要<strong>跨节点容灾</strong>，单机挂了业务还能跑。</li>
<li>团队规模上来，需要<strong>命名空间隔离与 RBAC</strong>。</li>
</ul><p>如果只是想要一个能跑、够用的部署，Compose 完全没必要换，别为时髦给自己加运维负担。</p>

<table><thead><tr><th>Docker Compose</th><th>Kubernetes</th><th>说明</th></tr></thead><tbody><tr><td>service</td><td>Deployment</td><td>服务定义 → 副本控制器</td></tr><tr><td>service 单实例</td><td>Pod</td><td>最小调度单元</td></tr><tr><td>ports</td><td>Service / Ingress</td><td>端口暴露与路由</td></tr><tr><td>networks</td><td>Service / NetworkPolicy</td><td>服务发现与网络隔离</td></tr><tr><td>named volume</td><td>PersistentVolumeClaim</td><td>持久化存储声明</td></tr><tr><td>bind mount</td><td>hostPath / ConfigMap</td><td>宿主挂载或配置注入</td></tr><tr><td>environment / env_file</td><td>ConfigMap / Secret</td><td>配置与密钥</td></tr><tr><td>healthcheck</td><td>livenessProbe / readinessProbe</td><td>存活与就绪探针</td></tr><tr><td>depends_on</td><td>initContainers</td><td>启动依赖</td></tr><tr><td>profiles</td><td>namespace / Helm values</td><td>环境与场景隔离</td></tr><tr><td>restart</td><td>restartPolicy（Pod 级）</td><td>重启策略</td></tr><tr><td>deploy.resources</td><td>resources.requests/limits</td><td>资源配额</td></tr><tr><td><code>--scale</code></td><td>replicas / HPA</td><td>副本数</td></tr></tbody></table><p>映射相当直观：服务怎么切分、网络怎么隔离、哪些有状态，这些设计在 Compose 阶段想清楚，迁移时基本照搬。所以用 Compose 认真组织项目，本身就是给未来上 K8s 铺路。</p></section>
<section><h2>14. 常见问题排查<a href="#14-常见问题排查"><span>#</span></a></h2><p><strong>容器起来了但连不上数据库</strong>：症状是后端启动后立即退出，日志里 <code>ECONNREFUSED</code> 或 <code>could not connect to server</code>。</p><ul>
<li>只写了 <code>depends_on: [db]</code> 却没配 <code>condition: service_healthy</code>，api 在 db 就绪前就去连，加上健康检查条件即可。</li>
<li>连接地址写错。容器里<strong>不能用 <code>localhost</code> 连数据库</strong>——那是 api 容器自己，必须用服务名 <code>db:5432</code>。</li>
<li>两个服务不在同一网络，检查各自 <code>networks</code> 是否有交集。</li>
</ul><p><strong>端口冲突</strong>：报错 <code>bind: address already in use</code>，说明宿主机端口被占用，用 <code>docker ps --format '{{.Names}} {{.Ports}}' | grep 8080</code> 定位。解决方式是换宿主机端口（<code>"8081:80"</code>）或停掉占用进程。冲突只看<strong>宿主机侧</strong>端口，容器内端口可以重复。</p><p><strong>卷权限问题</strong>：PostgreSQL 等应用报 <code>Permission denied</code>，通常是宿主路径（bind mount）属主与容器内用户不匹配。named volume 一般没这问题，是首选；bind mount 场景可用 <code>user:</code> 对齐 UID/GID，或调整宿主机目录权限。</p><p><strong>容器内 DNS 解析失败</strong>：报 <code>bad address</code> 或 <code>Name or service not known</code>。检查服务是否真的在同一自定义网络；检查服务名拼写与 <code>services:</code> 下的键是否<strong>完全一致</strong>（区分大小写）；手动加了自定义网络却漏掉对方服务也会解析不到。</p><p><strong>配置不生效</strong>：改了 compose 文件但行为没变，先用 <code>docker compose config</code> 输出插值、合并后的最终配置，看到的内容与预期不符，问题就在这里。另外改了代码却没加 <code>--build</code>，跑的还是旧镜像，也会造成”配置没生效”的错觉。</p></section>
<section><h2>15. 命令与配置速查表<a href="#15-命令与配置速查表"><span>#</span></a></h2><p><strong>常用命令</strong></p>

<table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>docker compose up -d</code></td><td>后台启动全部服务</td></tr><tr><td><code>docker compose up -d --build</code></td><td>重建镜像后启动</td></tr><tr><td><code>docker compose down</code></td><td>停止并删除容器与网络</td></tr><tr><td><code>docker compose down -v</code></td><td>同时删除命名卷（清数据）</td></tr><tr><td><code>docker compose ps</code></td><td>查看服务状态</td></tr><tr><td><code>docker compose logs -f api</code></td><td>实时跟踪日志</td></tr><tr><td><code>docker compose exec api sh</code></td><td>进入运行中的容器</td></tr><tr><td><code>docker compose run --rm api cmd</code></td><td>一次性容器执行命令</td></tr><tr><td><code>docker compose config</code></td><td>验证并输出最终配置</td></tr><tr><td><code>docker compose restart api</code></td><td>重启服务</td></tr></tbody></table><p><strong>服务常用字段</strong></p>

<table><thead><tr><th>字段</th><th>作用</th></tr></thead><tbody><tr><td><code>image</code> / <code>build</code></td><td>镜像来源</td></tr><tr><td><code>container_name</code></td><td>指定容器名（不可与 scale 共用）</td></tr><tr><td><code>ports</code> / <code>expose</code></td><td>对外映射 / 仅内部暴露</td></tr><tr><td><code>environment</code> / <code>env_file</code></td><td>环境变量注入</td></tr><tr><td><code>command</code> / <code>entrypoint</code></td><td>覆盖启动命令</td></tr><tr><td><code>depends_on</code></td><td>启动依赖与条件</td></tr><tr><td><code>healthcheck</code></td><td>健康检查</td></tr><tr><td><code>volumes</code> / <code>networks</code></td><td>数据持久化 / 网络接入</td></tr><tr><td><code>restart</code></td><td>重启策略</td></tr><tr><td><code>deploy.resources</code></td><td>资源限制</td></tr><tr><td><code>logging</code></td><td>日志驱动与轮转</td></tr><tr><td><code>profiles</code></td><td>按需启动分组</td></tr></tbody></table><p><strong>插值语法与可选值</strong></p>

<table><thead><tr><th>语法 / 项</th><th>含义或可选值</th></tr></thead><tbody><tr><td><code>${VAR}</code></td><td>直接取值，未定义则为空</td></tr><tr><td><code>${VAR:-default}</code></td><td>未定义或空时用默认值</td></tr><tr><td><code>${VAR:?err}</code></td><td>未定义时直接报错</td></tr><tr><td><code>${VAR:+alt}</code></td><td>已定义时替换为 alt</td></tr><tr><td><code>$$VAR</code></td><td>转义为字面量</td></tr><tr><td><code>depends_on.condition</code></td><td><code>service_started</code> / <code>service_healthy</code> / <code>service_completed_successfully</code></td></tr><tr><td><code>restart</code></td><td><code>no</code> / <code>always</code> / <code>on-failure[:N]</code> / <code>unless-stopped</code></td></tr><tr><td><code>healthcheck.test</code></td><td><code>["CMD", ...]</code> / <code>["CMD-SHELL", "..."]</code></td></tr><tr><td>日志驱动</td><td><code>json-file</code> / <code>local</code> / <code>syslog</code> / <code>fluentd</code> / <code>gelf</code></td></tr></tbody></table></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/devops/docker%E5%85%A5%E9%97%A8%E4%B8%8E%E5%AE%9E%E6%88%98/</id>
      <title type="text">Docker 入门与实战</title>
      <published>2026-09-11T00:00:00.000Z</published>
      <updated>2026-09-11T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/devops/docker%E5%85%A5%E9%97%A8%E4%B8%8E%E5%AE%9E%E6%88%98/"/>
      <summary type="text">Docker 容器技术系统入门，涵盖核心概念、常用命令、Dockerfile 编写、数据卷与网络、多阶段构建与镜像瘦身实践</summary>
      <content type="html"><![CDATA[<p>应用交付的核心矛盾只有一个：构建它的环境和运行它的环境不一致。Docker 用镜像把应用及其运行时依赖打包成不可变的交付物，用容器把它跑起来，让”在我机器上能跑”这句话不再有存在的理由。</p>
<section><h2>一、为什么要用 Docker<a href="#一为什么要用-docker"><span>#</span></a></h2><p>传统部署把依赖直接装在宿主机上：Python 版本、JDK 版本、系统动态库、环境变量，全都散落在服务器上。当同一台机器跑多个服务，或者开发、测试、生产由不同的人维护时，环境差异必然变成故障来源。典型症状：开发机是 Ubuntu 22.04、服务器是 CentOS 7，编译出的二进制跑不起来；本地 Node 18、CI 上 Node 16，锁文件解析结果不同；服务器上有人手工升级过 OpenSSL，另一个服务因此挂掉；新人配环境配一整天，还是缺某个 <code>libssl</code>。</p><p>Docker 的答案是：把应用和依赖一起打包成镜像，镜像在任何装了 Docker 的机器上以容器形式运行。它解决的不是性能问题，而是交付一致性问题。</p><section><h3>容器与虚拟机的区别<a href="#容器与虚拟机的区别"><span>#</span></a></h3><p>虚拟机通过 Hypervisor 虚拟出整套硬件，每个实例都带一个完整操作系统和自己的内核；容器直接复用宿主机内核，只隔离进程、文件系统、网络等资源。这个根本差异决定了两者在启动速度和资源占用上的量级差别。</p>

<table><thead><tr><th>对比维度</th><th>虚拟机 (VM)</th><th>容器 (Container)</th></tr></thead><tbody><tr><td>启动速度</td><td>数十秒到分钟级</td><td>毫秒到秒级</td></tr><tr><td>资源占用</td><td>每个实例独占一套 OS，内存通常 GB 级</td><td>共享内核，内存通常 MB 级</td></tr><tr><td>隔离级别</td><td>硬件级隔离，更彻底</td><td>内核级隔离（namespace + cgroup），相对较弱</td></tr><tr><td>镜像体积</td><td>通常数 GB</td><td>通常几十 MB 到几百 MB</td></tr><tr><td>适用场景</td><td>强隔离需求、异构内核、完整 OS 环境</td><td>微服务、CI/CD、快速交付与弹性伸缩</td></tr></tbody></table><p>容器不是”轻量级虚拟机”，它本质上是被限制了资源、换了视角的一组 Linux 进程。判断标准是能不能共享内核：能共享就用容器，必须在 Linux 上跑 Windows 才用虚拟机。</p></section></section>
<section><h2>二、三个核心概念<a href="#二三个核心概念"><span>#</span></a></h2><ul>
<li>镜像 (Image)：只读模板，等价于”类”或”安装包”。它由一层层只读文件系统叠加而成，每层对应 Dockerfile 中的一条构建指令。</li>
<li>容器 (Container)：镜像的运行实例，等价于”对象”。启动时在镜像最上层加一个可写层，容器内所有写入都落在可写层，镜像本身永不变。</li>
<li>仓库 (Registry)：集中存放和分发镜像的服务，等价于”应用商店”。默认是 Docker Hub，企业内常用 Harbor、云厂商的 ACR、TCR。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Dockerfile --docker build--&gt; Image --docker push--&gt; Registry</span></div></div><div><div><div>2</div></div><div><span><span>                                     </span></span><span>^                     |</span></div></div><div><div><div>3</div></div><div><span><span>                                     </span></span><span>|---- docker pull ----|</span></div></div><div><div><div>4</div></div><div><span>Container &lt;--docker run-- Image &lt;----+</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>由此可以直接推出几条结论：删容器不影响镜像，删镜像也不影响仓库里的副本；基于同一个镜像可以启动无数个互不干扰的容器；容器内改文件默认不持久，删容器即丢失，这正是数据卷要解决的问题；镜像分层是共享的，本地十个基于 <code>node:20-alpine</code> 的镜像，基础层在磁盘上只存一份。</p></section>
<section><h2>三、安装与基本配置<a href="#三安装与基本配置"><span>#</span></a></h2><section><h3>Linux（Ubuntu / Debian）<a href="#linuxubuntu--debian"><span>#</span></a></h3><p>用 Docker 官方源安装，不要用发行版仓库里的 <code>docker.io</code>，后者版本通常明显落后。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>apt-get</span><span> </span><span>update</span><span> &amp;&amp; </span><span>sudo</span><span> </span><span>apt-get</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>ca-certificates</span><span> </span><span>curl</span><span> </span><span>gnupg</span><span> &amp;&amp; </span><span>sudo</span><span> </span><span>install</span><span> </span><span>-m</span><span> </span><span>0755</span><span> </span><span>-d</span><span> </span><span>/etc/apt/keyrings</span></div></div><div><div><div>2</div></div><div><span>curl</span><span> </span><span>-fsSL</span><span> </span><span>https://download.docker.com/linux/ubuntu/gpg</span><span> | </span><span>sudo</span><span> </span><span>gpg</span><span> </span><span>--dearmor</span><span> </span><span>-o</span><span> </span><span>/etc/apt/keyrings/docker.gpg</span></div></div><div><div><div>3</div></div><div><span>echo</span><span> </span><span>"deb [arch=$(</span><span>dpkg</span><span> </span><span>--print-architecture</span><span>) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(</span><span>.</span><span> /etc/os-release &amp;&amp; </span><span>echo</span><span> </span><span>$VERSION_CODENAME</span><span>) stable"</span><span> | </span><span>sudo</span><span> </span><span>tee</span><span> </span><span>/etc/apt/sources.list.d/docker.list</span><span> &gt; </span><span>/dev/null</span></div></div><div><div><div>4</div></div><div><span>sudo</span><span> </span><span>apt-get</span><span> </span><span>update</span><span> &amp;&amp; </span><span>sudo</span><span> </span><span>apt-get</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>docker-ce</span><span> </span><span>docker-ce-cli</span><span> </span><span>containerd.io</span><span> </span><span>docker-buildx-plugin</span><span> </span><span>docker-compose-plugin</span></div></div><div><div><div>5</div></div><div><span>sudo</span><span> </span><span>usermod</span><span> </span><span>-aG</span><span> </span><span>docker</span><span> </span><span>$USER</span><span>   </span><span># 免 sudo，需重新登录生效</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>测试环境也可用一键脚本 <code>curl -fsSL https://get.docker.com | sudo sh</code>，生产建议走仓库方式以便版本管理。</p></section><section><h3>Windows（WSL2 后端）<a href="#windowswsl2-后端"><span>#</span></a></h3><p>管理员 PowerShell 执行 <code>wsl --install</code>，重启后 <code>wsl --set-default-version 2</code>；安装 Docker Desktop 时勾选 “Use WSL 2 instead of Hyper-V”；在 Settings → Resources → WSL Integration 中启用对应发行版。关键建议：代码放在 WSL2 文件系统内（如 <code>~/projects</code>），不要放 <code>/mnt/c/...</code> 下——跨文件系统 IO 慢好几倍，<code>node_modules</code> 安装会慢到无法忍受。</p></section><section><h3>macOS<a href="#macos"><span>#</span></a></h3><p>用 <code>brew install --cask docker</code> 安装 Docker Desktop，或 <code>brew install orbstack</code> 装更轻量的 OrbStack。Apple Silicon 默认拉取 <code>arm64</code> 镜像；目标镜像只有 <code>amd64</code> 版本时需显式指定平台，性能会有损失：<code>docker pull --platform linux/amd64 legacy-image:1.0</code>。</p></section><section><h3>配置国内镜像加速器<a href="#配置国内镜像加速器"><span>#</span></a></h3><p>编辑 <code>/etc/docker/daemon.json</code>，Windows/macOS 在 Docker Desktop 的 Settings → Docker Engine 中编辑同样的 JSON，然后重启服务：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"registry-mirrors"</span><span>: [</span></div></div><div><div><div>3</div></div><div><span>    </span><span>"https://docker.m.daocloud.io"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>    </span><span>"https://dockerproxy.com"</span><span>,</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"https://mirror.ccs.tencentyun.com"</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>],</span></div></div><div><div><div>7</div></div><div><span>  </span><span>"log-driver"</span><span>: </span><span>"json-file"</span><span>,</span></div></div><div><div><div>8</div></div><div><span>  </span><span>"log-opts"</span><span>: { </span><span>"max-size"</span><span>: </span><span>"50m"</span><span>, </span><span>"max-file"</span><span>: </span><span>"3"</span><span> }</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>daemon-reload</span><span> &amp;&amp; </span><span>sudo</span><span> </span><span>systemctl</span><span> </span><span>restart</span><span> </span><span>docker</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>加速器地址的可用性随时间变化，<code>docker pull</code> 仍失败就先换地址再排查。</p></section><section><h3>验证安装<a href="#验证安装"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>version</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>info</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>hello-world</span><span>   </span><span># 版本 / 环境信息 / 冒烟测试</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>四、镜像操作命令<a href="#四镜像操作命令"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>pull</span><span> </span><span>nginx:1.27-alpine</span><span>                    </span><span># 不写标签默认 latest，生产禁用</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>pull</span><span> </span><span>--platform</span><span> </span><span>linux/amd64</span><span> </span><span>openjdk:17-slim</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>images</span><span>                                    </span><span># -f dangling=true 只看悬空镜像</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>history</span><span> </span><span>nginx:1.27-alpine</span><span>                 </span><span># 构建历史与每层大小</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>rmi</span><span> </span><span>nginx:1.27-alpine</span><span>                     </span><span># 删除；image prune -a 清理未使用镜像</span></div></div><div><div><div>6</div></div><div><span>docker</span><span> </span><span>tag</span><span> </span><span>myapp:1.0</span><span> </span><span>registry.example.com/team/myapp:1.0</span><span>   </span><span># 不复制数据，只加引用名</span></div></div><div><div><div>7</div></div><div><span>docker</span><span> </span><span>login</span><span> </span><span>registry.example.com</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>push</span><span> </span><span>registry.example.com/team/myapp:1.0</span></div></div><div><div><div>8</div></div><div><span>docker</span><span> </span><span>inspect</span><span> </span><span>nginx:1.27-alpine</span><span>                 </span><span># 完整元数据</span></div></div><div><div><div>9</div></div><div><span>docker</span><span> </span><span>inspect</span><span> </span><span>-f</span><span> </span><span>'{{json .Config.ExposedPorts}}'</span><span> </span><span>nginx:1.27-alpine</span><span>   </span><span># Go 模板抽取字段</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>导入导出要区分两对命令：<code>save</code>/<code>load</code> 传输镜像（保留分层与标签，用于离线环境），<code>export</code>/<code>import</code> 传输容器文件系统（丢失历史分层，一般只用于制作极简基础镜像）。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>save</span><span> </span><span>-o</span><span> </span><span>myapp-1.0.tar</span><span> </span><span>myapp:1.0</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>load</span><span> </span><span>-i</span><span> </span><span>myapp-1.0.tar</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>export</span><span> </span><span>-o</span><span> </span><span>container-fs.tar</span><span> </span><span>my-running-container</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>import</span><span> </span><span>container-fs.tar</span><span> </span><span>myapp:from-container</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>五、容器生命周期命令<a href="#五容器生命周期命令"><span>#</span></a></h2><p>生命周期是：创建 → 启动 → 运行 → 停止 → 重启 → 删除。多数时候 <code>docker run</code> 一步到位，但排查时每个阶段都用得上。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>web</span><span> </span><span>nginx:1.27-alpine</span><span>   </span><span># 创建并启动；docker create 只创建不启动</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>ps</span><span>                                    </span><span># 运行中；-a 含已退出，-aq 只输出 ID</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>start</span><span> </span><span>web</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>stop</span><span> </span><span>web</span><span>          </span><span># stop 先发 SIGTERM，默认 10 秒后 SIGKILL</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>kill</span><span> </span><span>web</span><span>                              </span><span># 直接强杀；docker restart 重启</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>rm</span><span> </span><span>web</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>rm</span><span> </span><span>-f</span><span> </span><span>web</span><span>            </span><span># -f 强制删除运行中的容器</span></div></div><div><div><div>6</div></div><div><span>docker</span><span> </span><span>container</span><span> </span><span>prune</span><span>                       </span><span># 清理已退出的容器</span></div></div><div><div><div>7</div></div><div><span>docker</span><span> </span><span>top</span><span> </span><span>web</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>port</span><span> </span><span>web</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>diff</span><span> </span><span>web</span><span>   </span><span># 进程 / 端口映射 / 文件系统改动</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>run 的常用参数<a href="#run-的常用参数"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>my-api</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>  </span><span>-p</span><span> </span><span>8080:80</span><span> </span><span>-v</span><span> </span><span>/data/api:/app/data</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>-e</span><span> </span><span>TZ=Asia/Shanghai</span><span> </span><span>\</span></div></div><div><div><div>4</div></div><div><span>  </span><span>--restart</span><span> </span><span>unless-stopped</span><span> </span><span>--network</span><span> </span><span>app-net</span><span> </span><span>\</span></div></div><div><div><div>5</div></div><div><span>  </span><span>--memory</span><span> </span><span>512m</span><span> </span><span>--cpus</span><span> </span><span>1.0</span><span> </span><span>\</span></div></div><div><div><div>6</div></div><div><span>  </span><span>registry.example.com/team/my-api:1.2.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>参数</th><th>含义</th><th>实践建议</th></tr></thead><tbody><tr><td><code>-d</code></td><td>后台运行</td><td>服务类容器必加，不加会占住终端</td></tr><tr><td><code>-p 8080:80</code></td><td>端口映射，左为宿主机，右为容器</td><td>与 EXPOSE 无强绑定</td></tr><tr><td><code>-v 宿主:容器</code></td><td>挂载数据卷或绑定目录</td><td>数据必须落在这里</td></tr><tr><td><code>-e KEY=VALUE</code></td><td>注入环境变量</td><td>密钥不要写命令行，用 <code>--env-file</code></td></tr><tr><td><code>--name</code></td><td>指定容器名</td><td>不指定会随机生成，后续操作很难受</td></tr><tr><td><code>--restart</code></td><td><code>no</code> / <code>on-failure</code> / <code>always</code> / <code>unless-stopped</code></td><td>生产用 <code>unless-stopped</code></td></tr><tr><td><code>--network</code></td><td>加入指定网络</td><td>自定义网络内可用容器名互访</td></tr><tr><td><code>--rm</code> / <code>-it</code></td><td>退出后自动删除 / 交互式 TTY</td><td>一次性任务加上；调试时配合 <code>sh</code></td></tr><tr><td><code>--memory</code> / <code>--cpus</code> / <code>--user</code></td><td>资源上限与运行用户</td><td>防止吃光宿主机；加固时用非 root</td></tr></tbody></table><p>最常踩的坑：<code>-p 8080:80</code> 与 <code>-p 80:8080</code> 方向相反，左边永远是宿主机。</p></section><section><h3>exec、logs、stats、cp<a href="#execlogsstatscp"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>exec</span><span> </span><span>-it</span><span> </span><span>web</span><span> </span><span>sh</span><span>             </span><span># alpine 没有 bash；-u root 可提权进入</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>logs</span><span> </span><span>-f</span><span> </span><span>--tail</span><span> </span><span>100</span><span> </span><span>-t</span><span> </span><span>web</span><span>   </span><span># -t 带时间戳，-f 持续跟踪</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>stats</span><span>                       </span><span># 实时资源占用，Ctrl+C 退出；--no-stream 只采样一次</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>cp</span><span> </span><span>web:/etc/nginx/nginx.conf</span><span> </span><span>./nginx.conf.bak</span><span>   </span><span># 双向复制，参数顺序反过来即可</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>不要用 <code>docker cp</code> 去宿主机 <code>/var/lib/docker/containers/...</code> 捞日志，那样既易损坏文件也难解析；正确做法是限制 <code>log-driver</code> 大小，再用 <code>docker logs</code> 或集中式日志系统采集。</p></section></section>
<section><h2>六、Dockerfile 详解<a href="#六dockerfile-详解"><span>#</span></a></h2><p>Dockerfile 是一串指令，每条指令产生一个镜像层。理解”在构建期还是运行期生效”是写好它的前提。</p>

<table><thead><tr><th>指令</th><th>作用</th><th>生效阶段</th></tr></thead><tbody><tr><td><code>FROM</code></td><td>指定基础镜像，必须是第一条有效指令</td><td>构建期</td></tr><tr><td><code>RUN</code></td><td>构建时执行命令，结果固化为新层</td><td>构建期</td></tr><tr><td><code>COPY</code></td><td>从构建上下文复制文件</td><td>构建期</td></tr><tr><td><code>ADD</code></td><td>类似 COPY，额外支持 URL 和自动解压 tar</td><td>构建期</td></tr><tr><td><code>WORKDIR</code></td><td>设置工作目录，自动创建</td><td>构建期并影响运行期</td></tr><tr><td><code>ENV</code></td><td>环境变量，构建期与运行期都可见</td><td>两者</td></tr><tr><td><code>ARG</code></td><td>构建参数，仅构建期可见</td><td>构建期</td></tr><tr><td><code>EXPOSE</code></td><td>声明监听端口，元数据性质</td><td>元数据</td></tr><tr><td><code>VOLUME</code></td><td>声明匿名卷挂载点</td><td>运行期</td></tr><tr><td><code>USER</code></td><td>指定后续指令和容器的运行用户</td><td>两者</td></tr><tr><td><code>CMD</code></td><td>默认启动命令，可被覆盖</td><td>运行期</td></tr><tr><td><code>ENTRYPOINT</code></td><td>固定入口，参数可被追加</td><td>运行期</td></tr><tr><td><code>HEALTHCHECK</code> / <code>LABEL</code></td><td>健康检查命令 / 镜像元数据</td><td>运行期 / 元数据</td></tr></tbody></table><section><h3>常用指令组合<a href="#常用指令组合"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>FROM</span><span> node:20.11-alpine3.19              # 固定版本与架构标签，禁止 latest</span></div></div><div><div><div>2</div></div><div><span>ARG</span><span> NPM_REGISTRY=https://registry.npmmirror.com   # 仅构建期可见</span></div></div><div><div><div>3</div></div><div><span>ENV</span><span> NODE_ENV=production TZ=Asia/Shanghai          # 会写进镜像，运行期可见</span></div></div><div><div><div>4</div></div><div><span>WORKDIR</span><span> /app                                      # 自动创建目录</span></div></div><div><div><div>5</div></div><div><span>COPY</span><span> --chown=node:node package.json package-lock.json ./</span></div></div><div><div><div>6</div></div><div><span>COPY</span><span> --from=builder /app/dist ./dist    # 从多阶段构建的前序阶段复制产物</span></div></div><div><div><div>7</div></div><div><span>ADD</span><span> app.tar.gz /app/                    # 额外支持 URL 与自动解压，非必要不用</span></div></div><div><div><div>8</div></div><div><span>EXPOSE</span><span> 8080</span></div></div><div><div><div>9</div></div><div><span>VOLUME</span><span> [</span><span>"/app/data"</span><span>]</span></div></div><div><div><div>10</div></div><div><span>RUN</span><span> addgroup -S app &amp;&amp; adduser -S app -G app</span></div></div><div><div><div>11</div></div><div><span>USER</span><span> app                                # 切换到非 root 用户</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>ARG</code> 与 <code>ENV</code> 的差别：<code>ARG</code> 仅构建期可见、不留在最终镜像；<code>ENV</code> 会持久化。但 <code>ARG</code> 的值仍会出现在 <code>docker history</code> 里，真正的密钥要用 BuildKit 的 secret 挂载。<code>VOLUME</code> 也有个隐蔽陷阱：在 <code>VOLUME</code> 声明之后对该目录执行 <code>RUN</code> 写入的数据会丢失，因为声明后它被当成挂载点——要么把数据准备放在声明之前，要么运行期用 <code>-v</code> 显式挂载。</p></section><section><h3>CMD 与 ENTRYPOINT 的区别<a href="#cmd-与-entrypoint-的区别"><span>#</span></a></h3><p>一句话概括：<code>ENTRYPOINT</code> 定义”这个镜像是什么”，<code>CMD</code> 定义”默认怎么运行它”。</p>

<table><thead><tr><th>对比项</th><th>CMD</th><th>ENTRYPOINT</th></tr></thead><tbody><tr><td>定位</td><td>默认命令或默认参数</td><td>固定入口命令</td></tr><tr><td>能否被覆盖</td><td><code>docker run</code> 后跟命令即覆盖</td><td>不能直接覆盖，需 <code>--entrypoint</code></td></tr><tr><td>追加参数的语义</td><td>追加参数会替换整个 CMD</td><td>追加参数会追加到 ENTRYPOINT 之后</td></tr><tr><td>一个 Dockerfile 中数量</td><td>只有最后一条生效</td><td>只有最后一条生效</td></tr><tr><td>shell 形式</td><td>走 <code>/bin/sh -c</code>，能解析变量但收不到信号</td><td>同左</td></tr><tr><td>exec 形式</td><td>直接执行，进程是 PID 1，能收到信号</td><td>同左</td></tr><tr><td>组合用法</td><td>搭配 ENTRYPOINT 时作为默认参数</td><td>定义入口，CMD 提供参数默认值</td></tr></tbody></table><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>CMD</span><span> [</span><span>"node"</span><span>, </span><span>"server.js"</span><span>]              # 单独用时：docker run myimg ls 会绕过应用</span></div></div><div><div><div>2</div></div><div><span>ENTRYPOINT</span><span> [</span><span>"node"</span><span>, </span><span>"server.js"</span><span>]       # 单独用时：run 的追加参数会被追加到其后</span></div></div><div><div><div>3</div></div><div><span>ENTRYPOINT</span><span> [</span><span>"node"</span><span>, </span><span>"server.js"</span><span>]       # 组合，推荐</span></div></div><div><div><div>4</div></div><div><span>CMD</span><span> [</span><span>"--port"</span><span>, </span><span>"8080"</span><span>]                 # 默认参数，命令行传参时整体替换</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>组合写法下，<code>docker run myimg --port 3000</code> 时 CMD 整体被替换，最终执行 <code>node server.js --port 3000</code>。另一个必须注意的点：优先用 exec 形式（JSON 数组）。写成 shell 形式 <code>CMD node server.js</code> 时 PID 1 是 <code>/bin/sh</code>，<code>docker stop</code> 的 SIGTERM 会被 sh 吞掉，只能等满 10 秒被 SIGKILL，优雅关闭逻辑永远不执行。</p></section></section>
<section><h2>七、构建镜像<a href="#七构建镜像"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>-t</span><span> </span><span>myapp:1.0</span><span> </span><span>.</span><span>                            </span><span># 当前目录为上下文</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>-f</span><span> </span><span>docker/Dockerfile.prod</span><span> </span><span>-t</span><span> </span><span>myapp:1.0</span><span> </span><span>.</span><span>  </span><span># 指定 Dockerfile</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>--build-arg</span><span> </span><span>NPM_REGISTRY=https://registry.npmmirror.com</span><span> </span><span>-t</span><span> </span><span>myapp:1.0</span><span> </span><span>.</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>--target</span><span> </span><span>builder</span><span> </span><span>-t</span><span> </span><span>myapp:debug</span><span> </span><span>.</span><span>         </span><span># 只构建指定阶段</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>--no-cache</span><span> </span><span>-t</span><span> </span><span>myapp:1.0</span><span> </span><span>.</span><span>                 </span><span># 强制重建，不走缓存</span></div></div><div><div><div>6</div></div><div><span>docker</span><span> </span><span>buildx</span><span> </span><span>build</span><span> </span><span>--platform</span><span> </span><span>linux/amd64,linux/arm64</span><span> </span><span>-t</span><span> </span><span>registry.example.com/team/myapp:1.0</span><span> </span><span>--push</span><span> </span><span>.</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>构建上下文与 .dockerignore<a href="#构建上下文与-dockerignore"><span>#</span></a></h3><p>命令最后那个 <code>.</code> 不是”去哪找 Dockerfile”，而是”把什么作为构建上下文交给构建器”。上下文内所有内容（<code>.dockerignore</code> 排除之后）都会被发送，<code>COPY</code> 只能引用上下文内的文件。上下文过大会直接拖慢构建：带 <code>node_modules</code> 和 <code>.git</code> 的项目上下文可能几百 MB，每次构建都要先传输一遍。也可以用管道方式执行 <code>docker build -t app:1.0 -</code>，从 stdin 读取 Dockerfile，此时不会发送任何构建上下文。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>node_modules</span></div></div><div><div><div>2</div></div><div><span>.git</span></div></div><div><div><div>3</div></div><div><span>dist</span></div></div><div><div><div>4</div></div><div><span>.env</span></div></div><div><div><div>5</div></div><div><span>*.log</span></div></div><div><div><div>6</div></div><div><span>Dockerfile</span></div></div><div><div><div>7</div></div><div><span>README.md</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>注意 <code>.dockerignore</code> 的路径相对上下文根目录，<code>**/</code> 前缀才匹配任意层级。</p></section><section><h3>分层缓存<a href="#分层缓存"><span>#</span></a></h3><p>缓存失效规则：某条指令的内容或其依赖的前一层发生变化，该层及其之后所有层全部重建。因此指令顺序决定构建速度——变化频率低的放前面、高的放后面。下面两个 Dockerfile 执行结果相同，但后者改源码时不需要重装依赖：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 慢：任何文件变动都让后面的层全部失效</span></div></div><div><div><div>2</div></div><div><span>FROM</span><span> node:20-alpine</span></div></div><div><div><div>3</div></div><div><span>WORKDIR</span><span> /app</span></div></div><div><div><div>4</div></div><div><span>COPY</span><span> . .</span></div></div><div><div><div>5</div></div><div><span>RUN</span><span> npm ci</span></div></div><div><div><div>6</div></div><div><span>CMD</span><span> [</span><span>"npm"</span><span>, </span><span>"start"</span><span>]</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 快：依赖清单没变就命中缓存，改代码只重建最后一层</span></div></div><div><div><div>9</div></div><div><span>FROM</span><span> node:20-alpine</span></div></div><div><div><div>10</div></div><div><span>WORKDIR</span><span> /app</span></div></div><div><div><div>11</div></div><div><span>COPY</span><span> package.json package-lock.json ./</span></div></div><div><div><div>12</div></div><div><span>RUN</span><span> npm ci</span></div></div><div><div><div>13</div></div><div><span>COPY</span><span> . .</span></div></div><div><div><div>14</div></div><div><span>CMD</span><span> [</span><span>"npm"</span><span>, </span><span>"start"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>八、数据持久化<a href="#八数据持久化"><span>#</span></a></h2><p>容器可写层随容器生命周期存亡，删容器即丢数据。持久化有两条路：数据卷 (volume) 与绑定挂载 (bind mount)。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>volume</span><span> </span><span>create</span><span> </span><span>pgdata</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>pg</span><span> </span><span>-v</span><span> </span><span>pgdata:/var/lib/postgresql/data</span><span> </span><span>postgres:16-alpine</span><span>    </span><span># 数据卷</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>web</span><span> </span><span>-v</span><span> </span><span>/opt/site:/usr/share/nginx/html:ro</span><span> </span><span>nginx:1.27-alpine</span><span> </span><span># 绑定挂载，:ro 只读</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--tmpfs</span><span> </span><span>/tmp:rw,size=64m</span><span> </span><span>nginx:1.27-alpine</span><span>                         </span><span># tmpfs，只在内存</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>对比项</th><th>数据卷 (volume)</th><th>绑定挂载 (bind mount)</th></tr></thead><tbody><tr><td>存储位置</td><td>Docker 管理的目录，如 <code>/var/lib/docker/volumes/pgdata</code></td><td>宿主机上任意指定路径</td></tr><tr><td>创建方式</td><td><code>docker volume create</code> 或 <code>-v 卷名:路径</code> 自动创建</td><td>路径必须已存在</td></tr><tr><td>生命周期</td><td>独立于容器，删容器不删卷</td><td>完全由用户管理</td></tr><tr><td>跨平台一致性</td><td>一致，各平台都由虚拟机内管理</td><td>Windows/Linux 路径与权限语义差异大</td></tr><tr><td>性能 / 可移植性</td><td>高且原生，可被多个容器复用</td><td>Linux 原生高，跨文件系统慢，与宿主机路径强绑定</td></tr><tr><td>适用场景</td><td>数据库数据、应用持久数据、多容器共享</td><td>开发时挂载源码热更新、挂载配置文件</td></tr></tbody></table><p>选型结论：生产环境的数据一律用数据卷（生命周期清晰、跨平台一致、便于备份）；开发环境挂载源码或配置文件用绑定挂载（改文件立即生效，无需重建镜像）。只写容器路径不写卷名会创建匿名卷，名字随机、难以管理，应尽量避免。管理命令本身很轻：<code>docker volume ls</code> 列出，<code>docker volume inspect pgdata</code> 看挂载点与使用它的容器，<code>docker volume rm</code> 删除（必须先停止并删除使用它的容器），<code>docker volume prune</code> 清理所有未被使用的卷。</p><p>备份与恢复的标准做法是起一个临时容器，把卷和宿主机目录同时挂进去打 tar 包，不依赖宿主机上卷的实际路径：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>-v</span><span> </span><span>pgdata:/data</span><span> </span><span>-v</span><span> </span><span>"</span><span>$PWD</span><span>":/backup</span><span> </span><span>alpine:3.20</span><span> </span><span>tar</span><span> </span><span>czf</span><span> </span><span>/backup/pgdata-</span><span>$(</span><span>date</span><span> </span><span>+%Y%m%d</span><span>)</span><span>.tar.gz</span><span> </span><span>-C</span><span> </span><span>/data</span><span> </span><span>.</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>-v</span><span> </span><span>pgdata:/data</span><span> </span><span>-v</span><span> </span><span>"</span><span>$PWD</span><span>":/backup</span><span> </span><span>alpine:3.20</span><span> </span><span>sh</span><span> </span><span>-c</span><span> </span><span>"rm -rf /data/* &amp;&amp; tar xzf /backup/pgdata-20260911.tar.gz -C /data"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>数据库类容器应先保证数据一致：PostgreSQL 用 <code>pg_dump</code> 做逻辑备份更安全，直接打包数据目录属于物理备份，必须在数据库停止或快照一致的前提下执行。</p></section>
<section><h2>九、网络<a href="#九网络"><span>#</span></a></h2>

<table><thead><tr><th>模式</th><th>说明</th><th>典型用途</th></tr></thead><tbody><tr><td><code>bridge</code></td><td>默认模式，接入 Docker 网桥，通过 NAT 访问外网</td><td>绝大多数单机场景</td></tr><tr><td><code>host</code></td><td>直接使用宿主机网络命名空间，无端口映射，性能最好</td><td>高性能代理、需要绑定大量端口</td></tr><tr><td><code>none</code> / <code>container:名称</code></td><td>只有 lo 回环 / 共享另一容器的网络命名空间</td><td>离线批处理与安全隔离 / sidecar 模式</td></tr><tr><td>自定义 bridge</td><td>用户创建，内置 DNS 服务发现</td><td>生产推荐，服务间用容器名通信</td></tr></tbody></table><p>默认 <code>bridge</code> 下的容器只能通过 IP 互访，而 IP 随容器重建变化，因此不可用于服务间调用。自定义网络提供内置 DNS，容器名就是主机名：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>network</span><span> </span><span>create</span><span> </span><span>--driver</span><span> </span><span>bridge</span><span> </span><span>app-net</span><span>          </span><span># 也可加 --subnet 172.28.0.0/16</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>db</span><span> </span><span>--network</span><span> </span><span>app-net</span><span> </span><span>-e</span><span> </span><span>POSTGRES_PASSWORD=secret</span><span> </span><span>postgres:16-alpine</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>api</span><span> </span><span>--network</span><span> </span><span>app-net</span><span> </span><span>-p</span><span> </span><span>8080:8080</span><span> </span><span>my-api:1.0</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>exec</span><span> </span><span>-it</span><span> </span><span>api</span><span> </span><span>sh</span><span> </span><span>-c</span><span> </span><span>"nc -zv db 5432"</span><span>    </span><span># 直接用容器名访问数据库</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>network</span><span> </span><span>connect</span><span> </span><span>app-net-2</span><span> </span><span>api</span><span>          </span><span># 把运行中的容器接入另一个网络</span></div></div><div><div><div>6</div></div><div><span>docker</span><span> </span><span>network</span><span> </span><span>disconnect</span><span> </span><span>app-net</span><span> </span><span>api</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同一自定义网络内的容器用容器名或网络别名互访；不同网络的容器默认完全隔离。这也是做环境隔离的基础——把生产容器和测试容器放进不同网络。排查时常用下面这些手段：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>--rm</span><span> </span><span>--network</span><span> </span><span>app-net</span><span> </span><span>nicolaka/netshoot</span><span> </span><span>ping</span><span> </span><span>-c</span><span> </span><span>2</span><span> </span><span>db</span><span>   </span><span># 临时容器测连通性</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>exec</span><span> </span><span>-it</span><span> </span><span>api</span><span> </span><span>cat</span><span> </span><span>/etc/resolv.conf</span><span>                           </span><span># 容器内 DNS 配置</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>inspect</span><span> </span><span>-f</span><span> </span><span>'{{json .NetworkSettings.Networks}}'</span><span> </span><span>api</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>端口映射原理<a href="#端口映射原理"><span>#</span></a></h3><p><code>-p 8080:80</code> 背后是 iptables（或 nftables）的 DNAT 规则：宿主机 8080 收到流量，<code>DOCKER</code> 链把目标地址改写为容器 IP 的 80 端口，回程由 conntrack 自动反向 NAT。由此可直接推出几个现象：端口被占用时启动失败，报 <code>bind: address already in use</code>；服务在容器内监听 <code>127.0.0.1</code> 时映射后宿主机访问不通，因为网络命名空间里 <code>127.0.0.1</code> 只指容器自身，服务必须监听 <code>0.0.0.0</code>；<code>-p 127.0.0.1:8080:80</code> 只绑定回环地址，可避免暴露到公网；<code>host</code> 模式下 <code>-p</code> 无效，因为没有独立网络命名空间。</p></section></section>
<section><h2>十、多阶段构建<a href="#十多阶段构建"><span>#</span></a></h2><p>多阶段构建解决”构建时需要的工具链不该出现在运行镜像里”的矛盾：同一个 Dockerfile 里可以有多个 <code>FROM</code>，每个 <code>FROM</code> 开启一个新阶段，阶段之间用 <code>COPY --from=</code> 传递产物。单阶段写法把完整工具链、npm 缓存、devDependencies 和 <code>.git</code> 全部塞进镜像，通常 1GB 以上；改为多阶段后运行镜像仅保留生产依赖与构建产物：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>FROM</span><span> node:20.11-alpine3.19 </span><span>AS</span><span> builder</span></div></div><div><div><div>2</div></div><div><span>WORKDIR</span><span> /app</span></div></div><div><div><div>3</div></div><div><span>COPY</span><span> package.json package-lock.json ./</span></div></div><div><div><div>4</div></div><div><span>RUN</span><span> npm ci --include=dev &amp;&amp; npm cache clean --force</span></div></div><div><div><div>5</div></div><div><span>COPY</span><span> . .</span></div></div><div><div><div>6</div></div><div><span>RUN</span><span> npm run build</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>FROM</span><span> node:20.11-alpine3.19 </span><span>AS</span><span> runner</span></div></div><div><div><div>9</div></div><div><span>WORKDIR</span><span> /app</span></div></div><div><div><div>10</div></div><div><span>ENV</span><span> NODE_ENV=production TZ=Asia/Shanghai</span></div></div><div><div><div>11</div></div><div><span>COPY</span><span> package.json package-lock.json ./</span></div></div><div><div><div>12</div></div><div><span>RUN</span><span> npm ci --omit=dev &amp;&amp; npm cache clean --force          # 只装生产依赖</span></div></div><div><div><div>13</div></div><div><span>COPY</span><span> --from=builder /app/dist ./dist                      # 只复制构建产物</span></div></div><div><div><div>14</div></div><div><span>RUN</span><span> addgroup -S nodejs &amp;&amp; adduser -S nodejs -G nodejs &amp;&amp; chown -R nodejs:nodejs /app</span></div></div><div><div><div>15</div></div><div><span>USER</span><span> nodejs</span></div></div><div><div><div>16</div></div><div><span>EXPOSE</span><span> 3000</span></div></div><div><div><div>17</div></div><div><span>CMD</span><span> [</span><span>"node"</span><span>, </span><span>"dist/server.js"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>体积可降到 150MB 左右；换成 distroless 基础镜像后能压到 100MB 以内。Java 应用同理：单阶段的 Maven 构建镜像会把 Maven、本地仓库、JDK 全带进去（通常 800MB 到 1GB），改为 Maven 构建、JRE 运行，并用 Spring Boot 的 <code>layertools</code> 把 fat jar 拆成四层，业务代码改动只让最后一层失效，依赖层几乎总能命中缓存：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>FROM</span><span> maven:3.9-eclipse-temurin-17 </span><span>AS</span><span> builder</span></div></div><div><div><div>2</div></div><div><span>WORKDIR</span><span> /build</span></div></div><div><div><div>3</div></div><div><span>COPY</span><span> pom.xml .</span></div></div><div><div><div>4</div></div><div><span>RUN</span><span> mvn -B dependency:go-offline                          # 依赖先行，利用层缓存</span></div></div><div><div><div>5</div></div><div><span>COPY</span><span> src ./src</span></div></div><div><div><div>6</div></div><div><span>RUN</span><span> mvn -B clean package -DskipTests</span></div></div><div><div><div>7</div></div><div><span>RUN</span><span> java -Djarmode=layertools -jar target/app.jar extract</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>FROM</span><span> eclipse-temurin:17-jre-alpine </span><span>AS</span><span> runner</span></div></div><div><div><div>10</div></div><div><span>RUN</span><span> addgroup -S spring &amp;&amp; adduser -S spring -G spring</span></div></div><div><div><div>11</div></div><div><span>WORKDIR</span><span> /app</span></div></div><div><div><div>12</div></div><div><span>USER</span><span> spring:spring</span></div></div><div><div><div>13</div></div><div><span>COPY</span><span> --from=builder --chown=spring:spring /build/extracted/dependencies/ ./</span></div></div><div><div><div>14</div></div><div><span>COPY</span><span> --from=builder --chown=spring:spring /build/extracted/spring-boot-loader/ ./</span></div></div><div><div><div>15</div></div><div><span>COPY</span><span> --from=builder --chown=spring:spring /build/extracted/snapshot-dependencies/ ./</span></div></div><div><div><div>16</div></div><div><span>COPY</span><span> --from=builder --chown=spring:spring /build/extracted/application/ ./</span></div></div><div><div><div>17</div></div><div><span>ENV</span><span> JAVA_OPTS=</span><span>"-XX:MaxRAMPercentage=75.0 -Duser.timezone=Asia/Shanghai"</span></div></div><div><div><div>18</div></div><div><span>ENTRYPOINT</span><span> [</span><span>"sh"</span><span>, </span><span>"-c"</span><span>, </span><span>"exec java $JAVA_OPTS org.springframework.boot.loader.launch.JarLauncher"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>ENTRYPOINT</code> 里的 <code>exec</code> 前缀是必要的，否则 java 进程不是 PID 1，收不到 SIGTERM，优雅停机不会执行。</p></section>
<section><h2>十一、实战：容器化一个 React (Vite) 应用<a href="#十一实战容器化一个-react-vite-应用"><span>#</span></a></h2><p>目标是把 Vite 构建的 SPA 打包成 Nginx 静态托管镜像，支持前端路由 history 模式，并暴露健康检查端点。项目根目录下放 <code>Dockerfile</code>、<code>nginx.conf</code> 和 <code>.dockerignore</code>（内容见第七节）。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># ---------- 阶段一：构建静态资源 ----------</span></div></div><div><div><div>2</div></div><div><span>FROM</span><span> node:20.11-alpine3.19 </span><span>AS</span><span> builder</span></div></div><div><div><div>3</div></div><div><span>WORKDIR</span><span> /app</span></div></div><div><div><div>4</div></div><div><span>ARG</span><span> VITE_API_BASE_URL=/api                 # Vite 只把 VITE_ 前缀的变量内联进产物</span></div></div><div><div><div>5</div></div><div><span>ENV</span><span> VITE_API_BASE_URL=$VITE_API_BASE_URL</span></div></div><div><div><div>6</div></div><div><span>COPY</span><span> package.json package-lock.json ./</span></div></div><div><div><div>7</div></div><div><span>RUN</span><span> npm ci &amp;&amp; npm cache clean --force</span></div></div><div><div><div>8</div></div><div><span>COPY</span><span> . .</span></div></div><div><div><div>9</div></div><div><span>RUN</span><span> npm run build</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># ---------- 阶段二：Nginx 托管 ----------</span></div></div><div><div><div>12</div></div><div><span>FROM</span><span> nginx:1.27-alpine </span><span>AS</span><span> runner</span></div></div><div><div><div>13</div></div><div><span>RUN</span><span> rm -f /etc/nginx/conf.d/default.conf</span></div></div><div><div><div>14</div></div><div><span>COPY</span><span> nginx.conf /etc/nginx/conf.d/app.conf</span></div></div><div><div><div>15</div></div><div><span>COPY</span><span> --from=builder /app/dist /usr/share/nginx/html</span></div></div><div><div><div>16</div></div><div><span># 非 root 用户不能监听 1024 以下端口，容器内改听 8080，对外端口由宿主机决定</span></div></div><div><div><div>17</div></div><div><span>RUN</span><span> chown -R nginx:nginx /usr/share/nginx/html /var/cache/nginx /var/log/nginx &amp;&amp; touch /tmp/nginx.pid &amp;&amp; chown nginx:nginx /tmp/nginx.pid</span></div></div><div><div><div>18</div></div><div><span>USER</span><span> nginx</span></div></div><div><div><div>19</div></div><div><span>EXPOSE</span><span> 8080</span></div></div><div><div><div>20</div></div><div><span>HEALTHCHECK</span><span> --interval=30s --timeout=3s --start-period=5s --retries=3 CMD wget -qO- http://127.0.0.1:8080/healthz || exit 1</span></div></div><div><div><div>21</div></div><div><span>CMD</span><span> [</span><span>"nginx"</span><span>, </span><span>"-g"</span><span>, </span><span>"daemon off;"</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>nginx.conf：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>server</span><span> {</span></div></div><div><div><div>2</div></div><div><span>   <span> </span></span><span>listen </span><span>      </span><span>8080</span><span>;</span></div></div><div><div><div>3</div></div><div><span>   <span> </span></span><span>server_name </span><span> _;</span></div></div><div><div><div>4</div></div><div><span>   <span> </span></span><span>root </span><span>  /usr/share/nginx/html;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>   <span> </span></span><span>gzip </span><span>on</span><span>;</span></div></div><div><div><div>7</div></div><div><span>   <span> </span></span><span>gzip_comp_level </span><span>5</span><span>;</span></div></div><div><div><div>8</div></div><div><span>   <span> </span></span><span>gzip_types </span><span>text/plain text/css application/json application/javascript image/svg+xml;</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>location</span><span> ^~ </span><span>/assets/ </span><span>{                 </span><span># 带 hash 的资源可长期缓存</span></div></div><div><div><div>11</div></div><div><span>       <span> </span></span><span>expires </span><span>1y;</span></div></div><div><div><div>12</div></div><div><span>       <span> </span></span><span>add_header </span><span>Cache-Control </span><span>"public, immutable"</span><span>;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>    </span><span>location</span><span> = </span><span>/index.html </span><span>{               </span><span># 不缓存，保证发版后拿到最新资源引用</span></div></div><div><div><div>15</div></div><div><span>       <span> </span></span><span>add_header </span><span>Cache-Control </span><span>"no-cache, no-store, must-revalidate"</span><span>;</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>    </span><span>location</span><span> = </span><span>/healthz </span><span>{                  </span><span># 健康检查端点</span></div></div><div><div><div>18</div></div><div><span>       <span> </span></span><span>add_header </span><span>Content-Type text/plain;</span></div></div><div><div><div>19</div></div><div><span>        </span><span>return</span><span> </span><span>200</span><span> </span><span>"ok</span><span>\n</span><span>"</span><span>;</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>    </span><span>location</span><span> / {                           </span><span># SPA history 模式回退</span></div></div><div><div><div>22</div></div><div><span>       <span> </span></span><span>try_files </span><span>$</span><span>uri</span><span> $</span><span>uri</span><span>/ /index.html;</span></div></div><div><div><div>23</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>24</div></div><div><span>    </span><span>location</span><span> /api/ {                       </span><span># 反向代理后端，避免浏览器跨域</span></div></div><div><div><div>25</div></div><div><span>       <span> </span></span><span>proxy_pass </span><span>        http://backend:8080/;</span></div></div><div><div><div>26</div></div><div><span>       <span> </span></span><span>proxy_set_header </span><span>  Host              $</span><span>host</span><span>;</span></div></div><div><div><div>27</div></div><div><span>       <span> </span></span><span>proxy_set_header </span><span>  X-Real-IP         $</span><span>remote_addr</span><span>;</span></div></div><div><div><div>28</div></div><div><span>       <span> </span></span><span>proxy_set_header </span><span>  X-Forwarded-For   $</span><span>proxy_add_x_forwarded_for</span><span>;</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>构建、运行与验证：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>build</span><span> </span><span>-t</span><span> </span><span>my-react-app:1.0</span><span> </span><span>.</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-d</span><span> </span><span>--name</span><span> </span><span>my-react-app</span><span> </span><span>-p</span><span> </span><span>8080:8080</span><span> </span><span>--restart</span><span> </span><span>unless-stopped</span><span> </span><span>my-react-app:1.0</span></div></div><div><div><div>3</div></div><div><span>curl</span><span> </span><span>-I</span><span> </span><span>http://127.0.0.1:8080/healthz</span><span>          </span><span># 健康检查端点</span></div></div><div><div><div>4</div></div><div><span>curl</span><span> </span><span>-I</span><span> </span><span>http://127.0.0.1:8080/some/deep/route</span><span>  </span><span># 验证 history 路由回退</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用 Compose 编排前后端，<code>proxy_pass http://backend:8080/</code> 中的 <code>backend</code> 就是服务名，同一自定义网络内由内置 DNS 解析，不需要写死 IP：</p><p>同一套镜像也可以用 Compose 编排，<code>proxy_pass http://backend:8080/</code> 中的 <code>backend</code> 就是服务名，由内置 DNS 解析，不需要写死 IP：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>services</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>backend</span><span>:</span></div></div><div><div><div>3</div></div><div><span>    </span><span>image</span><span>: </span><span>registry.example.com/team/my-api:1.2.0</span></div></div><div><div><div>4</div></div><div><span>    </span><span>networks</span><span>: [</span><span>app-net</span><span>]</span></div></div><div><div><div>5</div></div><div><span>  </span><span>frontend</span><span>:</span></div></div><div><div><div>6</div></div><div><span>    </span><span>build</span><span>: { </span><span>context</span><span>: </span><span>.</span><span>, </span><span>args</span><span>: { </span><span>VITE_API_BASE_URL</span><span>: </span><span>/api</span><span> } }</span></div></div><div><div><div>7</div></div><div><span>    </span><span>ports</span><span>: [</span><span>"80:8080"</span><span>]</span></div></div><div><div><div>8</div></div><div><span>    </span><span>depends_on</span><span>:</span></div></div><div><div><div>9</div></div><div><span>      </span><span>backend</span><span>: { </span><span>condition</span><span>: </span><span>service_healthy</span><span> }</span></div></div><div><div><div>10</div></div><div><span>    </span><span>networks</span><span>: [</span><span>app-net</span><span>]</span></div></div><div><div><div>11</div></div><div><span>networks</span><span>:</span></div></div><div><div><div>12</div></div><div><span>  </span><span>app-net</span><span>: { </span><span>driver</span><span>: </span><span>bridge</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>至于体积收益：同样的应用单阶段打包是 1.1GB（<code>node:20</code>），多阶段换 <code>node:20-alpine</code> 后约 150MB，改成 Nginx 托管静态产物只需 45MB（<code>nginx:1.27-alpine</code>），再裁剪到 distroless 可压到 30MB。</p></section>
<section><h2>十二、生产最佳实践<a href="#十二生产最佳实践"><span>#</span></a></h2><p><strong>镜像瘦身。</strong> 优先用 <code>alpine</code>、<code>slim</code> 或 <code>distroless</code> 基础镜像，<code>distroless</code> 连 shell 都没有，攻击面最小，代价是排障时无法 <code>docker exec sh</code>，只能靠日志与健康检查。<code>nginx:1.27-alpine</code> 只有 45MB 而 <code>nginx:1.27</code> 有 190MB，差距全部来自基础镜像。安装与清理必须放在同一条 <code>RUN</code> 里，否则清理动作落在单独一层而失效，标准写法是 <code>RUN apt-get update &amp;&amp; apt-get install -y --no-install-recommends ca-certificates curl &amp;&amp; rm -rf /var/lib/apt/lists/*</code>。</p><p><strong>非 root 用户。</strong> 默认以 root 运行意味着容器内进程逃逸后直接拥有宿主机 root 权限。基础镜像自带非 root 用户时直接 <code>USER node</code>；否则自己创建（alpine 用 <code>RUN addgroup -S app &amp;&amp; adduser -S app -G app</code>，Debian 用 <code>useradd -r -u 10001 -m appuser</code>），再 <code>USER app</code>。运行期也能用 <code>--user 1000:1000</code> 覆盖，但要注意挂载卷的属主，卷属主是 root 时会报权限错误。</p><p><strong>固定基础镜像版本。</strong> 禁止 <code>FROM node:latest</code>，内容会漂移，今天构建成功明天可能失败。至少固定到次版本（<code>node:20.11.1-alpine3.19</code>），最严格是锁定 digest，形如 <code>FROM node@sha256:7f5c9b3a...</code>，代价是安全更新也不会自动进来，需要配合自动化的依赖升级流程。</p><p><strong>分层缓存优化。</strong> 依赖清单先行、源码最后复制；不常变的 <code>ENV</code>、<code>ARG</code> 放前面；<code>apt-get install</code> 集中写在一条指令里。CI 中启用 BuildKit 的缓存与 secret 挂载，依赖下载目录可跨构建复用且不会进入镜像层：在 Dockerfile 首行加 <code># syntax=docker/dockerfile:1.7</code>，把安装命令写成 <code>RUN --mount=type=cache,target=/root/.npm --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci --registry=https://registry.example.com</code>，构建时用 <code>docker build --secret id=npmrc,src=$HOME/.npmrc -t app:1.0 .</code> 传入私服凭证。</p><p><strong>健康检查。</strong> <code>--interval</code> 检查间隔、<code>--timeout</code> 单次超时、<code>--start-period</code> 启动宽限期（期内失败不计入 <code>--retries</code>）、<code>--retries</code> 连续失败多少次标记 unhealthy。典型写法 <code>HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 CMD curl -fsS http://127.0.0.1:8080/health || exit 1</code>。健康检查只标记状态，Docker 本身不会重启 unhealthy 的容器，需要配合 Compose 的 <code>depends_on: condition: service_healthy</code> 或编排平台的探针来驱动重启与流量摘除。</p><p><strong>日志处理。</strong> 默认的 <code>json-file</code> 驱动不做轮转，长时间运行会把 <code>/var/lib/docker</code> 撑满磁盘，这是最常见的生产事故。在 daemon.json 中把 <code>log-driver</code> 设为 <code>json-file</code> 并加上 <code>log-opts</code> 的 <code>max-size: 50m</code> 与 <code>max-file: 3</code> 即可限制单容器日志体积。更完整的方案是接 <code>fluentd</code>、<code>gelf</code> 或 <code>syslog</code> 驱动，把日志送到 ELK、Loki 之类的集中式系统。</p><p><strong>其他容易忽略的点。</strong> <code>docker stop</code> 默认超时 10 秒，应用优雅关闭更久时应显式 <code>docker stop -t 30</code>。容器内只跑一个前台进程，用 supervisord 塞多进程会带来信号转发和僵尸进程问题，正确做法是拆成多个容器。需要写入的目录声明为 <code>VOLUME</code> 或挂载卷，保持容器无状态才能随时重建。用 <code>--read-only</code> 加 <code>--tmpfs /tmp</code> 运行可显著降低被写入攻击的风险，前提是应用不写文件系统。</p></section>
<section><h2>十三、常见问题排查<a href="#十三常见问题排查"><span>#</span></a></h2><section><h3>容器启动即退出<a href="#容器启动即退出"><span>#</span></a></h3><p>先看退出码和日志，不要猜：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>ps</span><span> </span><span>-a</span><span>                             </span><span># 看 EXIT CODE</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>logs</span><span> </span><span>--tail</span><span> </span><span>100</span><span> </span><span>容器名</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>inspect</span><span> </span><span>-f</span><span> </span><span>'{{.State.Status}} {{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'</span><span> </span><span>容器名</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>run</span><span> </span><span>-it</span><span> </span><span>--entrypoint</span><span> </span><span>sh</span><span> </span><span>镜像名</span><span>    </span><span># 覆盖入口进容器查环境</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>退出码</th><th>含义</th><th>常见原因</th></tr></thead><tbody><tr><td>0</td><td>正常退出</td><td>主进程是短命令，或脚本把进程 daemon 化了</td></tr><tr><td>1</td><td>应用错误</td><td>启动异常、配置缺失、依赖不可用</td></tr><tr><td>126 / 127</td><td>无法执行 / 找不到命令</td><td>没有执行权限、缺 shebang，或命令不存在（alpine 里用了 bash）</td></tr><tr><td>137 / 139</td><td>被 SIGKILL / 段错误</td><td>通常 OOM 被内核杀掉；或二进制架构不匹配</td></tr><tr><td>143</td><td>被 SIGTERM</td><td>正常 <code>docker stop</code> 后的退出码，属预期行为</td></tr></tbody></table><p>最常见的两条：一是 Dockerfile 用了 shell 形式的 <code>CMD</code> 且脚本把主进程 daemon 化，容器没有前台进程可守；二是镜像里没有 <code>bash</code>（alpine 只有 <code>sh</code>），<code>CMD ["bash", ...]</code> 直接 127。</p></section><section><h3>端口占用<a href="#端口占用"><span>#</span></a></h3><p>报错形如 <code>bind: address already in use</code>，先用 <code>sudo ss -lntp | grep :8080</code>（Windows 用 <code>netstat -ano | findstr :8080</code>）确认占用者，再换宿主机端口启动，例如 <code>docker run -d -p 8081:80 nginx:1.27-alpine</code>。别忘检查 <code>docker ps -a</code> 里状态为 <code>Up</code> 的旧容器，它们同样占用端口。</p></section><section><h3>时区问题<a href="#时区问题"><span>#</span></a></h3><p>容器默认时区是 UTC，日志时间会比北京时间少 8 小时。最省事的是注入 <code>-e TZ=Asia/Shanghai</code>，也可以挂载宿主机时区文件 <code>-v /etc/localtime:/etc/localtime:ro</code>；若要在 Dockerfile 中固定，写 <code>ENV TZ=Asia/Shanghai</code> 与 <code>RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime &amp;&amp; echo $TZ &gt; /etc/timezone</code> 两行即可。Java 应用除 <code>TZ</code> 外还要注意 JVM 自身参数，加 <code>-Duser.timezone=Asia/Shanghai</code> 更稳妥。</p></section><section><h3>权限问题<a href="#权限问题"><span>#</span></a></h3><p>症状是 <code>Permission denied</code> 或 <code>Operation not permitted</code>，通常三个来源：容器内以非 root 运行但挂载目录属主是 root，解决方式是构建时 <code>chown</code> 或用 <code>-u "$(id -u):$(id -g)"</code> 对齐属主；宿主机开启 SELinux（CentOS/RHEL）需要给挂载加 <code>:z</code> 或 <code>:Z</code> 标签；容器内进程需要特殊 capability，默认已丢弃大部分，需要用 <code>--cap-add</code> 但应优先考虑非特权方案。调试时先 <code>docker exec -it 容器名 ls -aln /data</code> 确认属主与权限。</p></section><section><h3>磁盘占用清理<a href="#磁盘占用清理"><span>#</span></a></h3><p>Docker 占用来自四处：镜像、容器、卷、构建缓存。清理顺序是先看再删：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>docker</span><span> </span><span>system</span><span> </span><span>df</span><span>                   </span><span># 总览各部分占用，-v 看明细</span></div></div><div><div><div>2</div></div><div><span>docker</span><span> </span><span>system</span><span> </span><span>prune</span><span>                </span><span># 安全清理：停止的容器、未使用网络、悬空镜像、构建缓存</span></div></div><div><div><div>3</div></div><div><span>docker</span><span> </span><span>system</span><span> </span><span>prune</span><span> </span><span>-a</span><span> </span><span>--volumes</span><span>   </span><span># 更彻底，含未被使用的镜像与卷</span></div></div><div><div><div>4</div></div><div><span>docker</span><span> </span><span>container</span><span> </span><span>prune</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>image</span><span> </span><span>prune</span><span> </span><span>-a</span><span> &amp;&amp; </span><span>docker</span><span> </span><span>volume</span><span> </span><span>prune</span></div></div><div><div><div>5</div></div><div><span>docker</span><span> </span><span>builder</span><span> </span><span>prune</span><span> </span><span>--keep-storage</span><span> </span><span>10GB</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>docker system prune -a --volumes</code> 会删除未被使用的数据卷，数据库数据可能就在里面，执行前务必确认 <code>docker volume ls</code> 的内容。生产环境建议把 <code>data-root</code> 放在独立分区并监控磁盘水位。其他高频问题：<code>docker info | grep -A 5 "Registry Mirrors"</code> 确认加速器生效；容器内 DNS 解析看 <code>/etc/resolv.conf</code>；宿主机内网与默认网段冲突时在 daemon.json 中设 <code>"bip": "10.20.0.1/24"</code>；WSL2 磁盘膨胀需 <code>wsl --shutdown</code> 后压缩 vhdx。</p></section></section>
<section><h2>十四、命令速查表<a href="#十四命令速查表"><span>#</span></a></h2><section><h3>镜像<a href="#镜像"><span>#</span></a></h3>

<table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>docker pull nginx:1.27-alpine</code></td><td>拉取镜像</td></tr><tr><td><code>docker images</code></td><td>列出本地镜像</td></tr><tr><td><code>docker rmi 镜像:标签</code> / <code>docker tag 源 目标</code> / <code>docker push 仓库/镜像:标签</code></td><td>删除 / 打标签 / 推送镜像</td></tr><tr><td><code>docker save -o a.tar 镜像</code> / <code>docker load -i a.tar</code></td><td>导出 / 导入镜像</td></tr><tr><td><code>docker history 镜像</code> / <code>docker inspect 镜像</code></td><td>构建历史 / 镜像元数据</td></tr></tbody></table></section><section><h3>容器<a href="#容器"><span>#</span></a></h3>

<table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>docker run -d -p 8080:80 --name web nginx</code></td><td>后台运行并映射端口</td></tr><tr><td><code>docker ps</code> / <code>docker ps -a</code> / <code>docker ps -aq</code></td><td>列出运行中 / 全部 / 仅 ID</td></tr><tr><td><code>docker start/stop/restart/kill 容器</code></td><td>启动 / 停止 / 重启 / 强杀</td></tr><tr><td><code>docker rm -f 容器</code> / <code>docker container prune</code></td><td>删除容器 / 清理已退出</td></tr><tr><td><code>docker exec -it 容器 sh</code> / <code>docker logs -f --tail 100 容器</code></td><td>进入容器 / 跟踪日志</td></tr><tr><td><code>docker stats</code> / <code>docker cp 容器:路径 本地路径</code></td><td>实时资源占用 / 复制文件</td></tr></tbody></table></section><section><h3>构建<a href="#构建"><span>#</span></a></h3>

<table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>docker build -t app:1.0 .</code></td><td>构建镜像</td></tr><tr><td><code>docker build -f Dockerfile.prod --target builder -t app:1.0 .</code></td><td>指定 Dockerfile 与阶段</td></tr><tr><td><code>docker build --build-arg K=V -t app:1.0 .</code></td><td>传构建参数</td></tr><tr><td><code>docker buildx build --platform linux/amd64,linux/arm64 --push .</code></td><td>多架构构建推送</td></tr></tbody></table></section><section><h3>卷与网络<a href="#卷与网络"><span>#</span></a></h3>

<table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>docker volume create 卷名</code> / <code>ls</code> / <code>inspect 卷名</code></td><td>创建 / 列出 / 查看数据卷</td></tr><tr><td><code>docker volume rm 卷名</code> / <code>docker volume prune</code></td><td>删除 / 清理数据卷</td></tr><tr><td><code>docker network create 网络名</code></td><td>创建自定义网络</td></tr><tr><td><code>docker network connect 网络 容器</code> / <code>disconnect</code></td><td>接入 / 断开网络</td></tr></tbody></table></section><section><h3>系统与清理<a href="#系统与清理"><span>#</span></a></h3>

<table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>docker version</code> / <code>docker info</code></td><td>版本 / 环境信息</td></tr><tr><td><code>docker system df</code> / <code>docker system df -v</code></td><td>磁盘占用总览 / 明细</td></tr><tr><td><code>docker system prune</code></td><td>安全清理未使用资源</td></tr><tr><td><code>docker compose up -d</code> / <code>docker compose down</code></td><td>编排启停</td></tr><tr><td><code>docker compose logs -f 服务名</code> / <code>docker compose ps</code></td><td>服务日志 / 服务状态</td></tr></tbody></table></section></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/devops/kubernetes%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E4%B8%8E%E5%AE%9E%E6%88%98/</id>
      <title type="text">Kubernetes 核心概念与实战入门</title>
      <published>2026-09-11T00:00:00.000Z</published>
      <updated>2026-09-11T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/devops/kubernetes%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E4%B8%8E%E5%AE%9E%E6%88%98/"/>
      <summary type="text">Kubernetes 容器编排入门，涵盖集群架构、Pod/Deployment/Service/Ingress 核心对象、kubectl 实战、滚动更新与回滚、Helm 包管理</summary>
      <content type="html"><![CDATA[<p>Docker 把”打包应用”变成了标准动作，但要把应用稳定跑在生产环境里，单机容器远远不够。本文从设计动机讲到核心对象，再落到 kubectl、滚动更新、探针、资源管理、Helm 与本地实践的完整链路。</p>
<section><h2>1. 为什么需要编排<a href="#1-为什么需要编排"><span>#</span></a></h2><p>单机 Docker（或 Compose）在生产会立刻暴露以下局限：</p><ul>
<li>故障自愈：<code>--restart=always</code> 能拉起崩溃的容器，但<strong>节点整机宕机</strong>时容器不会漂移到其他机器。</li>
<li>水平扩缩容：<code>docker compose up --scale</code> 只能单机扩副本，跨节点分发要自己写脚本。</li>
<li>服务发现与负载均衡：容器重建后 IP 会变，多副本之间缺少稳定的虚拟地址、DNS 名字与统一入口。</li>
<li>滚动发布与回滚：Compose 默认”停旧起新”，做不到按比例灰度，也做不到一条命令回滚。</li>
<li>配置与密钥：环境变量散落在多个 compose 文件，无法统一注入与轮转。</li>
<li>资源隔离与存储编排：多容器抢 CPU/内存缺少调度层配额，Pod 漂移后数据卷也无法跟着走。</li>
</ul><p>Kubernetes（简称 K8s）把这些能力抽象成<strong>声明式 API</strong>：你只描述期望状态（desired state），控制器持续把实际状态（actual state）拉回期望。这与 <code>docker run</code> 的命令式思路有本质区别——K8s 关心的是”持续稳定在某个状态”。</p><p>要解决的问题可以概括为：调度、自愈、扩缩容、服务发现、负载均衡、配置管理、密钥管理、存储编排、滚动发布、批处理、多租户隔离。</p></section>
<section><h2>2. 集群架构<a href="#2-集群架构"><span>#</span></a></h2><p>集群分为<strong>控制平面</strong>（Control Plane）与<strong>工作节点</strong>（Worker Node）。</p><section><h3>2.1 控制平面组件<a href="#21-控制平面组件"><span>#</span></a></h3>

<table><thead><tr><th>组件</th><th>职责</th></tr></thead><tbody><tr><td>kube-apiserver</td><td>集群唯一入口，提供 REST API，负责认证、授权、准入控制与校验</td></tr><tr><td>etcd</td><td>分布式 KV 存储，保存集群全部状态，是唯一的真相来源</td></tr><tr><td>kube-scheduler</td><td>为尚未绑定节点的 Pod 选择运行节点</td></tr><tr><td>kube-controller-manager</td><td>运行各类控制器，做调谐（reconcile）</td></tr><tr><td>cloud-controller-manager</td><td>对接云厂商 API，管理负载均衡器、路由、节点生命周期</td></tr></tbody></table></section><section><h3>2.2 工作节点组件<a href="#22-工作节点组件"><span>#</span></a></h3>

<table><thead><tr><th>组件</th><th>职责</th></tr></thead><tbody><tr><td>kubelet</td><td>接收 PodSpec，调用 CRI 启动容器，上报状态，执行探针</td></tr><tr><td>kube-proxy</td><td>维护节点网络规则（iptables / IPVS / nftables），实现 Service 转发</td></tr><tr><td>容器运行时</td><td>真正跑容器的组件，如 containerd、CRI-O</td></tr></tbody></table><p>只有 apiserver 与 etcd 交互，其余组件都通过 watch/list 与 apiserver 通信。这种中心辐射结构让状态变更路径唯一，便于鉴权与审计。</p></section><section><h3>2.3 一次 <code>kubectl apply</code> 到 Pod 运行的完整链路<a href="#23-一次-kubectl-apply-到-pod-运行的完整链路"><span>#</span></a></h3><ol>
<li>kubectl 读取 kubeconfig，把 YAML 转成 JSON，通过 HTTPS 发给 kube-apiserver。</li>
<li>apiserver 依次执行认证（证书 / token）、授权（RBAC）、准入控制（MutatingWebhook 改值 → 校验 → ValidatingWebhook 拦截），把对象写入 etcd。此时 Deployment 已持久化。</li>
<li>Deployment controller 通过 watch 感知新对象并创建 ReplicaSet；ReplicaSet controller 再创建指定数量的 Pod，此时 <code>spec.nodeName</code> 为空、状态为 <code>Pending</code>。</li>
<li>kube-scheduler 发现未调度的 Pod，经过预选（Filter）与优选（Score）选出节点并绑定（binding），写回 <code>spec.nodeName</code>。</li>
<li>目标节点的 kubelet 感知到”有 Pod 分给我了”，调用 CRI 让运行时拉镜像、创建容器，并通过 CNI 配置网络。</li>
<li>kubelet 持续上报状态；Pod 就绪后 Endpoints/EndpointSlice 被更新，Service 才会把流量转发过来。</li>
</ol><p>每一步都是异步的 watch + 调谐，任何一环失败都会被下一轮调谐纠正，这就是 K8s “最终一致”的来源。</p></section></section>
<section><h2>3. 核心对象逐个讲<a href="#3-核心对象逐个讲"><span>#</span></a></h2><section><h3>3.1 Pod：最小调度单元<a href="#31-pod最小调度单元"><span>#</span></a></h3><p>Pod 是最小的<strong>调度与运行单元</strong>，可理解为”一个或多个共享网络命名空间的容器组合”。</p><p>为什么不直接调度容器？现实里经常需要多个进程紧密协作（日志采集、代理、配置热加载），它们要共享网络、存储与生命周期。打包成 Pod 后，调度、IP 分配、卷挂载与生命周期管理都被统一处理。Pod 内所有容器共享同一网络命名空间：共享 IP 与端口空间，可用 <code>localhost</code> 互访，共享挂载的 Volume；但<strong>不共享文件系统</strong>，进程空间也各自独立。最常见的是 <strong>sidecar</strong> 模式：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>v1</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>Pod</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>web-with-sidecar</span><span>, </span><span>labels</span><span>: { </span><span>app</span><span>: </span><span>web</span><span> } }</span></div></div><div><div><div>4</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>restartPolicy</span><span>: </span><span>Always</span></div></div><div><div><div>6</div></div><div><span>  </span><span>containers</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>web</span></div></div><div><div><div>8</div></div><div><span>      </span><span>image</span><span>: </span><span>nginx:1.27</span></div></div><div><div><div>9</div></div><div><span>      </span><span>volumeMounts</span><span>: [{ </span><span>name</span><span>: </span><span>logs</span><span>, </span><span>mountPath</span><span>: </span><span>/var/log/nginx</span><span> }]</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>log-shipper</span></div></div><div><div><div>11</div></div><div><span>      </span><span>image</span><span>: </span><span>busybox:1.36</span></div></div><div><div><div>12</div></div><div><span>      </span><span>command</span><span>: [</span><span>"sh"</span><span>, </span><span>"-c"</span><span>, </span><span>"tail -F /logs/access.log"</span><span>]</span></div></div><div><div><div>13</div></div><div><span>      </span><span>volumeMounts</span><span>: [{ </span><span>name</span><span>: </span><span>logs</span><span>, </span><span>mountPath</span><span>: </span><span>/logs</span><span> }]</span></div></div><div><div><div>14</div></div><div><span>  </span><span>volumes</span><span>:</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>logs</span></div></div><div><div><div>16</div></div><div><span>      </span><span>emptyDir</span><span>: {}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>restartPolicy</code> 有三档：<code>Always</code>（默认，常驻服务）、<code>OnFailure</code>（非 0 退出才重启，适合一次性任务）、<code>Never</code>，它作用于整个 Pod。Pod 相位包括 <code>Pending</code>、<code>Running</code>、<code>Succeeded</code>、<code>Failed</code>、<code>Unknown</code>。</p><p>生产环境几乎不直接创建裸 Pod：裸 Pod 没有自愈能力（节点故障或删除后不会重建），应交给 Deployment / StatefulSet / DaemonSet。</p></section><section><h3>3.2 Deployment：无状态应用的副本管理<a href="#32-deployment无状态应用的副本管理"><span>#</span></a></h3><p>Deployment 描述”我要 N 个副本的某个 Pod 模板，且支持滚动更新与回滚”。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>apps/v1</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>Deployment</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>web</span><span> }</span></div></div><div><div><div>4</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>replicas</span><span>: </span><span>3</span></div></div><div><div><div>6</div></div><div><span>  </span><span>selector</span><span>:</span></div></div><div><div><div>7</div></div><div><span>    </span><span>matchLabels</span><span>: { </span><span>app</span><span>: </span><span>web</span><span> }</span></div></div><div><div><div>8</div></div><div><span>  </span><span>strategy</span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>type</span><span>: </span><span>RollingUpdate</span></div></div><div><div><div>10</div></div><div><span>    </span><span>rollingUpdate</span><span>: { </span><span>maxSurge</span><span>: </span><span>1</span><span>, </span><span>maxUnavailable</span><span>: </span><span>0</span><span> }</span></div></div><div><div><div>11</div></div><div><span>  </span><span>template</span><span>:</span></div></div><div><div><div>12</div></div><div><span>    </span><span>metadata</span><span>:</span></div></div><div><div><div>13</div></div><div><span>      </span><span>labels</span><span>: { </span><span>app</span><span>: </span><span>web</span><span> }</span></div></div><div><div><div>14</div></div><div><span>    </span><span>spec</span><span>:</span></div></div><div><div><div>15</div></div><div><span>      </span><span>containers</span><span>:</span></div></div><div><div><div>16</div></div><div><span><span>        </span></span><span>- </span><span>name</span><span>: </span><span>web</span></div></div><div><div><div>17</div></div><div><span>          </span><span>image</span><span>: </span><span>nginx:1.27</span></div></div><div><div><div>18</div></div><div><span>          </span><span>ports</span><span>: [{ </span><span>containerPort</span><span>: </span><span>80</span><span> }]</span></div></div><div><div><div>19</div></div><div><span>          </span><span>resources</span><span>:</span></div></div><div><div><div>20</div></div><div><span>            </span><span>requests</span><span>: { </span><span>cpu</span><span>: </span><span>100m</span><span>, </span><span>memory</span><span>: </span><span>128Mi</span><span> }</span></div></div><div><div><div>21</div></div><div><span>            </span><span>limits</span><span>: { </span><span>cpu</span><span>: </span><span>500m</span><span>, </span><span>memory</span><span>: </span><span>256Mi</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>关键点：</p><ul>
<li>Deployment <strong>不直接创建 Pod</strong>：层级是 <code>Deployment → ReplicaSet → Pod</code>。</li>
<li>修改 Pod 模板（通常是镜像）会生成<strong>新的 ReplicaSet</strong>，旧的保留并缩容到 0，这是滚动更新的基础。</li>
<li><code>selector</code> 必须匹配 <code>template.metadata.labels</code>，否则 apiserver 直接拒绝。</li>
<li><code>maxSurge</code>：更新期间最多<strong>超出</strong>期望副本数多少；<code>maxUnavailable</code>：最多<strong>不可用</strong>多少。设为 0 表示必须先起新、就绪后再停旧，容量不下降但更新更慢。</li>
</ul><p>回滚依赖 ReplicaSet 的历史，默认保留 <code>revisionHistoryLimit=10</code> 个。</p></section><section><h3>3.3 Service：稳定的服务入口<a href="#33-service稳定的服务入口"><span>#</span></a></h3><p>Pod IP 是临时的，Service 提供稳定的虚拟 IP（ClusterIP）与 DNS 名字，并通过 label selector 找到后端 Pod，维护 Endpoints。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>v1</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>Service</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>web</span><span> }</span></div></div><div><div><div>4</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>type</span><span>: </span><span>ClusterIP</span></div></div><div><div><div>6</div></div><div><span>  </span><span>selector</span><span>: { </span><span>app</span><span>: </span><span>web</span><span> }</span></div></div><div><div><div>7</div></div><div><span>  </span><span>ports</span><span>: [{ </span><span>port</span><span>: </span><span>80</span><span>, </span><span>targetPort</span><span>: </span><span>80</span><span> }]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>四种类型对比：</p>

<table><thead><tr><th>类型</th><th>访问范围</th><th>典型用途</th><th>是否依赖云厂商</th></tr></thead><tbody><tr><td>ClusterIP</td><td>仅集群内部</td><td>服务间互调（默认）</td><td>否</td></tr><tr><td>NodePort</td><td>集群外，节点 IP + 30000-32767</td><td>测试环境、临时暴露</td><td>否</td></tr><tr><td>LoadBalancer</td><td>集群外，云负载均衡器</td><td>生产对外暴露</td><td>是（裸机需 MetalLB）</td></tr><tr><td>ExternalName</td><td>无代理，返回 DNS CNAME</td><td>映射集群外服务</td><td>否</td></tr></tbody></table><p>selector 选中 Pod 后，Endpoints controller 会把**就绪（Ready）**的 Pod IP 写入 Endpoints。没通过 readinessProbe 的 Pod 不会出现，也就收不到流量。默认转发是随机（iptables）/轮询（IPVS），不保证同一客户端落到同一 Pod；需要会话保持可设 <code>sessionAffinity: ClientIP</code> 与 <code>sessionAffinityConfig.clientIP.timeoutSeconds</code>。</p></section><section><h3>3.4 Ingress：七层路由<a href="#34-ingress七层路由"><span>#</span></a></h3><p>Service 工作在四层，只能按端口暴露；Ingress 工作在七层，能按域名、路径、Header 路由，并集中管理 TLS。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>networking.k8s.io/v1</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>Ingress</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>web-ingress</span><span> }</span></div></div><div><div><div>4</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>ingressClassName</span><span>: </span><span>nginx</span></div></div><div><div><div>6</div></div><div><span>  </span><span>tls</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>- </span><span>hosts</span><span>: [</span><span>"sakura-hu.top"</span><span>]</span></div></div><div><div><div>8</div></div><div><span>      </span><span>secretName</span><span>: </span><span>sakura-tls</span></div></div><div><div><div>9</div></div><div><span>  </span><span>rules</span><span>:</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>- </span><span>host</span><span>: </span><span>sakura-hu.top</span></div></div><div><div><div>11</div></div><div><span>      </span><span>http</span><span>:</span></div></div><div><div><div>12</div></div><div><span>        </span><span>paths</span><span>:</span></div></div><div><div><div>13</div></div><div><span><span>          </span></span><span>- </span><span>path</span><span>: </span><span>/</span></div></div><div><div><div>14</div></div><div><span>            </span><span>pathType</span><span>: </span><span>Prefix</span></div></div><div><div><div>15</div></div><div><span>            </span><span>backend</span><span>: { </span><span>service</span><span>: { </span><span>name</span><span>: </span><span>web</span><span>, </span><span>port</span><span>: { </span><span>number</span><span>: </span><span>80</span><span> } } }</span></div></div><div><div><div>16</div></div><div><span><span>          </span></span><span>- </span><span>path</span><span>: </span><span>/api</span></div></div><div><div><div>17</div></div><div><span>            </span><span>pathType</span><span>: </span><span>Prefix</span></div></div><div><div><div>18</div></div><div><span>            </span><span>backend</span><span>: { </span><span>service</span><span>: { </span><span>name</span><span>: </span><span>api</span><span>, </span><span>port</span><span>: { </span><span>number</span><span>: </span><span>8080</span><span> } } }</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>要点：</p><ul>
<li>Ingress 资源本身<strong>只是路由规则</strong>，必须有 <strong>Ingress Controller</strong>（ingress-nginx、Traefik、Envoy Gateway）去实现它。没装 Controller，规则就是一条没人执行的死配置。</li>
<li>后端是 <strong>Service</strong> 而不是 Pod：<code>Ingress → Service → Endpoints → Pod</code>。</li>
<li><code>pathType</code> 有 <code>Prefix</code>、<code>Exact</code>、<code>ImplementationSpecific</code>；TLS 证书通常放在 Secret 里，或用 cert-manager 自动签发。</li>
</ul></section><section><h3>3.5 ConfigMap 与 Secret：配置与密钥<a href="#35-configmap-与-secret配置与密钥"><span>#</span></a></h3><p>ConfigMap 存非敏感配置，Secret 存敏感信息，用法几乎一致，但 Secret 需要 <strong>base64 编码</strong>、常用于 TLS/镜像拉取，并可开启静态加密。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>v1</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>ConfigMap</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>web-config</span><span> }</span></div></div><div><div><div>4</div></div><div><span>data</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>APP_ENV</span><span>: </span><span>production</span></div></div><div><div><div>6</div></div><div><span>  </span><span>nginx.conf</span><span>: </span><span>|</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>server {</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>listen 80;</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>location / { root /usr/share/nginx/html; }</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>---</span></div></div><div><div><div>12</div></div><div><span>apiVersion</span><span>: </span><span>v1</span></div></div><div><div><div>13</div></div><div><span>kind</span><span>: </span><span>Secret</span></div></div><div><div><div>14</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>db-secret</span><span> }</span></div></div><div><div><div>15</div></div><div><span>type</span><span>: </span><span>Opaque</span></div></div><div><div><div>16</div></div><div><span>data</span><span>:</span></div></div><div><div><div>17</div></div><div><span>  </span><span>username</span><span>: </span><span>cm9vdA==</span><span>        </span><span># echo -n 'root' | base64</span></div></div><div><div><div>18</div></div><div><span>  </span><span>password</span><span>: </span><span>c2VjcmV0MTIz</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><strong>关键认知：base64 是编码，不是加密。</strong> 任何能读到该对象的人一行命令就能解出来：<code>kubectl get secret db-secret -o jsonpath='{.data.password}' | base64 -d</code>。所以 Secret 的安全性靠 RBAC、etcd 静态加密（EncryptionConfiguration）与外部密钥管理（Vault、Sealed Secrets、External Secrets Operator），而不是 base64。</p><p>使用方式有两种，注入环境变量或挂载为文件：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>containers</span><span>:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>web</span></div></div><div><div><div>4</div></div><div><span>      </span><span>image</span><span>: </span><span>nginx:1.27</span></div></div><div><div><div>5</div></div><div><span>      </span><span>envFrom</span><span>: [{ </span><span>configMapRef</span><span>: { </span><span>name</span><span>: </span><span>web-config</span><span> } }]</span></div></div><div><div><div>6</div></div><div><span>      </span><span>env</span><span>:</span></div></div><div><div><div>7</div></div><div><span><span>        </span></span><span>- </span><span>name</span><span>: </span><span>DB_PASSWORD</span></div></div><div><div><div>8</div></div><div><span>          </span><span>valueFrom</span><span>: { </span><span>secretKeyRef</span><span>: { </span><span>name</span><span>: </span><span>db-secret</span><span>, </span><span>key</span><span>: </span><span>password</span><span> } }</span></div></div><div><div><div>9</div></div><div><span>      </span><span>volumeMounts</span><span>: [{ </span><span>name</span><span>: </span><span>config-vol</span><span>, </span><span>mountPath</span><span>: </span><span>/etc/nginx/conf.d</span><span> }]</span></div></div><div><div><div>10</div></div><div><span>  </span><span>volumes</span><span>:</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>config-vol</span></div></div><div><div><div>12</div></div><div><span>      </span><span>configMap</span><span>:</span></div></div><div><div><div>13</div></div><div><span>        </span><span>name</span><span>: </span><span>web-config</span></div></div><div><div><div>14</div></div><div><span>        </span><span>items</span><span>: [{ </span><span>key</span><span>: </span><span>nginx.conf</span><span>, </span><span>path</span><span>: </span><span>default.conf</span><span> }]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>用 Volume 挂载时，ConfigMap/Secret 更新后文件会自动同步（kubelet 周期性刷新），而<strong>环境变量方式不会更新</strong>，必须重建 Pod——这是”改了配置没生效”的最常见原因。常见 Secret 类型有 <code>Opaque</code>、<code>kubernetes.io/tls</code>、<code>kubernetes.io/dockerconfigjson</code>、<code>kubernetes.io/service-account-token</code>、<code>kubernetes.io/basic-auth</code>。</p></section><section><h3>3.6 Namespace、Label 与 Selector、Annotation<a href="#36-namespacelabel-与-selectorannotation"><span>#</span></a></h3><ul>
<li><strong>Namespace</strong>：逻辑隔离单位，用于区分环境或团队。它<strong>不是网络隔离</strong>，跨 Namespace 默认互通，要靠 NetworkPolicy。</li>
<li><strong>Label</strong>：键值对，用于<strong>被选择</strong>，是 Service、Deployment、NetworkPolicy 的筛选依据，如 <code>app=web</code>。</li>
<li><strong>Selector</strong>：<code>matchLabels</code> 精确匹配，<code>matchExpressions</code> 支持 <code>In</code>/<code>NotIn</code>/<code>Exists</code>/<code>DoesNotExist</code>；kubectl 里写作 <code>-l app=web,tier!=db</code>。</li>
<li><strong>Annotation</strong>：也是键值对，但<strong>不可被选择</strong>，存放工具元数据，如 ingress 的 <code>nginx.ingress.kubernetes.io/rewrite-target</code>。</li>
</ul><p>一句话区分：<strong>Label 给机器选，Annotation 给人/工具读。</strong></p></section><section><h3>3.7 存储：从 emptyDir 到 StorageClass<a href="#37-存储从-emptydir-到-storageclass"><span>#</span></a></h3>

<table><thead><tr><th>卷类型</th><th>生命周期</th><th>适用场景</th></tr></thead><tbody><tr><td>emptyDir</td><td>随 Pod 创建与删除</td><td>容器间临时共享，缓存、日志中转</td></tr><tr><td>hostPath</td><td>绑定节点</td><td>单节点测试、访问宿主机路径（生产慎用）</td></tr><tr><td>PersistentVolume (PV)</td><td>集群级资源</td><td>管理员预先或动态提供的存储</td></tr><tr><td>PersistentVolumeClaim (PVC)</td><td>用户申请</td><td>声明”我要 10Gi、ReadWriteOnce”</td></tr><tr><td>StorageClass</td><td>供给模板</td><td>动态创建 PV，指定磁盘类型与回收策略</td></tr></tbody></table><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>v1</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>PersistentVolumeClaim</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>data-pvc</span><span> }</span></div></div><div><div><div>4</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>accessModes</span><span>: [</span><span>"ReadWriteOnce"</span><span>]</span></div></div><div><div><div>6</div></div><div><span>  </span><span>storageClassName</span><span>: </span><span>standard</span></div></div><div><div><div>7</div></div><div><span>  </span><span>resources</span><span>: { </span><span>requests</span><span>: { </span><span>storage</span><span>: </span><span>10Gi</span><span> } }</span></div></div><div><div><div>8</div></div><div><span>---</span></div></div><div><div><div>9</div></div><div><span># 在 Pod 中引用该 PVC</span></div></div><div><div><div>10</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>11</div></div><div><span>  </span><span>volumes</span><span>:</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>data</span></div></div><div><div><div>13</div></div><div><span>      </span><span>persistentVolumeClaim</span><span>: { </span><span>claimName</span><span>: </span><span>data-pvc</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>访问模式有 <code>ReadWriteOnce</code>（单节点读写）、<code>ReadOnlyMany</code>（多节点只读）、<code>ReadWriteMany</code>（多节点读写，取决于存储插件）；回收策略有 <code>Retain</code>、<code>Delete</code>（<code>Recycle</code> 已废弃）。</p><p>串成一句话：<strong>Pod 通过 PVC 申请存储，PVC 绑定 PV，PV 由 StorageClass 动态供给。</strong> 有状态应用注意：Deployment 的多个副本共享同一 PVC 会出问题，应使用 StatefulSet + volumeClaimTemplates，让每个副本拿到独立 PVC。</p></section></section>
<section><h2>4. kubectl 常用命令<a href="#4-kubectl-常用命令"><span>#</span></a></h2><p>查看、诊断与操作：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>pods</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>-o</span><span> </span><span>wide</span><span>                 </span><span># 列表，带节点/IP</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>deploy,svc,ing</span><span> </span><span>-n</span><span> </span><span>prod</span><span>               </span><span># 一次看多种资源</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>describe</span><span> </span><span>pod</span><span> </span><span>web-abc</span><span> </span><span>-n</span><span> </span><span>prod</span><span>             </span><span># 详细事件（排查第一站）</span></div></div><div><div><div>4</div></div><div><span>kubectl</span><span> </span><span>logs</span><span> </span><span>web-abc</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>-f</span><span> </span><span>--tail=100</span><span>       </span><span># 实时日志</span></div></div><div><div><div>5</div></div><div><span>kubectl</span><span> </span><span>exec</span><span> </span><span>-it</span><span> </span><span>web-abc</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>--</span><span> </span><span>sh</span><span>           </span><span># 进入容器</span></div></div><div><div><div>6</div></div><div><span>kubectl</span><span> </span><span>top</span><span> </span><span>pod</span><span> </span><span>-n</span><span> </span><span>prod</span><span>                          </span><span># 资源占用（需 metrics-server）</span></div></div><div><div><div>7</div></div><div><span>kubectl</span><span> </span><span>apply</span><span> </span><span>-f</span><span> </span><span>deploy.yaml</span><span>                     </span><span># 声明式创建/更新（推荐）</span></div></div><div><div><div>8</div></div><div><span>kubectl</span><span> </span><span>delete</span><span> </span><span>-f</span><span> </span><span>deploy.yaml</span><span>                    </span><span># 按清单删除</span></div></div><div><div><div>9</div></div><div><span>kubectl</span><span> </span><span>scale</span><span> </span><span>deploy/web</span><span> </span><span>--replicas=5</span><span> </span><span>-n</span><span> </span><span>prod</span><span>    </span><span># 手动扩缩容</span></div></div><div><div><div>10</div></div><div><span>kubectl</span><span> </span><span>port-forward</span><span> </span><span>svc/web</span><span> </span><span>8080:80</span><span> </span><span>-n</span><span> </span><span>prod</span><span>     </span><span># 本地转发调试</span></div></div><div><div><div>11</div></div><div><span>kubectl</span><span> </span><span>cp</span><span> </span><span>prod/web-abc:/etc/nginx/nginx.conf</span><span> </span><span>./nginx.conf</span></div></div><div><div><div>12</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>events</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>--sort-by=.metadata.creationTimestamp</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常用参数：</p>

<table><thead><tr><th>参数</th><th>含义</th></tr></thead><tbody><tr><td><code>-o wide</code></td><td>输出额外列（节点、IP）</td></tr><tr><td><code>-o yaml</code> / <code>-o json</code></td><td>输出完整对象，对照字段</td></tr><tr><td><code>-o jsonpath='{.spec.replicas}'</code></td><td>精确取值，写脚本首选</td></tr><tr><td><code>-n NAME</code></td><td>指定 Namespace，<code>-A</code> 为全部</td></tr><tr><td><code>-l app=web</code></td><td>按 Label 过滤</td></tr><tr><td><code>--watch</code> / <code>-w</code></td><td>持续监听变化</td></tr><tr><td><code>--dry-run=client -o yaml</code></td><td>只生成 YAML 不提交，用来抄模板</td></tr></tbody></table><p>不确定资源怎么写时，先用 <code>kubectl create deploy web --image=nginx --dry-run=client -o yaml</code> 生成骨架，再改成声明式文件。</p></section>
<section><h2>5. YAML 清单规范<a href="#5-yaml-清单规范"><span>#</span></a></h2><p>每个清单至少有四个顶层字段：<code>apiVersion</code>、<code>kind</code>、<code>metadata</code>、<code>spec</code>（部分对象用 <code>data</code> / <code>rules</code> 替代 spec）。</p><p>常见 apiVersion 与 kind 对应：</p>

<table><thead><tr><th>kind</th><th>apiVersion</th></tr></thead><tbody><tr><td>Pod / Service / ConfigMap / Secret / Namespace / PV / PVC</td><td>v1</td></tr><tr><td>Deployment / ReplicaSet / StatefulSet / DaemonSet</td><td>apps/v1</td></tr><tr><td>Job / CronJob</td><td>batch/v1</td></tr><tr><td>Ingress / NetworkPolicy</td><td>networking.k8s.io/v1</td></tr><tr><td>HorizontalPodAutoscaler</td><td>autoscaling/v2</td></tr><tr><td>Role / RoleBinding / ClusterRole / ClusterRoleBinding</td><td>rbac.authorization.k8s.io/v1</td></tr><tr><td>PodDisruptionBudget</td><td>policy/v1</td></tr></tbody></table><p>书写注意：缩进用<strong>空格</strong>不要用 Tab，同级字段缩进必须一致；列表项用 <code>-</code>；字符串里的 <code>:</code> 或特殊字符建议加引号；一个文件可用 <code>---</code> 分隔多个对象。</p><p>查字段最快的方式不是搜索引擎，而是 apiserver 自带的文档：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>explain</span><span> </span><span>deploy.spec.strategy.rollingUpdate</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>explain</span><span> </span><span>pod.spec.containers.lifecycle</span><span> </span><span>--recursive</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>api-resources</span><span>   </span><span># 所有资源及短名</span></div></div><div><div><div>4</div></div><div><span>kubectl</span><span> </span><span>api-versions</span><span>    </span><span># 所有 API 组版本</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>6. 滚动更新与回滚实战<a href="#6-滚动更新与回滚实战"><span>#</span></a></h2><p>把 <code>web</code> 这个 Deployment 的镜像从 <code>1.27</code> 升到 <code>1.28</code>：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>deploy</span><span> </span><span>web</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>-o</span><span> </span><span>jsonpath='{.spec.template.spec.containers[0].image}'</span><span>   </span><span># 记录当前版本</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>set</span><span> </span><span>image</span><span> </span><span>deploy/web</span><span> </span><span>web=nginx:1.28</span><span> </span><span>-n</span><span> </span><span>prod</span><span>    </span><span># 更新镜像，触发滚动更新</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>status</span><span> </span><span>deploy/web</span><span> </span><span>-n</span><span> </span><span>prod</span><span>              </span><span># 观察新 RS 扩容、旧 RS 缩容</span></div></div><div><div><div>4</div></div><div><span>kubectl</span><span> </span><span>get</span><span> </span><span>rs</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>-l</span><span> </span><span>app=web</span><span>                      </span><span># 查看两个 RS 的副本数变化</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>kubectl set image</code> 本质是修改 <code>spec.template</code>，Deployment controller 因此创建新 ReplicaSet，并按 <code>maxSurge</code> / <code>maxUnavailable</code> 逐步扩新缩旧。过程中可以随时暂停：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>pause</span><span>  </span><span>deploy/web</span><span> </span><span>-n</span><span> </span><span>prod</span><span>    </span><span># 先滚一部分，观察指标与错误率</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>resume</span><span> </span><span>deploy/web</span><span> </span><span>-n</span><span> </span><span>prod</span><span>    </span><span># 没问题再恢复</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>暂停常用于金丝雀思路。更规范的做法是用两个 Deployment 或引入 Argo Rollouts / Flagger。出问题时回滚：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>history</span><span> </span><span>deploy/web</span><span> </span><span>-n</span><span> </span><span>prod</span><span>                 </span><span># 所有 revision</span></div></div><div><div><div>2</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>history</span><span> </span><span>deploy/web</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>--revision=3</span><span>    </span><span># 某个 revision 的细节</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>undo</span><span> </span><span>deploy/web</span><span> </span><span>-n</span><span> </span><span>prod</span><span>                    </span><span># 回到上一个可用版本</span></div></div><div><div><div>4</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>undo</span><span> </span><span>deploy/web</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>--to-revision=1</span><span>    </span><span># 回到指定 revision</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>两点补充：给 revision 加可读备注用 <code>kubectl annotate deploy/web kubernetes.io/change-cause="upgrade nginx to 1.28"</code>；<code>rollout undo</code> 的实现是<strong>把旧 ReplicaSet 的 Pod 模板重新写回 Deployment</strong>，会再生成一个新 revision——不是”回到过去”，而是”用旧模板再滚一次”。</p></section>
<section><h2>7. 健康检查<a href="#7-健康检查"><span>#</span></a></h2><p>三种探针职责不同：</p>

<table><thead><tr><th>探针</th><th>回答的问题</th><th>失败后果</th></tr></thead><tbody><tr><td>livenessProbe</td><td>进程还活着吗？</td><td>达阈值后<strong>重启容器</strong></td></tr><tr><td>readinessProbe</td><td>现在能接流量吗？</td><td><strong>从 Endpoints 摘除</strong>，不重启</td></tr><tr><td>startupProbe</td><td>启动完了吗？</td><td>未成功前<strong>抑制</strong> liveness/readiness</td></tr></tbody></table><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>containers</span><span>:</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>- </span><span>name</span><span>: </span><span>web</span></div></div><div><div><div>4</div></div><div><span>      </span><span>image</span><span>: </span><span>nginx:1.28</span></div></div><div><div><div>5</div></div><div><span>      </span><span>startupProbe</span><span>:</span></div></div><div><div><div>6</div></div><div><span>        </span><span>httpGet</span><span>: { </span><span>path</span><span>: </span><span>/</span><span>, </span><span>port</span><span>: </span><span>80</span><span> }</span></div></div><div><div><div>7</div></div><div><span>        </span><span>failureThreshold</span><span>: </span><span>30</span></div></div><div><div><div>8</div></div><div><span>        </span><span>periodSeconds</span><span>: </span><span>10</span></div></div><div><div><div>9</div></div><div><span>      </span><span>livenessProbe</span><span>:</span></div></div><div><div><div>10</div></div><div><span>        </span><span>httpGet</span><span>: { </span><span>path</span><span>: </span><span>/healthz</span><span>, </span><span>port</span><span>: </span><span>80</span><span> }</span></div></div><div><div><div>11</div></div><div><span>        </span><span>initialDelaySeconds</span><span>: </span><span>10</span></div></div><div><div><div>12</div></div><div><span>        </span><span>periodSeconds</span><span>: </span><span>10</span></div></div><div><div><div>13</div></div><div><span>        </span><span>failureThreshold</span><span>: </span><span>3</span></div></div><div><div><div>14</div></div><div><span>      </span><span>readinessProbe</span><span>:</span></div></div><div><div><div>15</div></div><div><span>        </span><span>httpGet</span><span>: { </span><span>path</span><span>: </span><span>/ready</span><span>, </span><span>port</span><span>: </span><span>80</span><span> }</span></div></div><div><div><div>16</div></div><div><span>        </span><span>periodSeconds</span><span>: </span><span>5</span></div></div><div><div><div>17</div></div><div><span>        </span><span>failureThreshold</span><span>: </span><span>2</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>三种探测方式：<code>httpGet</code>（返回 200-399 视为成功，最常用）、<code>tcpSocket</code>（端口能连上即成功）、<code>exec</code>（命令退出码为 0 即成功）。关键参数：<code>initialDelaySeconds</code>（首检延迟）、<code>periodSeconds</code>（周期）、<code>timeoutSeconds</code>（超时）、<code>successThreshold</code>（连续成功几次算就绪）、<code>failureThreshold</code>（连续失败几次算失败）。</p><p>两个经典翻车场景：</p><ol>
<li><strong>探针太激进反复重启。</strong> 应用冷启动要 60 秒，<code>initialDelaySeconds</code> 只给 5 秒，liveness 阈值又小，容器刚启动就被判”死了”并重启，陷入 CrashLoopBackOff。正确做法是<strong>用 startupProbe 兜住启动期</strong>，让 liveness 在启动完成后才开始计时。</li>
<li><strong>没配 readinessProbe，流量打到未就绪 Pod。</strong> 容器一 Running 就进 Endpoints，但应用还在加载缓存或连数据库，用户直接 500。正确做法是给 readiness 一个能真实反映”可服务”的检查点。</li>
</ol><p>反过来说，livenessProbe 也不该依赖外部依赖（比如数据库）：数据库抖一下会让所有 Pod 被判死并同时重启，造成雪崩。记住：<strong>“我活着”用 liveness，“我能干活”用 readiness。</strong></p></section>
<section><h2>8. 资源管理<a href="#8-资源管理"><span>#</span></a></h2><ul>
<li><code>requests</code>：调度依据，也是 cgroup 的 <code>cpu.shares</code> 参考。调度器把 Pod 放到”剩余可分配 requests 足够”的节点。</li>
<li><code>limits</code>：运行上限。CPU 超限被<strong>节流（throttle）</strong>，内存超限直接 <strong>OOMKilled</strong>。</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>resources</span><span>:</span></div></div><div><div><div>2</div></div><div><span>  </span><span>requests</span><span>: { </span><span>cpu</span><span>: </span><span>250m</span><span>, </span><span>memory</span><span>: </span><span>256Mi</span><span> }</span></div></div><div><div><div>3</div></div><div><span>  </span><span>limits</span><span>:   { </span><span>cpu</span><span>: </span><span>500m</span><span>, </span><span>memory</span><span>: </span><span>512Mi</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>单位：CPU 的 <code>1</code> = 1 个核心，<code>1000m</code> = 1 核，<code>250m</code> = 0.25 核，CPU 是可压缩资源；内存 <code>Mi</code> = 1024×1024 字节，<code>M</code> = 1000×1000 字节，<code>Gi</code> 同理，内存是<strong>不可压缩资源</strong>，超了只能杀。</p><p>QoS 等级由 requests/limits 的配置方式决定：</p>

<table><thead><tr><th>QoS 等级</th><th>条件</th><th>驱逐优先级</th></tr></thead><tbody><tr><td>Guaranteed</td><td>每个容器都设了 cpu/memory 且 requests 等于 limits</td><td>最低（最后被驱逐）</td></tr><tr><td>Burstable</td><td>设置了 requests，但不满足 Guaranteed</td><td>中等</td></tr><tr><td>BestEffort</td><td>完全没设 requests/limits</td><td>最高（最先被驱逐）</td></tr></tbody></table><p>节点内存压力大时，kubelet 按 <code>BestEffort → Burstable → Guaranteed</code> 的顺序驱逐；同一等级内，超出 requests 越多的越先被驱逐。</p><p>实践建议：requests 按<strong>稳态占用</strong>设置，limits 按<strong>峰值容忍</strong>设置，两者别相等（失去弹性）也别差太多（突发就被节流）；关键服务设 Guaranteed，批处理设 Burstable 即可。排障方面：<code>kubectl describe pod</code> 出现 <code>Last State: Terminated, Reason: OOMKilled</code> 说明内存超限，结合 <code>kubectl top pod</code> 与容器内真实曲线判断是 limits 太小还是内存泄漏；CPU 节流则看 cgroup 的 <code>cpu.stat</code> 中 <code>nr_throttled</code> / <code>throttled_time</code>。</p></section>
<section><h2>9. 自动扩缩容<a href="#9-自动扩缩容"><span>#</span></a></h2><p>HorizontalPodAutoscaler（HPA）根据指标自动调整 Deployment / StatefulSet / ReplicaSet 的副本数，<strong>只做水平扩缩</strong>（加减副本），不改 resources。原理是 HPA controller 每 15 秒（默认）拉一次指标，计算 <code>期望副本数 = ceil(当前副本数 × 当前指标 / 目标指标)</code>，再更新 Deployment 的 <code>replicas</code>。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>apiVersion</span><span>: </span><span>autoscaling/v2</span></div></div><div><div><div>2</div></div><div><span>kind</span><span>: </span><span>HorizontalPodAutoscaler</span></div></div><div><div><div>3</div></div><div><span>metadata</span><span>: { </span><span>name</span><span>: </span><span>web-hpa</span><span>, </span><span>namespace</span><span>: </span><span>prod</span><span> }</span></div></div><div><div><div>4</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>scaleTargetRef</span><span>: { </span><span>apiVersion</span><span>: </span><span>apps/v1</span><span>, </span><span>kind</span><span>: </span><span>Deployment</span><span>, </span><span>name</span><span>: </span><span>web</span><span> }</span></div></div><div><div><div>6</div></div><div><span>  </span><span>minReplicas</span><span>: </span><span>2</span></div></div><div><div><div>7</div></div><div><span>  </span><span>maxReplicas</span><span>: </span><span>10</span></div></div><div><div><div>8</div></div><div><span>  </span><span>metrics</span><span>:</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>- </span><span>type</span><span>: </span><span>Resource</span></div></div><div><div><div>10</div></div><div><span>      </span><span>resource</span><span>: { </span><span>name</span><span>: </span><span>cpu</span><span>, </span><span>target</span><span>: { </span><span>type</span><span>: </span><span>Utilization</span><span>, </span><span>averageUtilization</span><span>: </span><span>60</span><span> } }</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>- </span><span>type</span><span>: </span><span>Resource</span></div></div><div><div><div>12</div></div><div><span>      </span><span>resource</span><span>: { </span><span>name</span><span>: </span><span>memory</span><span>, </span><span>target</span><span>: { </span><span>type</span><span>: </span><span>Utilization</span><span>, </span><span>averageUtilization</span><span>: </span><span>75</span><span> } }</span></div></div><div><div><div>13</div></div><div><span>  </span><span>behavior</span><span>:</span></div></div><div><div><div>14</div></div><div><span>    </span><span>scaleDown</span><span>: { </span><span>stabilizationWindowSeconds</span><span>: </span><span>300</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>要点：</p><ul>
<li>HPA 依赖 <strong>metrics-server</strong> 提供 CPU/内存指标。没装会报 <code>unable to get metrics</code>，<code>kubectl top</code> 也无法工作。</li>
<li><code>averageUtilization: 60</code> 指所有 Pod 的平均 CPU 使用率相对 requests 达到 60% 时扩容，<strong>因此必须设置 requests</strong>，否则无法计算百分比。</li>
<li>自定义指标（QPS、队列长度）需要 Prometheus Adapter 或 KEDA，通过 custom / external metrics API 暴露。</li>
<li><code>behavior.stabilizationWindowSeconds</code> 是缩容冷却窗口，防止指标抖动导致副本数上下横跳。</li>
<li>节点层面还需扩容：Cluster Autoscaler（云上）或 Karpenter 在 Pod 因资源不足 Pending 时增加节点。</li>
<li>垂直方向有 VPA，但调整 requests 需要重建 Pod，与 HPA 用在同一指标上会互相打架。</li>
</ul></section>
<section><h2>10. Helm：包管理<a href="#10-helm包管理"><span>#</span></a></h2><p>原生 YAML 的问题是同一套应用要部署到多个环境，镜像 tag、副本数、域名都不同，只能复制多份或手工改，容易漂移。Helm 用<strong>模板 + 变量</strong>解决，并提供版本化的发布管理。</p><p>Chart 目录结构：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>mychart/</span></div></div><div><div><div>2</div></div><div><span>├── Chart.yaml          # 元信息：name、version、appVersion、依赖</span></div></div><div><div><div>3</div></div><div><span>├── values.yaml         # 默认变量</span></div></div><div><div><div>4</div></div><div><span>├── charts/             # 子 Chart（依赖）</span></div></div><div><div><div>5</div></div><div><span>└── templates/</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>├── deployment.yaml</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>├── service.yaml</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>├── _helpers.tpl    # 可复用的模板片段</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>└── NOTES.txt       # 安装后提示信息</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>模板渲染与对应的 <code>values.yaml</code>：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># templates/deployment.yaml（节选）</span></div></div><div><div><div>2</div></div><div><span>apiVersion</span><span>: </span><span>apps/v1</span></div></div><div><div><div>3</div></div><div><span>kind</span><span>: </span><span>Deployment</span></div></div><div><div><div>4</div></div><div><span>metadata</span><span>:</span></div></div><div><div><div>5</div></div><div><span>  </span><span>name</span><span>: {{ </span><span>include "mychart.fullname" .</span><span> }}</span></div></div><div><div><div>6</div></div><div><span>spec</span><span>:</span></div></div><div><div><div>7</div></div><div><span>  </span><span>replicas</span><span>: {{ </span><span>.Values.replicaCount</span><span> }}</span></div></div><div><div><div>8</div></div><div><span>  </span><span>template</span><span>:</span></div></div><div><div><div>9</div></div><div><span>    </span><span>spec</span><span>:</span></div></div><div><div><div>10</div></div><div><span>      </span><span>containers</span><span>:</span></div></div><div><div><div>11</div></div><div><span><span>        </span></span><span>- </span><span>name</span><span>: {{ </span><span>.Chart.Name</span><span> }}</span></div></div><div><div><div>12</div></div><div><span>          </span><span>image</span><span>: </span><span>"{{ .Values.image.repository }}:{{ .Values.image.tag }}"</span></div></div><div><div><div>13</div></div><div><span>          </span><span>resources</span><span>:</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>{{- </span><span>toYaml .Values.resources | nindent 12</span><span> }}</span></div></div><div><div><div>15</div></div><div><span>---</span></div></div><div><div><div>16</div></div><div><span># values.yaml</span></div></div><div><div><div>17</div></div><div><span>replicaCount</span><span>: </span><span>3</span></div></div><div><div><div>18</div></div><div><span>image</span><span>: { </span><span>repository</span><span>: </span><span>nginx</span><span>, </span><span>tag</span><span>: </span><span>"1.28"</span><span> }</span></div></div><div><div><div>19</div></div><div><span>resources</span><span>:</span></div></div><div><div><div>20</div></div><div><span>  </span><span>requests</span><span>: { </span><span>cpu</span><span>: </span><span>100m</span><span>, </span><span>memory</span><span>: </span><span>128Mi</span><span> }</span></div></div><div><div><div>21</div></div><div><span>  </span><span>limits</span><span>: { </span><span>cpu</span><span>: </span><span>500m</span><span>, </span><span>memory</span><span>: </span><span>256Mi</span><span> }</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>常用命令：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>helm</span><span> </span><span>create</span><span> </span><span>mychart</span><span>                              </span><span># 生成骨架</span></div></div><div><div><div>2</div></div><div><span>helm</span><span> </span><span>template</span><span> </span><span>mychart</span><span> </span><span>-f</span><span> </span><span>values-prod.yaml</span><span>        </span><span># 本地渲染，不安装（调试首选）</span></div></div><div><div><div>3</div></div><div><span>helm</span><span> </span><span>install</span><span> </span><span>web</span><span> </span><span>./mychart</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>--create-namespace</span></div></div><div><div><div>4</div></div><div><span>helm</span><span> </span><span>upgrade</span><span> </span><span>web</span><span> </span><span>./mychart</span><span> </span><span>-n</span><span> </span><span>prod</span><span> </span><span>-f</span><span> </span><span>values-prod.yaml</span></div></div><div><div><div>5</div></div><div><span>helm</span><span> </span><span>rollback</span><span> </span><span>web</span><span> </span><span>2</span><span> </span><span>-n</span><span> </span><span>prod</span><span>                      </span><span># 回滚到第 2 个 revision</span></div></div><div><div><div>6</div></div><div><span>helm</span><span> </span><span>list</span><span> </span><span>-n</span><span> </span><span>prod</span><span> &amp;&amp; </span><span>helm</span><span> </span><span>history</span><span> </span><span>web</span><span> </span><span>-n</span><span> </span><span>prod</span><span>    </span><span># 查看已发布与发布历史</span></div></div><div><div><div>7</div></div><div><span>helm</span><span> </span><span>uninstall</span><span> </span><span>web</span><span> </span><span>-n</span><span> </span><span>prod</span></div></div><div><div><div>8</div></div><div><span>helm</span><span> </span><span>lint</span><span> </span><span>./mychart</span><span>                              </span><span># 静态检查</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>自己写一个简单 Chart 的要点：</p><ol>
<li><code>Chart.yaml</code> 里 <code>version</code> 是 Chart 版本，<code>appVersion</code> 是应用版本，两者独立。</li>
<li>所有可变量都放进 <code>values.yaml</code>，模板里用 <code>.Values.xxx</code> 引用，不要硬编码。</li>
<li>用 <code>_helpers.tpl</code> 定义 <code>fullname</code>、<code>labels</code> 等公共片段，避免重复；注意缩进陷阱，配合 <code>nindent</code> / <code>indent</code> / <code>toYaml</code> 使用。</li>
<li>先用 <code>helm template</code> 在本地渲染检查，确认输出无误再 install；敏感值不要写进 <code>values.yaml</code>，用 <code>--set</code> 或外部 Secret 注入。</li>
</ol><p>Helm 3 相比 Helm 2 移除了 Tiller，权限直接走调用者的 kubeconfig，默认更安全。</p></section>
<section><h2>11. 本地实践<a href="#11-本地实践"><span>#</span></a></h2>

<table><thead><tr><th>方案</th><th>定位</th><th>多节点</th><th>适用场景</th></tr></thead><tbody><tr><td>minikube</td><td>单机完整集群，功能全</td><td>可选</td><td>学习、验证多组件功能</td></tr><tr><td>kind</td><td>Docker 里跑 K8s 节点容器</td><td>支持</td><td>CI、本地测试、K8s 自身开发</td></tr><tr><td>k3s</td><td>轻量发行版，单二进制</td><td>支持</td><td>边缘、树莓派、轻量实验</td></tr><tr><td>Docker Desktop K8s</td><td>桌面一键开启</td><td>单节点</td><td>日常开发最省事</td></tr></tbody></table><p>用 kind 起一个本地集群，并部署第一个应用：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>brew</span><span> </span><span>install</span><span> </span><span>kind</span><span> </span><span>kubectl</span><span>                   </span><span># 安装</span></div></div><div><div><div>2</div></div><div><span>kind</span><span> </span><span>create</span><span> </span><span>cluster</span><span> </span><span>--name</span><span> </span><span>dev</span><span>              </span><span># 创建集群（默认单节点）</span></div></div><div><div><div>3</div></div><div><span>kubectl</span><span> </span><span>cluster-info</span><span> </span><span>--context</span><span> </span><span>kind-dev</span><span> &amp;&amp; </span><span>kubectl</span><span> </span><span>get</span><span> </span><span>nodes</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>kubectl</span><span> </span><span>create</span><span> </span><span>deployment</span><span> </span><span>hello</span><span> </span><span>--image=nginx:1.28</span><span> </span><span>--dry-run=client</span><span> </span><span>-o</span><span> </span><span>yaml</span><span> &gt; </span><span>hello.yaml</span></div></div><div><div><div>6</div></div><div><span>kubectl</span><span> </span><span>expose</span><span> </span><span>deployment</span><span> </span><span>hello</span><span> </span><span>--port=80</span><span> </span><span>--type=NodePort</span><span> </span><span>--dry-run=client</span><span> </span><span>-o</span><span> </span><span>yaml</span><span> &gt;&gt; </span><span>hello.yaml</span></div></div><div><div><div>7</div></div><div><span>kubectl</span><span> </span><span>apply</span><span> </span><span>-f</span><span> </span><span>hello.yaml</span></div></div><div><div><div>8</div></div><div><span>kubectl</span><span> </span><span>rollout</span><span> </span><span>status</span><span> </span><span>deploy/hello</span><span> &amp;&amp; </span><span>kubectl</span><span> </span><span>get</span><span> </span><span>pods,svc</span></div></div><div><div><div>9</div></div><div><span>kubectl</span><span> </span><span>port-forward</span><span> </span><span>svc/hello</span><span> </span><span>8080:80</span><span>      </span><span># kind 里用 port-forward 或 Ingress 访问</span></div></div><div><div><div>10</div></div><div><span>curl</span><span> </span><span>-I</span><span> </span><span>http://localhost:8080</span></div></div><div><div><div>11</div></div><div><span>kubectl</span><span> </span><span>delete</span><span> </span><span>-f</span><span> </span><span>hello.yaml</span><span> &amp;&amp; </span><span>kind</span><span> </span><span>delete</span><span> </span><span>cluster</span><span> </span><span>--name</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>若要在 kind 里验证 Ingress，还需额外安装 ingress-nginx，并给节点配置 <code>extraPortMappings</code> 映射 80/443。</p></section>
<section><h2>12. 生产必备<a href="#12-生产必备"><span>#</span></a></h2><ul>
<li><strong>命名空间隔离</strong>：按团队或环境划分 Namespace，配合 ResourceQuota 与 LimitRange 限制总量与默认值。</li>
<li><strong>RBAC 最小权限</strong>：用 Role/RoleBinding（命名空间级）或 ClusterRole/ClusterRoleBinding（集群级）精确授权，别把 <code>cluster-admin</code> 发给服务账号。</li>
<li><strong>NetworkPolicy</strong>：默认所有 Pod 互通；一旦某 Pod 被任意策略选中，就变成”默认拒绝 + 显式放行”。生产建议先做默认拒绝入站，再逐条开放。</li>
<li><strong>镜像拉取密钥</strong>：私有仓库用 <code>kubernetes.io/dockerconfigjson</code> 类型的 Secret，挂到 ServiceAccount 的 <code>imagePullSecrets</code> 上。</li>
<li><strong>PodDisruptionBudget</strong>：声明自愿中断时至少保留几个可用副本（如 <code>minAvailable: 2</code>），防止节点维护时把服务全部驱逐。</li>
<li><strong>日志与监控</strong>：指标用 Prometheus + Grafana，日志用 Loki（或 ELK），链路用 OpenTelemetry；采集走 sidecar 或 DaemonSet，应用只输出到 stdout。</li>
<li><strong>其他</strong>：镜像别用 <code>latest</code> tag；配好探针；设置 <code>terminationGracePeriodSeconds</code> 并处理 SIGTERM 优雅退出；用 <code>topologySpreadConstraints</code> 打散副本；给关键命名空间设 PriorityClass。</li>
</ul></section>
<section><h2>13. 什么时候不该用 K8s<a href="#13-什么时候不该用-k8s"><span>#</span></a></h2><p>K8s 强大但要付代价：学习曲线陡、组件多、网络与存储抽象复杂、排障链路长、需要有人持续维护。以下情况值得认真考虑替代方案：</p><ul>
<li><strong>团队小、没有专职运维</strong>：一两个人维护集群的隐性成本很高，直接用托管容器服务能少踩坑。</li>
<li><strong>单机就扛得住</strong>：流量不大、可用性要求不苛刻时，Docker Compose 足够。</li>
<li><strong>只是定时任务或简单 API</strong>：Serverless（云函数、Cloud Run、Cloudflare Workers）零运维，按调用计费。</li>
<li><strong>需要精细调度但不是容器场景</strong>：Nomad 更简单，适合混合编排容器与普通进程；边缘与资源受限环境可用 k3s、KubeEdge。</li>
</ul><p>成本权衡要算三笔账：<strong>人力账</strong>（谁维护集群与升级）、<strong>资源账</strong>（控制平面与系统组件自身占用，托管集群按节点收费）、<strong>机会成本</strong>（时间花在 K8s 还是业务上）。一个经验法则是：如果容器数量少于十几个、没有强制的多租户隔离与弹性需求，先别上 K8s。</p></section>
<section><h2>14. 面试高频问题<a href="#14-面试高频问题"><span>#</span></a></h2><p><strong>Pod 和容器的区别？</strong> 容器是最小运行单元，Pod 是最小<strong>调度</strong>单元。一个 Pod 可含多个容器，共享网络命名空间与 Volume，共享生命周期，容器间可用 <code>localhost</code> 互访，端口不能重复。</p><p><strong>Deployment 和 StatefulSet 的区别？</strong> Deployment 面向无状态应用：Pod 可互换、随机名字、不需要稳定存储、支持并行滚动更新。StatefulSet 面向有状态应用：Pod 名字稳定有序（<code>db-0</code>、<code>db-1</code>）、网络标识稳定（配合 Headless Service）、每个 Pod 有独立 PVC、按顺序创建与删除。</p><p><strong>Service 如何实现负载均衡？</strong> Service 通过 label selector 选中 Pod，Endpoints controller 把就绪 Pod 的 IP 写入 Endpoints，kube-proxy 在节点上把 ClusterIP 的流量按 iptables/IPVS 规则分发到后端。默认是四层转发；ClusterIP 是虚拟 IP，不绑定网卡，只存在于节点规则中。</p><p><strong>三种探针的区别？</strong> liveness 决定”要不要重启”，readiness 决定”要不要接流量”，startup 决定”启动是否完成（用于抑制前两者）”。</p><p><strong>滚动更新的原理？</strong> 改 <code>spec.template</code> 后 Deployment 创建新 ReplicaSet，按 <code>maxSurge</code>/<code>maxUnavailable</code> 逐步扩新缩旧；新 Pod 通过 readiness 后才接流量，全部就绪后旧 RS 缩到 0（保留历史以便回滚）。<code>rollout undo</code> 即把旧 RS 的模板写回。</p><p><strong>requests 和 limits 的区别？</strong> requests 参与调度、决定 QoS 与驱逐优先级；limits 是运行硬上限，CPU 超限被节流，内存超限被 OOMKilled。</p><p><strong>Pod 一直 Pending 怎么排查？</strong> 看 <code>kubectl describe pod</code> 的 Events。常见原因：资源不足无法满足 requests、节点有 Taint 且缺少 Toleration、nodeSelector/affinity 不匹配、PVC 未绑定、超过 ResourceQuota。</p><p><strong>CrashLoopBackOff 怎么排查？</strong> 用 <code>kubectl logs --previous</code> 看上次崩溃日志，<code>kubectl describe pod</code> 看退出码与原因（OOMKilled / 探针失败 / 命令写错 / 配置缺失）。退出码 137 通常是 OOMKilled 或 SIGKILL。</p><p><strong>Service 和 Ingress 的区别？</strong> Service 工作在一到四层，暴露 IP + 端口，通常对应一个后端；Ingress 工作在七层，按域名/路径路由到不同 Service，还能统一管理 TLS。</p><p><strong>ConfigMap 更新后为什么没生效？</strong> 环境变量方式在容器启动时已固化，不会随 ConfigMap 更新，必须 <code>kubectl rollout restart</code>；Volume 挂载方式则由 kubelet 周期性同步。</p></section>
<section><h2>15. 命令与资源对象速查表<a href="#15-命令与资源对象速查表"><span>#</span></a></h2><p>常用命令：</p>

<table><thead><tr><th>命令</th><th>用途</th></tr></thead><tbody><tr><td><code>kubectl get RES -n NS -o wide</code></td><td>列表查看</td></tr><tr><td><code>kubectl describe RES NAME</code></td><td>详情与事件</td></tr><tr><td><code>kubectl logs POD -f --tail=100</code></td><td>日志</td></tr><tr><td><code>kubectl exec -it POD -- sh</code></td><td>进入容器</td></tr><tr><td><code>kubectl apply -f FILE</code></td><td>声明式应用</td></tr><tr><td><code>kubectl delete -f FILE</code></td><td>删除</td></tr><tr><td><code>kubectl scale deploy/NAME --replicas=N</code></td><td>扩缩容</td></tr><tr><td><code>kubectl set image deploy/NAME C=IMG</code></td><td>更新镜像</td></tr><tr><td><code>kubectl rollout status deploy/NAME</code></td><td>查看发布进度</td></tr><tr><td><code>kubectl rollout undo deploy/NAME</code></td><td>回滚</td></tr><tr><td><code>kubectl port-forward svc/NAME 8080:80</code></td><td>本地端口转发</td></tr><tr><td><code>kubectl top pod</code> / <code>kubectl top node</code></td><td>资源占用</td></tr><tr><td><code>kubectl cp POD:/path ./local</code></td><td>文件拷贝</td></tr><tr><td><code>kubectl explain RES.FIELD</code></td><td>查字段说明</td></tr><tr><td><code>kubectl api-resources</code></td><td>资源清单</td></tr></tbody></table><p>资源对象：</p>

<table><thead><tr><th>资源</th><th>短名</th><th>作用</th></tr></thead><tbody><tr><td>Pod</td><td>po</td><td>最小调度单元</td></tr><tr><td>Deployment</td><td>deploy</td><td>无状态副本管理 + 滚动更新</td></tr><tr><td>ReplicaSet</td><td>rs</td><td>维持副本数（由 Deployment 管理）</td></tr><tr><td>StatefulSet</td><td>sts</td><td>有状态应用，稳定标识与独立存储</td></tr><tr><td>DaemonSet</td><td>ds</td><td>每节点跑一份（日志、监控 Agent）</td></tr><tr><td>Job / CronJob</td><td>cj</td><td>一次性任务 / 定时任务</td></tr><tr><td>Service</td><td>svc</td><td>稳定入口与四层负载均衡</td></tr><tr><td>Ingress</td><td>ing</td><td>七层路由</td></tr><tr><td>ConfigMap / Secret</td><td>cm</td><td>非敏感配置 / 敏感信息（base64，非加密）</td></tr><tr><td>Namespace</td><td>ns</td><td>逻辑隔离</td></tr><tr><td>PersistentVolume / PersistentVolumeClaim</td><td>pv / pvc</td><td>集群级存储资源 / 存储申请</td></tr><tr><td>StorageClass</td><td>sc</td><td>动态存储供给模板</td></tr><tr><td>HorizontalPodAutoscaler</td><td>hpa</td><td>自动扩缩容</td></tr><tr><td>NetworkPolicy</td><td>netpol</td><td>网络访问控制</td></tr><tr><td>Role / RoleBinding</td><td>-</td><td>命名空间级权限</td></tr><tr><td>ClusterRole / ClusterRoleBinding</td><td>-</td><td>集群级权限</td></tr><tr><td>PodDisruptionBudget</td><td>pdb</td><td>自愿中断的可用性保障</td></tr><tr><td>ResourceQuota / LimitRange</td><td>quota</td><td>命名空间总量限制 / 默认值</td></tr></tbody></table><p>字段速查最常见的几项：<code>spec.replicas</code>（期望副本数）、<code>spec.selector</code>（选择后端 Pod 的 Label）、<code>spec.template</code>（Pod 模板）、<code>spec.strategy.rollingUpdate</code>（maxSurge / maxUnavailable）、<code>spec.containers[].resources</code>（requests / limits）、<code>spec.nodeName</code>（Pod 绑定的节点）、<code>restartPolicy</code>（Always / OnFailure / Never）、<code>terminationGracePeriodSeconds</code>（优雅退出宽限期）。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/git%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</id>
      <title type="text">Git 学习笔记：从入门到实践</title>
      <published>2026-09-10T00:00:00.000Z</published>
      <updated>2026-09-10T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/git%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
      <summary type="text">Git 版本控制系统核心知识整理，涵盖基础概念、常用命令、分支管理、远程协作、高级技巧与工作流程，适合初学者和需要系统梳理 Git 知识的开发者</summary>
      <content type="html"><![CDATA[<p>Git 是目前世界上最先进的分布式版本控制系统，由 Linux 之父 Linus Torvalds 于 2005 年开发。无论是个人项目还是团队协作，Git 都是现代软件开发的基础设施。本文系统整理 Git 的核心知识，从基础概念到高级技巧，帮助你建立完整的 Git 知识体系。</p>
<section><h2>一、Git 基础概念<a href="#一git-基础概念"><span>#</span></a></h2><section><h3>什么是版本控制？<a href="#什么是版本控制"><span>#</span></a></h3><p>版本控制是一种记录文件内容变化，以便将来查阅特定版本修订情况的系统。</p></section><section><h3>Git 的三个工作区域<a href="#git-的三个工作区域"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>工作区（Working Directory）</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>↓  git add</span></div></div><div><div><div>3</div></div><div><span>暂存区（Staging Area / Index）</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>↓  git commit</span></div></div><div><div><div>5</div></div><div><span>本地仓库（Local Repository）</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>↓  git push</span></div></div><div><div><div>7</div></div><div><span>远程仓库（Remote Repository）</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>Git 文件的四种状态<a href="#git-文件的四种状态"><span>#</span></a></h3>

<table><thead><tr><th>状态</th><th>说明</th><th>所在区域</th></tr></thead><tbody><tr><td><strong>Untracked</strong></td><td>未跟踪，新文件未被 Git 管理</td><td>工作区</td></tr><tr><td><strong>Modified</strong></td><td>已修改，内容有变化</td><td>工作区</td></tr><tr><td><strong>Staged</strong></td><td>已暂存，准备提交</td><td>暂存区</td></tr><tr><td><strong>Committed</strong></td><td>已提交，保存在本地仓库</td><td>本地仓库</td></tr></tbody></table></section><section><h3>.git 目录结构<a href="#git-目录结构"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>.git/</span></div></div><div><div><div>2</div></div><div><span>├── HEAD          # 指向当前分支</span></div></div><div><div><div>3</div></div><div><span>├── config        # 仓库级配置</span></div></div><div><div><div>4</div></div><div><span>├── description   # 仓库描述（GitWeb 用）</span></div></div><div><div><div>5</div></div><div><span>├── hooks/        # 钩子脚本</span></div></div><div><div><div>6</div></div><div><span>├── index         # 暂存区</span></div></div><div><div><div>7</div></div><div><span>├── objects/      # 所有数据对象（blob/tree/commit）</span></div></div><div><div><div>8</div></div><div><span>├── refs/         # 分支和标签引用</span></div></div><div><div><div>9</div></div><div><span>└── logs/         # 引用日志</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>二、Git 配置<a href="#二git-配置"><span>#</span></a></h2><section><h3>三级配置<a href="#三级配置"><span>#</span></a></h3>

<table><thead><tr><th>级别</th><th>文件路径</th><th>命令参数</th></tr></thead><tbody><tr><td>系统级</td><td><code>/etc/gitconfig</code></td><td><code>--system</code></td></tr><tr><td>用户级</td><td><code>~/.gitconfig</code></td><td><code>--global</code></td></tr><tr><td>仓库级</td><td><code>.git/config</code></td><td><code>--local</code></td></tr></tbody></table><p>优先级：<code>--local</code> &gt; <code>--global</code> &gt; <code>--system</code></p></section><section><h3>基础配置<a href="#基础配置"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 设置用户信息</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>user.name</span><span> </span><span>"Your Name"</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>user.email</span><span> </span><span>"your@email.com"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 设置默认编辑器</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>core.editor</span><span> </span><span>vim</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 设置默认分支名</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>init.defaultBranch</span><span> </span><span>main</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 查看所有配置</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--list</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 查看某项配置</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>user.name</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>常用配置<a href="#常用配置"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 设置换行符转换（Windows 推荐）</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>core.autocrlf</span><span> </span><span>true</span><span>    </span><span># 提交时 LF，检出时 CRLF</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>core.autocrlf</span><span> </span><span>input</span><span>   </span><span># 提交时 LF，检出时不转换（Mac/Linux）</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 设置颜色输出</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>color.ui</span><span> </span><span>auto</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 设置 diff 工具</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>diff.tool</span><span> </span><span>vimdiff</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 设置 merge 工具</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>merge.tool</span><span> </span><span>vimdiff</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 设置别名（大幅提升效率）</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>alias.st</span><span> </span><span>status</span></div></div><div><div><div>16</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>alias.co</span><span> </span><span>checkout</span></div></div><div><div><div>17</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>alias.br</span><span> </span><span>branch</span></div></div><div><div><div>18</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>alias.ci</span><span> </span><span>commit</span></div></div><div><div><div>19</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>alias.lg</span><span> </span><span>"log --oneline --graph --all"</span></div></div><div><div><div>20</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>alias.last</span><span> </span><span>"log -1 HEAD"</span></div></div><div><div><div>21</div></div><div><span>git</span><span> </span><span>config</span><span> </span><span>--global</span><span> </span><span>alias.unstage</span><span> </span><span>"reset HEAD --"</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h2>三、Git 基本操作<a href="#三git-基本操作"><span>#</span></a></h2><section><h3>创建仓库<a href="#创建仓库"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 初始化新仓库</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>init</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 克隆远程仓库</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://github.com/user/repo.git</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>https://github.com/user/repo.git</span><span> </span><span>my-folder</span><span>  </span><span># 指定本地目录名</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 克隆特定分支</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>-b</span><span> </span><span>develop</span><span> </span><span>https://github.com/user/repo.git</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>查看状态<a href="#查看状态"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看工作区状态</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>status</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>status</span><span> </span><span>-s</span><span>    </span><span># 简洁模式</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 状态标识说明：</span></div></div><div><div><div>6</div></div><div><span># M  修改（已暂存）</span></div></div><div><div><div>7</div></div><div><span># M  修改（未暂存，两个 M 表示暂存后又修改）</span></div></div><div><div><div>8</div></div><div><span># A  新增文件（已暂存）</span></div></div><div><div><div>9</div></div><div><span># D  删除</span></div></div><div><div><div>10</div></div><div><span># ?? 未跟踪的新文件</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>添加到暂存区<a href="#添加到暂存区"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 添加单个文件</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>file.txt</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 添加所有变化</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>.</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 添加指定类型</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>*</span><span>.js</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 交互式添加（选择性暂存）</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>-p</span><span>    </span><span># 逐块选择要暂存的内容</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>提交<a href="#提交"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 提交暂存区内容</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"feat: add login feature"</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 跳过暂存区，直接提交已跟踪文件的修改</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-am</span><span> </span><span>"fix: bug in login"</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 修改上一次提交（未推送到远程时）</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>--amend</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>--amend</span><span> </span><span>-m</span><span> </span><span>"新的提交信息"</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 带签名提交</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-S</span><span> </span><span>-m</span><span> </span><span>"signed commit"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>提交信息规范（Conventional Commits）<a href="#提交信息规范conventional-commits"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;type&gt;(&lt;scope&gt;): &lt;subject&gt;</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>&lt;body&gt;</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>&lt;footer&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>常用 type：</p>

<table><thead><tr><th>type</th><th>说明</th></tr></thead><tbody><tr><td><code>feat</code></td><td>新功能</td></tr><tr><td><code>fix</code></td><td>修复 bug</td></tr><tr><td><code>docs</code></td><td>文档更新</td></tr><tr><td><code>style</code></td><td>代码格式（不影响逻辑）</td></tr><tr><td><code>refactor</code></td><td>重构（既非新功能也非修复）</td></tr><tr><td><code>perf</code></td><td>性能优化</td></tr><tr><td><code>test</code></td><td>测试相关</td></tr><tr><td><code>chore</code></td><td>构建/工具/依赖更新</td></tr></tbody></table><p>示例：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"feat(auth): add OAuth2 login support"</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"fix(api): handle null response in user endpoint"</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"docs: update README with installation steps"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>查看历史<a href="#查看历史"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 基本查看</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>log</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--oneline</span><span>           </span><span># 单行模式</span></div></div><div><div><div>4</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--oneline</span><span> </span><span>-10</span><span>       </span><span># 最近 10 条</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 图形化</span></div></div><div><div><div>7</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--oneline</span><span> </span><span>--graph</span><span> </span><span>--all</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 带文件变化</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--stat</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 按作者</span></div></div><div><div><div>13</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--author=</span><span>"name"</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># 按时间</span></div></div><div><div><div>16</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--since=</span><span>"2024-01-01"</span></div></div><div><div><div>17</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--until=</span><span>"2024-12-31"</span></div></div><div><div><div>18</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--after=</span><span>"1 week ago"</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span># 搜索提交信息</span></div></div><div><div><div>21</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--grep=</span><span>"fix"</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span># 搜索代码变化</span></div></div><div><div><div>24</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>-S</span><span> </span><span>"function_name"</span><span>    </span><span># 搜索引入/删除某段代码的提交</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div><span># 显示每次提交的详细变化</span></div></div><div><div><div>27</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>-p</span></div></div><div><div><div>28</div></div><div>
</div></div><div><div><div>29</div></div><div><span># 自定义格式</span></div></div><div><div><div>30</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>--pretty=format:</span><span>"%h %an %ar %s"</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>比较差异<a href="#比较差异"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 工作区 vs 暂存区</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>diff</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 暂存区 vs 最新提交</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>--staged</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>--cached</span><span>    </span><span># 同义</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 工作区 vs 最新提交</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>HEAD</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 两个提交之间</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>commit1</span><span> </span><span>commit2</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 两个分支之间</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>main..feature</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 只看文件名</span></div></div><div><div><div>18</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>--name-only</span></div></div><div><div><div>19</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>--name-status</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>撤销操作<a href="#撤销操作"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 丢弃工作区修改（未暂存）</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>--</span><span> </span><span>file.txt</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>restore</span><span> </span><span>file.txt</span><span>          </span><span># Git 2.23+ 推荐</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 取消暂存（保留工作区修改）</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>HEAD</span><span> </span><span>file.txt</span></div></div><div><div><div>7</div></div><div><span>git</span><span> </span><span>restore</span><span> </span><span>--staged</span><span> </span><span>file.txt</span><span> </span><span># Git 2.23+ 推荐</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 撤销上一次提交（保留修改在工作区）</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--soft</span><span> </span><span>HEAD~1</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 撤销上一次提交（保留修改在暂存区）</span></div></div><div><div><div>13</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--mixed</span><span> </span><span>HEAD~1</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># 彻底撤销上一次提交（丢弃修改！慎用）</span></div></div><div><div><div>16</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--hard</span><span> </span><span>HEAD~1</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span># 回退到指定提交（慎用）</span></div></div><div><div><div>19</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--hard</span><span> </span><span>&lt;commit-hash&gt;</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span># 安全撤销（创建新提交来反向操作）</span></div></div><div><div><div>22</div></div><div><span>git</span><span> </span><span>revert</span><span> </span><span>&lt;commit-hash&gt;</span></div></div><div><div><div>23</div></div><div><span>git</span><span> </span><span>revert</span><span> </span><span>HEAD</span><span>    </span><span># 撤销上一次提交</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>删除与重命名<a href="#删除与重命名"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 删除文件（从 Git 和工作区）</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>file.txt</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 只从 Git 删除（保留本地文件）</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>--cached</span><span> </span><span>file.txt</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 重命名</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>mv</span><span> </span><span>old-name.txt</span><span> </span><span>new-name.txt</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 批量重命名</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>mv</span><span> </span><span>docs/</span><span>*</span><span>.md</span><span> </span><span>documentation/</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>四、Git 分支管理<a href="#四git-分支管理"><span>#</span></a></h2><section><h3>分支基础<a href="#分支基础"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看分支</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>branch</span><span>              </span><span># 本地分支</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-a</span><span>           </span><span># 所有分支（本地+远程）</span></div></div><div><div><div>4</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-v</span><span>           </span><span># 带最后提交信息</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 创建分支</span></div></div><div><div><div>7</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>feature-login</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 切换分支</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>feature-login</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>feature-login</span><span>      </span><span># Git 2.23+ 推荐</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 创建并切换</span></div></div><div><div><div>14</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>-b</span><span> </span><span>feature-login</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>switch</span><span> </span><span>-c</span><span> </span><span>feature-login</span><span>   </span><span># Git 2.23+ 推荐</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 重命名分支</span></div></div><div><div><div>18</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-m</span><span> </span><span>old-name</span><span> </span><span>new-name</span></div></div><div><div><div>19</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-m</span><span> </span><span>new-name</span><span>        </span><span># 重命名当前分支</span></div></div><div><div><div>20</div></div><div>
</div></div><div><div><div>21</div></div><div><span># 删除分支</span></div></div><div><div><div>22</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-d</span><span> </span><span>feature-login</span><span>      </span><span># 已合并的分支</span></div></div><div><div><div>23</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-D</span><span> </span><span>feature-login</span><span>      </span><span># 强制删除（未合并也删除）</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>合并分支<a href="#合并分支"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 合并分支到当前分支</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>main</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>feature-login</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 合并类型：</span></div></div><div><div><div>6</div></div><div><span># 1. Fast-forward（快进）：main 没有新提交，直接移动指针</span></div></div><div><div><div>7</div></div><div><span># 2. Three-way merge（三方合并）：创建新的合并提交</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 禁止快进合并（保留分支历史）</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--no-ff</span><span> </span><span>feature-login</span><span> </span><span>-m</span><span> </span><span>"Merge feature-login into main"</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 合并指定分支的某个提交</span></div></div><div><div><div>13</div></div><div><span>git</span><span> </span><span>cherry-pick</span><span> </span><span>&lt;commit-hash&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>合并冲突处理<a href="#合并冲突处理"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 冲突文件中的标记</span></div></div><div><div><div>2</div></div><div><span>&lt;&lt;&lt;</span><span>&lt;&lt;&lt;</span><span>&lt;</span><span> HEAD</span></div></div><div><div><div>3</div></div><div><span>当前分支的内容</span></div></div><div><div><div>4</div></div><div><span>=======</span></div></div><div><div><div>5</div></div><div><span>被合并分支的内容</span></div></div><div><div><div>6</div></div><div><span>&gt;&gt;&gt;&gt;&gt;&gt;&gt; </span><span>feature-login</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 解决步骤：</span></div></div><div><div><div>9</div></div><div><span># 1. 编辑冲突文件，选择/合并内容</span></div></div><div><div><div>10</div></div><div><span># 2. 删除冲突标记</span></div></div><div><div><div>11</div></div><div><span># 3. git add &lt;resolved-file&gt;</span></div></div><div><div><div>12</div></div><div><span># 4. git commit</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 使用工具解决冲突</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>mergetool</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>变基（Rebase）<a href="#变基rebase"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 将当前分支变基到目标分支</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>feature</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>main</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 变基 vs 合并：</span></div></div><div><div><div>6</div></div><div><span># - merge：保留完整历史，创建合并提交</span></div></div><div><div><div>7</div></div><div><span># - rebase：线性历史，更干净但改写历史</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 交互式变基（修改提交历史）</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>-i</span><span> </span><span>HEAD~3</span><span>    </span><span># 修改最近 3 次提交</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 交互式变基操作：</span></div></div><div><div><div>13</div></div><div><span># pick   保留提交</span></div></div><div><div><div>14</div></div><div><span># reword 修改提交信息</span></div></div><div><div><div>15</div></div><div><span># edit   修改提交内容</span></div></div><div><div><div>16</div></div><div><span># squash 合并到上一个提交</span></div></div><div><div><div>17</div></div><div><span># drop   删除提交</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span># 变基冲突解决后继续</span></div></div><div><div><div>20</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>--continue</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span># 放弃变基</span></div></div><div><div><div>23</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>暂存工作区<a href="#暂存工作区"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 暂存当前工作</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>stash</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>save</span><span> </span><span>"描述信息"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 查看暂存列表</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>list</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 恢复最近的暂存</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>pop</span><span>          </span><span># 恢复并删除暂存记录</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>apply</span><span>        </span><span># 恢复但保留暂存记录</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 恢复指定暂存</span></div></div><div><div><div>13</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>apply</span><span> </span><span>stash@{</span><span>1</span><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># 删除暂存</span></div></div><div><div><div>16</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>drop</span><span> </span><span>stash@{</span><span>0</span><span>}</span></div></div><div><div><div>17</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>clear</span><span>        </span><span># 清空所有暂存</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h2>五、Git 远程操作<a href="#五git-远程操作"><span>#</span></a></h2><section><h3>远程仓库管理<a href="#远程仓库管理"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看远程仓库</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>remote</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>-v</span><span>           </span><span># 带 URL</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 添加远程仓库</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>add</span><span> </span><span>origin</span><span> </span><span>https://github.com/user/repo.git</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 修改远程 URL</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>set-url</span><span> </span><span>origin</span><span> </span><span>https://github.com/user/new-repo.git</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 删除远程仓库</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>remove</span><span> </span><span>origin</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 重命名</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>remote</span><span> </span><span>rename</span><span> </span><span>origin</span><span> </span><span>upstream</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>推送与拉取<a href="#推送与拉取"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 推送到远程</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>main</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>feature:feature</span><span>    </span><span># 推送本地分支到远程同名分支</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 设置上游分支（首次推送后设置）</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>-u</span><span> </span><span>origin</span><span> </span><span>main</span></div></div><div><div><div>7</div></div><div><span># 之后直接 git push 即可</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 拉取远程更新</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>pull</span><span> </span><span>origin</span><span> </span><span>main</span><span>              </span><span># fetch + merge</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>pull</span><span> </span><span>--rebase</span><span> </span><span>origin</span><span> </span><span>main</span><span>     </span><span># fetch + rebase（推荐，避免不必要的合并提交）</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 获取远程更新（不合并）</span></div></div><div><div><div>14</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>origin</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>--all</span><span>                   </span><span># 获取所有远程仓库</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 删除远程分支</span></div></div><div><div><div>18</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>--delete</span><span> </span><span>feature-login</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span># 推送标签</span></div></div><div><div><div>21</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>v1.0.0</span></div></div><div><div><div>22</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>--tags</span><span>            </span><span># 推送所有标签</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>标签管理<a href="#标签管理"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看标签</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>tag</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>tag</span><span> </span><span>-l</span><span> </span><span>"v1.*"</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 创建轻量标签</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>tag</span><span> </span><span>v1.0.0</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 创建附注标签（推荐）</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>tag</span><span> </span><span>-a</span><span> </span><span>v1.0.0</span><span> </span><span>-m</span><span> </span><span>"Release version 1.0.0"</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 给历史提交打标签</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>tag</span><span> </span><span>-a</span><span> </span><span>v1.0.0</span><span> </span><span>&lt;commit-hash&gt;</span><span> </span><span>-m</span><span> </span><span>"Release version 1.0.0"</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 删除标签</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>tag</span><span> </span><span>-d</span><span> </span><span>v1.0.0</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 查看标签信息</span></div></div><div><div><div>18</div></div><div><span>git</span><span> </span><span>show</span><span> </span><span>v1.0.0</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h2>六、Git 高级技巧<a href="#六git-高级技巧"><span>#</span></a></h2><section><h3>储藏（Stash）进阶<a href="#储藏stash进阶"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 包含未跟踪文件</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>-u</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 只储藏指定文件</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>push</span><span> </span><span>-m</span><span> </span><span>"message"</span><span> </span><span>--</span><span> </span><span>file1.txt</span><span> </span><span>file2.txt</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 从储藏创建新分支</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>branch</span><span> </span><span>new-feature</span><span> </span><span>stash@{</span><span>0</span><span>}</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 查看储藏差异</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>stash</span><span> </span><span>show</span><span> </span><span>-p</span><span> </span><span>stash@{</span><span>0</span><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>重写历史<a href="#重写历史"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 修改最近 N 次提交信息</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>-i</span><span> </span><span>HEAD~N</span></div></div><div><div><div>3</div></div><div><span># 将要修改的提交标记为 reword</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 合并多个提交</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>-i</span><span> </span><span>HEAD~N</span></div></div><div><div><div>7</div></div><div><span># 将要合并的提交标记为 squash</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 删除某次提交</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>-i</span><span> </span><span>HEAD~N</span></div></div><div><div><div>11</div></div><div><span># 将要删除的提交标记为 drop</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 拆分提交</span></div></div><div><div><div>14</div></div><div><span>git</span><span> </span><span>rebase</span><span> </span><span>-i</span><span> </span><span>HEAD~N</span></div></div><div><div><div>15</div></div><div><span># 将要拆分的提交标记为 edit</span></div></div><div><div><div>16</div></div><div><span># 然后分多次提交，最后 git rebase --continue</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>子模块（Submodule）<a href="#子模块submodule"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 添加子模块</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>submodule</span><span> </span><span>add</span><span> </span><span>https://github.com/user/lib.git</span><span> </span><span>libs/lib</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 克隆含子模块的仓库</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>--recursive</span><span> </span><span>https://github.com/user/repo.git</span></div></div><div><div><div>6</div></div><div><span># 或克隆后初始化</span></div></div><div><div><div>7</div></div><div><span>git</span><span> </span><span>submodule</span><span> </span><span>init</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>submodule</span><span> </span><span>update</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 更新子模块</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>submodule</span><span> </span><span>update</span><span> </span><span>--remote</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 删除子模块</span></div></div><div><div><div>14</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>libs/lib</span></div></div><div><div><div>15</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>.git/modules/libs/lib</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>钩子（Hooks）<a href="#钩子hooks"><span>#</span></a></h3><p>钩子脚本位于 <code>.git/hooks/</code> 目录，Git 会在特定事件触发时执行。</p><p>常用钩子：</p>

<table><thead><tr><th>钩子</th><th>触发时机</th><th>用途</th></tr></thead><tbody><tr><td><code>pre-commit</code></td><td>提交前</td><td>代码检查、格式化</td></tr><tr><td><code>commit-msg</code></td><td>提交信息写入后</td><td>检查提交信息格式</td></tr><tr><td><code>pre-push</code></td><td>推送前</td><td>运行测试</td></tr><tr><td><code>post-merge</code></td><td>合并后</td><td>更新依赖</td></tr><tr><td><code>pre-rebase</code></td><td>变基前</td><td>检查是否允许变基</td></tr></tbody></table><p>示例：使用 <code>pre-commit</code> 运行 ESLint</p><div><figure><figcaption><span>.git/hooks/pre-commit</span></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/bin/bash</span></div></div><div><div><div>2</div></div><div><span>npm</span><span> </span><span>run</span><span> </span><span>lint</span></div></div><div><div><div>3</div></div><div><span>if</span><span> [ </span><span>$?</span><span><span> </span><span>-ne</span><span> </span></span><span>0</span><span> ]; </span><span>then</span></div></div><div><div><div>4</div></div><div><span>    </span><span>echo</span><span> </span><span>"Lint failed. Commit aborted."</span></div></div><div><div><div>5</div></div><div><span>    </span><span>exit</span><span> </span><span>1</span></div></div><div><div><div>6</div></div><div><span>fi</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>调试工具<a href="#调试工具"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查找引入 bug 的提交</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>bisect</span><span> </span><span>start</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>bisect</span><span> </span><span>bad</span><span>                </span><span># 标记当前版本有 bug</span></div></div><div><div><div>4</div></div><div><span>git</span><span> </span><span>bisect</span><span> </span><span>good</span><span> </span><span>v1.0.0</span><span>        </span><span># 标记某个版本无 bug</span></div></div><div><div><div>5</div></div><div><span># Git 会自动检出中间版本供你测试</span></div></div><div><div><div>6</div></div><div><span># 测试后标记 good/bad，直到找到问题提交</span></div></div><div><div><div>7</div></div><div><span>git</span><span> </span><span>bisect</span><span> </span><span>reset</span><span>              </span><span># 结束调试</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 查看文件的每一行最后修改人</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>blame</span><span> </span><span>file.txt</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>blame</span><span> </span><span>-L</span><span> </span><span>10,20</span><span> </span><span>file.txt</span><span>   </span><span># 只看第 10-20 行</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 查找删除了某段代码的提交</span></div></div><div><div><div>14</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>-S</span><span> </span><span>"deleted_function"</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>log</span><span> </span><span>-p</span><span> </span><span>-S</span><span> </span><span>"deleted_function"</span><span>    </span><span># 带差异</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 清理未跟踪的文件</span></div></div><div><div><div>18</div></div><div><span>git</span><span> </span><span>clean</span><span> </span><span>-n</span><span>                  </span><span># 预览将删除的文件</span></div></div><div><div><div>19</div></div><div><span>git</span><span> </span><span>clean</span><span> </span><span>-f</span><span>                  </span><span># 删除未跟踪文件</span></div></div><div><div><div>20</div></div><div><span>git</span><span> </span><span>clean</span><span> </span><span>-fd</span><span>                 </span><span># 删除未跟踪文件和目录</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>Git 工作树（Worktree）<a href="#git-工作树worktree"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 添加工作树（同时在多个分支工作）</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>add</span><span> </span><span>../hotfix-branch</span><span> </span><span>hotfix</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 查看工作树</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>list</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 删除工作树</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>worktree</span><span> </span><span>remove</span><span> </span><span>../hotfix-branch</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>七、Git 工作流程<a href="#七git-工作流程"><span>#</span></a></h2><section><h3>集中式工作流<a href="#集中式工作流"><span>#</span></a></h3><p>所有人直接在 <code>main</code> 分支上工作，适合小团队。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>pull</span><span> </span><span>origin</span><span> </span><span>main</span></div></div><div><div><div>2</div></div><div><span># 编辑文件</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>.</span></div></div><div><div><div>4</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"update"</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>main</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>功能分支工作流（Feature Branch）<a href="#功能分支工作流feature-branch"><span>#</span></a></h3><p>每个新功能在独立分支开发，通过 PR/MR 合并。</p><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 创建功能分支</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>-b</span><span> </span><span>feature/login</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 开发、提交</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>.</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"feat: implement login"</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 推送并创建 PR</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>feature/login</span></div></div><div><div><div>10</div></div><div><span># 在 GitHub/GitLab 上创建 Pull Request</span></div></div><div><div><div>11</div></div><div><span># 代码审查后合并到 main</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 合并后删除分支</span></div></div><div><div><div>14</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>main</span></div></div><div><div><div>15</div></div><div><span>git</span><span> </span><span>pull</span><span> </span><span>origin</span><span> </span><span>main</span></div></div><div><div><div>16</div></div><div><span>git</span><span> </span><span>branch</span><span> </span><span>-d</span><span> </span><span>feature/login</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>Git Flow<a href="#git-flow"><span>#</span></a></h3><p>经典的分支模型，适合有明确发布周期的项目：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>main（生产分支）</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>│</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>├── develop（开发分支）</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>│     ├── feature/*（功能分支）</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>│     └── release/*（发布分支）</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>│</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>└── hotfix/*（热修复分支）</span></div></div></code></pre><div><div></div><div></div></div></figure></div>

<table><thead><tr><th>分支</th><th>用途</th><th>生命周期</th></tr></thead><tbody><tr><td><code>main</code></td><td>生产环境代码</td><td>永久</td></tr><tr><td><code>develop</code></td><td>最新开发代码</td><td>永久</td></tr><tr><td><code>feature/*</code></td><td>新功能开发</td><td>临时</td></tr><tr><td><code>release/*</code></td><td>发布准备</td><td>临时</td></tr><tr><td><code>hotfix/*</code></td><td>生产紧急修复</td><td>临时</td></tr></tbody></table></section><section><h3>GitHub Flow<a href="#github-flow"><span>#</span></a></h3><p>简化版工作流，适合持续部署：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 1. 从 main 创建分支</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>-b</span><span> </span><span>feature-x</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 2. 开发并提交</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>.</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"feat: implement feature-x"</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 3. 推送并创建 PR</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>feature-x</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 4. 代码审查、CI 通过后合并到 main</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 5. 部署 main 分支</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>Trunk-Based Development<a href="#trunk-based-development"><span>#</span></a></h3><p>所有人在主干（main/trunk）上工作，通过特性标志（Feature Flags）控制功能发布。</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 短命分支（1-2 天）</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>-b</span><span> </span><span>short-lived-feature</span></div></div><div><div><div>3</div></div><div><span># 快速开发</span></div></div><div><div><div>4</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"feat: partial implementation"</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>short-lived-feature</span></div></div><div><div><div>6</div></div><div><span># 立即合并回 main</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>八、常见问题与解决方案<a href="#八常见问题与解决方案"><span>#</span></a></h2><section><h3>1. 不小心提交了敏感信息<a href="#1-不小心提交了敏感信息"><span>#</span></a></h3><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 方法1：从历史中彻底删除</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>filter-branch</span><span> </span><span>--force</span><span> </span><span>--index-filter</span><span> </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"git rm --cached --ignore-unmatch path/to/file"</span><span> </span><span>\</span></div></div><div><div><div>4</div></div><div><span>  </span><span>--prune-empty</span><span> </span><span>--</span><span> </span><span>--all</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 方法2：使用 BFG 工具（推荐）</span></div></div><div><div><div>7</div></div><div><span># 安装 BFG：https://rtyley.github.io/bfg-repo-cleaner/</span></div></div><div><div><div>8</div></div><div><span>bfg</span><span> </span><span>--delete-files</span><span> </span><span>file-with-secrets.git</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>reflog</span><span> </span><span>expire</span><span> </span><span>--expire=now</span><span> </span><span>--all</span><span> &amp;&amp; </span><span>git</span><span> </span><span>gc</span><span> </span><span>--prune=now</span><span> </span><span>--aggressive</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 方法3：如果是最新提交</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--soft</span><span> </span><span>HEAD~1</span></div></div><div><div><div>13</div></div><div><span>git</span><span> </span><span>rm</span><span> </span><span>--cached</span><span> </span><span>path/to/file</span></div></div><div><div><div>14</div></div><div><span>git</span><span> </span><span>commit</span><span> </span><span>-m</span><span> </span><span>"remove sensitive file"</span></div></div><div><div><div>15</div></div><div><span># 然后轮换密钥/密码</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>2. 撤销已推送的提交<a href="#2-撤销已推送的提交"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 安全方式：创建反向提交</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>revert</span><span> </span><span>&lt;commit-hash&gt;</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>origin</span><span> </span><span>main</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 危险方式：强制推送（改写历史）</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>reset</span><span> </span><span>--hard</span><span> </span><span>HEAD~1</span></div></div><div><div><div>7</div></div><div><span>git</span><span> </span><span>push</span><span> </span><span>--force</span><span> </span><span>origin</span><span> </span><span>main</span><span>    </span><span># 慎用！可能影响他人</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>3. 合并冲突太多怎么办<a href="#3-合并冲突太多怎么办"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 使用他们的版本</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--theirs</span><span> </span><span>feature-branch</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 使用我们的版本</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--ours</span><span> </span><span>feature-branch</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 使用特定版本的文件</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>--theirs</span><span> </span><span>path/to/file</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>--ours</span><span> </span><span>path/to/file</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 放弃合并</span></div></div><div><div><div>12</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>--abort</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4. 恢复删除的分支<a href="#4-恢复删除的分支"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看分支引用日志</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>reflog</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 找到分支最后的提交</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>reflog</span><span> | </span><span>grep</span><span> </span><span>"branch-name"</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 恢复分支</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>-b</span><span> </span><span>recovered-branch</span><span> </span><span>&lt;commit-hash&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>5. 大文件导致仓库过大<a href="#5-大文件导致仓库过大"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 使用 Git LFS 管理大文件</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>lfs</span><span> </span><span>install</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>lfs</span><span> </span><span>track</span><span> </span><span>"*.psd"</span></div></div><div><div><div>4</div></div><div><span>git</span><span> </span><span>lfs</span><span> </span><span>track</span><span> </span><span>"*.zip"</span></div></div><div><div><div>5</div></div><div><span>git</span><span> </span><span>add</span><span> </span><span>.gitattributes</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 迁移已有大文件到 LFS</span></div></div><div><div><div>8</div></div><div><span>git</span><span> </span><span>lfs</span><span> </span><span>migrate</span><span> </span><span>import</span><span> </span><span>--include=</span><span>"*.psd"</span><span> </span><span>--everything</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 查看仓库大小</span></div></div><div><div><div>11</div></div><div><span>git</span><span> </span><span>count-objects</span><span> </span><span>-vH</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>6. 代码审查（Pull Request）<a href="#6-代码审查pull-request"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 检出 PR 分支进行本地测试</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>fetch</span><span> </span><span>origin</span><span> </span><span>pull/123/head:pr-123</span></div></div><div><div><div>3</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>pr-123</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 查看 PR 差异</span></div></div><div><div><div>6</div></div><div><span>git</span><span> </span><span>diff</span><span> </span><span>main..pr-123</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 合并 PR</span></div></div><div><div><div>9</div></div><div><span>git</span><span> </span><span>checkout</span><span> </span><span>main</span></div></div><div><div><div>10</div></div><div><span>git</span><span> </span><span>merge</span><span> </span><span>pr-123</span><span> </span><span>--no-ff</span><span> </span><span>-m</span><span> </span><span>"Merge PR #123"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>7. 查找性能问题<a href="#7-查找性能问题"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看哪些文件最大</span></div></div><div><div><div>2</div></div><div><span>git</span><span> </span><span>rev-list</span><span> </span><span>--objects</span><span> </span><span>--all</span><span> | </span><span>\</span></div></div><div><div><div>3</div></div><div><span>  </span><span>git</span><span> </span><span>cat-file</span><span> </span><span>--batch-check=</span><span>'%(objecttype) %(objectname) %(objectsize) %(rest)'</span><span> | </span><span>\</span></div></div><div><div><div>4</div></div><div><span>  </span><span>sed</span><span> </span><span>-n</span><span> </span><span>'s/^blob //p'</span><span> | </span><span>\</span></div></div><div><div><div>5</div></div><div><span>  </span><span>sort</span><span> </span><span>-rnk2</span><span> | </span><span>head</span><span> </span><span>-10</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 使用 git-sizer 分析仓库</span></div></div><div><div><div>8</div></div><div><span>git-sizer</span><span> </span><span>--verbose</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>九、Git 配置文件模板<a href="#九git-配置文件模板"><span>#</span></a></h2><section><h3>全局 .gitconfig 推荐配置<a href="#全局-gitconfig-推荐配置"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[user]</span></div></div><div><div><div>2</div></div><div><span>    </span><span>name</span><span><span> </span><span>=</span><span> Your Name</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>email</span><span><span> </span><span>=</span><span> your@email.com</span></span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>[core]</span></div></div><div><div><div>6</div></div><div><span>    </span><span>autocrlf</span><span><span> </span><span>=</span><span> input    </span></span><span># Mac/Linux</span></div></div><div><div><div>7</div></div><div><span>    </span><span># autocrlf = true   # Windows</span></div></div><div><div><div>8</div></div><div><span>    </span><span>editor</span><span><span> </span><span>=</span><span> vim</span></span></div></div><div><div><div>9</div></div><div><span>    </span><span>pager</span><span><span> </span><span>=</span><span> less -FRX</span></span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>[alias]</span></div></div><div><div><div>12</div></div><div><span>    </span><span>st</span><span><span> </span><span>=</span><span> status -sb</span></span></div></div><div><div><div>13</div></div><div><span>    </span><span>co</span><span><span> </span><span>=</span><span> checkout</span></span></div></div><div><div><div>14</div></div><div><span>    </span><span>br</span><span><span> </span><span>=</span><span> branch</span></span></div></div><div><div><div>15</div></div><div><span>    </span><span>ci</span><span><span> </span><span>=</span><span> commit</span></span></div></div><div><div><div>16</div></div><div><span>    </span><span>lg</span><span><span> </span><span>=</span><span> log --oneline --graph --all --decorate</span></span></div></div><div><div><div>17</div></div><div><span>    </span><span>last</span><span><span> </span><span>=</span><span> log -1 HEAD --stat</span></span></div></div><div><div><div>18</div></div><div><span>    </span><span>unstage</span><span><span> </span><span>=</span><span> reset HEAD --</span></span></div></div><div><div><div>19</div></div><div><span>    </span><span>amend</span><span><span> </span><span>=</span><span> commit --amend --no-edit</span></span></div></div><div><div><div>20</div></div><div><span>    </span><span>wip</span><span><span> </span><span>=</span><span> !git add -A &amp;&amp; git commit -m </span></span><span>"WIP"</span></div></div><div><div><div>21</div></div><div><span>    </span><span>undo</span><span><span> </span><span>=</span><span> reset --soft HEAD~1</span></span></div></div><div><div><div>22</div></div><div><span>    </span><span>sync</span><span><span> </span><span>=</span><span> !git fetch --all --prune &amp;&amp; git rebase origin/main</span></span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>[pull]</span></div></div><div><div><div>25</div></div><div><span>    </span><span>rebase</span><span><span> </span><span>=</span><span> true</span></span></div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>[push]</span></div></div><div><div><div>28</div></div><div><span>    </span><span>default</span><span><span> </span><span>=</span><span> current</span></span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>[merge]</span></div></div><div><div><div>31</div></div><div><span>    </span><span>tool</span><span><span> </span><span>=</span><span> vimdiff</span></span></div></div><div><div><div>32</div></div><div><span>    </span><span>conflictstyle</span><span><span> </span><span>=</span><span> diff3</span></span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>[diff]</span></div></div><div><div><div>35</div></div><div><span>    </span><span>tool</span><span><span> </span><span>=</span><span> vimdiff</span></span></div></div><div><div><div>36</div></div><div><span>    </span><span>algorithm</span><span><span> </span><span>=</span><span> histogram</span></span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>[init]</span></div></div><div><div><div>39</div></div><div><span>    </span><span>defaultBranch</span><span><span> </span><span>=</span><span> main</span></span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>[rerere]</span></div></div><div><div><div>42</div></div><div><span>    </span><span>enabled</span><span><span> </span><span>=</span><span> true    </span></span><span># 记住冲突解决方案</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>.gitignore 模板<a href="#gitignore-模板"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 依赖</span></div></div><div><div><div>2</div></div><div><span>node_modules/</span></div></div><div><div><div>3</div></div><div><span>vendor/</span></div></div><div><div><div>4</div></div><div><span>__pycache__/</span></div></div><div><div><div>5</div></div><div><span>*.pyc</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 构建产物</span></div></div><div><div><div>8</div></div><div><span>dist/</span></div></div><div><div><div>9</div></div><div><span>build/</span></div></div><div><div><div>10</div></div><div><span>*.o</span></div></div><div><div><div>11</div></div><div><span>*.exe</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 环境配置</span></div></div><div><div><div>14</div></div><div><span>.env</span></div></div><div><div><div>15</div></div><div><span>.env.local</span></div></div><div><div><div>16</div></div><div><span>.env.production</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span># IDE</span></div></div><div><div><div>19</div></div><div><span>.vscode/</span></div></div><div><div><div>20</div></div><div><span>.idea/</span></div></div><div><div><div>21</div></div><div><span>*.swp</span></div></div><div><div><div>22</div></div><div><span>*.swo</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span># 系统文件</span></div></div><div><div><div>25</div></div><div><span>.DS_Store</span></div></div><div><div><div>26</div></div><div><span>Thumbs.db</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span># 日志</span></div></div><div><div><div>29</div></div><div><span>*.log</span></div></div><div><div><div>30</div></div><div><span>logs/</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span># 临时文件</span></div></div><div><div><div>33</div></div><div><span>tmp/</span></div></div><div><div><div>34</div></div><div><span>temp/</span></div></div><div><div><div>35</div></div><div><span>*.tmp</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>
<section><h2>十、常用命令速查表<a href="#十常用命令速查表"><span>#</span></a></h2>

<table><thead><tr><th>场景</th><th>命令</th></tr></thead><tbody><tr><td>初始化仓库</td><td><code>git init</code></td></tr><tr><td>克隆仓库</td><td><code>git clone &lt;url&gt;</code></td></tr><tr><td>查看状态</td><td><code>git status</code></td></tr><tr><td>添加到暂存</td><td><code>git add .</code></td></tr><tr><td>提交</td><td><code>git commit -m "msg"</code></td></tr><tr><td>查看历史</td><td><code>git log --oneline --graph</code></td></tr><tr><td>创建分支</td><td><code>git switch -c &lt;branch&gt;</code></td></tr><tr><td>切换分支</td><td><code>git switch &lt;branch&gt;</code></td></tr><tr><td>合并分支</td><td><code>git merge &lt;branch&gt;</code></td></tr><tr><td>推送</td><td><code>git push origin &lt;branch&gt;</code></td></tr><tr><td>拉取更新</td><td><code>git pull --rebase origin main</code></td></tr><tr><td>暂存工作</td><td><code>git stash</code></td></tr><tr><td>恢复暂存</td><td><code>git stash pop</code></td></tr><tr><td>撤销修改</td><td><code>git restore &lt;file&gt;</code></td></tr><tr><td>取消暂存</td><td><code>git restore --staged &lt;file&gt;</code></td></tr><tr><td>撤销提交</td><td><code>git reset --soft HEAD~1</code></td></tr><tr><td>安全撤销</td><td><code>git revert &lt;commit&gt;</code></td></tr><tr><td>查看差异</td><td><code>git diff</code></td></tr><tr><td>查看分支</td><td><code>git branch -a</code></td></tr><tr><td>删除分支</td><td><code>git branch -d &lt;branch&gt;</code></td></tr><tr><td>打标签</td><td><code>git tag -a v1.0 -m "msg"</code></td></tr><tr><td>查找问题提交</td><td><code>git bisect start</code></td></tr><tr><td>清理仓库</td><td><code>git clean -fd</code></td></tr></tbody></table><hr /><p>以上内容覆盖了 Git 的核心知识点，从基础操作到高级技巧，以及团队协作的工作流程。建议在实际项目中多练习，Git 的很多功能只有在实际场景中才能真正掌握。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E9%9D%A2%E8%AF%95/ai%E9%9D%A2%E8%AF%95%E5%87%86%E5%A4%87%E6%B8%85%E5%8D%95_%E5%94%AE%E5%89%8D%E6%96%B9%E5%90%91/</id>
      <title type="text">AI 面试准备清单（售前解决方案方向）</title>
      <published>2026-09-10T00:00:00.000Z</published>
      <updated>2026-09-10T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E9%9D%A2%E8%AF%95/ai%E9%9D%A2%E8%AF%95%E5%87%86%E5%A4%87%E6%B8%85%E5%8D%95_%E5%94%AE%E5%89%8D%E6%96%B9%E5%90%91/"/>
      <summary type="text">AI 售前解决方案岗位面试准备，涵盖 AI 概念速查、行业趋势、工具对比、售前场景化问题、答题方法论</summary>
      <content type="html"><![CDATA[本文已加密保护，请访问网站查看。]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E9%9D%A2%E8%AF%95/%E7%95%99%E7%99%BD%E9%A1%B9%E7%9B%AE%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/</id>
      <title type="text">留白(Liubai)项目面试宝典</title>
      <published>2026-09-10T00:00:00.000Z</published>
      <updated>2026-09-10T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E9%9D%A2%E8%AF%95/%E7%95%99%E7%99%BD%E9%A1%B9%E7%9B%AE%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/"/>
      <summary type="text">留白心理健康伴侣 App 面试准备，涵盖 React + Capacitor + Supabase 技术栈、AI 协作开发方法论、高频面试问答</summary>
      <content type="html"><![CDATA[本文已加密保护，请访问网站查看。]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E9%9D%A2%E8%AF%95/%E7%A1%85%E8%B0%B7%E5%B0%8F%E6%99%BA%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/</id>
      <title type="text">硅谷小智 · AI 智能伴诊助手面试宝典</title>
      <published>2026-09-10T00:00:00.000Z</published>
      <updated>2026-09-10T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E9%9D%A2%E8%AF%95/%E7%A1%85%E8%B0%B7%E5%B0%8F%E6%99%BA%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/"/>
      <summary type="text">基于 LangChain4j + Spring Boot 3.2 的 AI 医疗伴诊助手项目面试准备，涵盖 RAG、Function Calling、流式输出、对话记忆等核心技术点</summary>
      <content type="html"><![CDATA[本文已加密保护，请访问网站查看。]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86/pmp%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E7%9F%A5%E8%AF%86%E4%BD%93%E7%B3%BB%E4%B8%8E%E9%9D%A2%E8%AF%95%E6%8C%87%E5%8D%97/</id>
      <title type="text">PMP 项目管理知识体系与面试指南</title>
      <published>2026-09-10T00:00:00.000Z</published>
      <updated>2026-09-10T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86/pmp%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E7%9F%A5%E8%AF%86%E4%BD%93%E7%B3%BB%E4%B8%8E%E9%9D%A2%E8%AF%95%E6%8C%87%E5%8D%97/"/>
      <summary type="text">PMP 项目管理核心知识体系整理，涵盖五大过程组、十大知识领域、关键工具与技术，以及项目管理岗位面试高频问题与答题思路</summary>
      <content type="html"><![CDATA[<p>本文系统梳理 PMP（Project Management Professional）项目管理知识体系的核心内容，面向准备项目管理相关岗位面试的同学。内容基于 PMBOK 第六版框架，兼顾第七版的最新理念，同时结合实际面试中的高频问题给出答题思路。</p>
<section><h2>一、PMP 与项目管理概览<a href="#一pmp-与项目管理概览"><span>#</span></a></h2><section><h3>什么是 PMP？<a href="#什么是-pmp"><span>#</span></a></h3><p>PMP 是由美国项目管理协会（PMI）颁发的项目管理专业资格认证，全球认可，是项目管理领域含金量最高的证书之一。</p></section><section><h3>项目管理的核心定义<a href="#项目管理的核心定义"><span>#</span></a></h3><blockquote><p><strong>项目</strong>是为创造独特的产品、服务或成果而进行的<strong>临时性</strong>工作。</p></blockquote><ul>
<li><strong>临时性</strong>：有明确的开始和结束时间（不代表时间短）</li>
<li><strong>独特性</strong>：每个项目的结果都是唯一的</li>
<li><strong>渐进明细</strong>：项目细节随推进逐步清晰</li>
</ul></section><section><h3>项目 vs 运营<a href="#项目-vs-运营"><span>#</span></a></h3>

<table><thead><tr><th>维度</th><th>项目</th><th>运营</th></tr></thead><tbody><tr><td>持续性</td><td>临时，有终点</td><td>持续，重复进行</td></tr><tr><td>目标</td><td>创造独特成果</td><td>维持业务运转</td></tr><tr><td>示例</td><td>开发一个 App</td><td>每天处理客服工单</td></tr></tbody></table></section></section>
<section><h2>二、五大过程组（Process Groups）<a href="#二五大过程组process-groups"><span>#</span></a></h2><p>项目管理的全生命周期分为 5 个过程组，它们不是严格的线性关系，而是<strong>重叠迭代</strong>的：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>启动 → 规划 → 执行 → 监控 → 收尾</span></div></div><div><div><div>2</div></div><div><span><span>         </span></span><span>↑________↓</span></div></div><div><div><div>3</div></div><div><span><span>         </span></span><span>（监控贯穿始终）</span></div></div></code></pre><div><div></div><div></div></div></figure></div><section><h3>1. 启动过程组（Initiating）<a href="#1-启动过程组initiating"><span>#</span></a></h3><p><strong>核心任务</strong>：定义项目、获得授权</p>

<table><thead><tr><th>过程</th><th>关键输出</th><th>说明</th></tr></thead><tbody><tr><td>识别干系人</td><td>干系人登记册</td><td>识别谁会影响/被影响</td></tr><tr><td>制定项目章程</td><td><strong>项目章程</strong></td><td>正式授权项目的文件，由发起人签发</td></tr></tbody></table><p><strong>面试高频问题</strong>：「项目章程包含什么？」</p><ul>
<li>项目目的与目标</li>
<li>高层级需求</li>
<li>里程碑计划</li>
<li>预算概要</li>
<li>委派的项目经理及权限</li>
<li>发起人签字</li>
</ul></section><section><h3>2. 规划过程组（Planning）<a href="#2-规划过程组planning"><span>#</span></a></h3><p><strong>核心任务</strong>：制定行动方案，<strong>规划是最重要的过程组</strong>（PMP 考试重点）</p><p>规划过程组包含 24 个过程，是过程数量最多的一组。核心规划文件：</p>

<table><thead><tr><th>文件</th><th>作用</th></tr></thead><tbody><tr><td><strong>项目管理计划</strong></td><td>整合所有子计划的主文件（范围、进度、成本、质量、资源、沟通、风险、采购、干系人）</td></tr><tr><td><strong>WBS</strong>（工作分解结构）</td><td>把项目范围逐层分解为可管理的工作包</td></tr><tr><td><strong>进度计划</strong></td><td>活动顺序、资源、工期 → 甘特图/网络图</td></tr><tr><td><strong>成本基准</strong></td><td>经批准的按时间段分配的项目预算</td></tr><tr><td><strong>风险登记册</strong></td><td>记录已识别的风险及应对策略</td></tr></tbody></table><p><strong>WBS 核心要点</strong>：</p><ul>
<li><strong>100% 规则</strong>：WBS 必须包含项目全部范围，不多不少</li>
<li><strong>最底层元素叫工作包</strong>（Work Package）</li>
<li>WBS 不是活动清单，是<strong>可交付成果</strong>的分解</li>
</ul></section><section><h3>3. 执行过程组（Executing）<a href="#3-执行过程组executing"><span>#</span></a></h3><p><strong>核心任务</strong>：协调资源，执行计划，产出可交付成果</p><p>重点过程：</p><ul>
<li><strong>指导与管理项目工作</strong>：产出可交付成果、工作绩效数据</li>
<li><strong>管理质量</strong>：执行质量管理活动（质量审计）</li>
<li><strong>获取资源 / 建设团队 / 管理团队</strong></li>
<li><strong>管理沟通</strong>：按沟通计划分发信息</li>
<li><strong>实施采购</strong>：外部采购的执行</li>
<li><strong>实施风险应对</strong></li>
</ul></section><section><h3>4. 监控过程组（Monitoring &amp; Controlling）<a href="#4-监控过程组monitoring--controlling"><span>#</span></a></h3><p><strong>核心任务</strong>：跟踪、审查、调整项目进展，贯穿项目始终</p><p>重点过程：</p>

<table><thead><tr><th>过程</th><th>作用</th></tr></thead><tbody><tr><td>监控项目工作</td><td>收集绩效数据，与基准对比</td></tr><tr><td><strong>整体变更控制</strong></td><td>审查所有变更请求，批准/否决</td></tr><tr><td>确认范围</td><td>干系人<strong>正式验收</strong>可交付成果</td></tr><tr><td>控制范围/进度/成本</td><td>防止范围蔓延、监控偏差</td></tr><tr><td>监督风险</td><td>跟踪已识别风险、识别新风险</td></tr></tbody></table><p><strong>变更管理是 PMP 考试的绝对重点</strong>：</p><ul>
<li>任何变更都必须走<strong>整体变更控制流程</strong></li>
<li>变更请求 → CCB（变更控制委员会）评估 → 批准/否决 → 更新计划 → 通知干系人</li>
<li><strong>不能绕过 CCB 直接实施变更</strong>（哪怕客户口头要求）</li>
</ul></section><section><h3>5. 收尾过程组（Closing）<a href="#5-收尾过程组closing"><span>#</span></a></h3><p><strong>核心任务</strong>：正式结束项目或阶段</p><p>收尾活动：</p><ul>
<li>验收最终可交付成果</li>
<li>关闭合同/采购</li>
<li>归档项目文件</li>
<li>总结经验教训（<strong>经验教训登记册</strong>）</li>
<li>释放资源</li>
<li>发布项目最终报告</li>
</ul><p><strong>面试常考</strong>：「项目可以被取消吗？」</p><ul>
<li>可以，在任何阶段都可以因商业原因被终止（如市场变化、资金不足），但必须走<strong>收尾流程</strong>，不能直接丢弃</li>
</ul></section></section>
<section><h2>三、十大知识领域（Knowledge Areas）<a href="#三十大知识领域knowledge-areas"><span>#</span></a></h2><section><h3>1. 项目整合管理<a href="#1-项目整合管理"><span>#</span></a></h3><p><strong>核心</strong>：协调所有知识领域的活动，是项目经理最核心的职责</p><p>关键工具：</p><ul>
<li><strong>专家判断</strong>：最常用的工具，几乎每个过程都会用到</li>
<li><strong>会议</strong>：开工会议（Kick-off Meeting）标志执行开始</li>
<li><strong>变更控制工具</strong>：变更日志、配置管理</li>
</ul></section><section><h3>2. 项目范围管理<a href="#2-项目范围管理"><span>#</span></a></h3><p><strong>核心</strong>：做且只做该做的事</p>

<table><thead><tr><th>过程</th><th>关键点</th></tr></thead><tbody><tr><td>规划范围管理</td><td>确定如何定义、确认和控制范围</td></tr><tr><td>收集需求</td><td>需求文件、需求跟踪矩阵</td></tr><tr><td>定义范围</td><td>项目范围说明书</td></tr><tr><td>创建 WBS</td><td>WBS、WBS 词典</td></tr><tr><td>确认范围</td><td>干系人<strong>正式验收</strong>（客户签字）</td></tr><tr><td>控制范围</td><td>防止范围蔓延</td></tr></tbody></table><p><strong>范围蔓延（Scope Creep）</strong>：未经控制的范围变更，是项目失败的主要原因之一</p><p><strong>确认范围 vs 质量控制</strong>：</p><ul>
<li>确认范围：关注<strong>验收</strong>（客户说”对，这就是我要的”）</li>
<li>质量控制：关注<strong>正确性</strong>（产品是否符合质量标准）</li>
</ul></section><section><h3>3. 项目进度管理<a href="#3-项目进度管理"><span>#</span></a></h3><p><strong>核心</strong>：按时完成项目</p><p>关键工具与技术：</p><p><strong>进度压缩</strong>（常考）：</p><ul>
<li><strong>赶工（Crashing）</strong>：增加资源缩短工期（如加人、加班），<strong>成本增加</strong></li>
<li><strong>快速跟进（Fast Tracking）</strong>：并行执行原本串行的活动，<strong>风险增加</strong></li>
</ul><p><strong>关键路径法（CPM）</strong>：</p><ul>
<li>关键路径 = 网络图中<strong>最长的路径</strong> = 项目的最短工期</li>
<li>关键路径上活动的浮动时间为 <strong>0</strong></li>
<li>延长关键路径活动 = 项目延期</li>
</ul><p><strong>三点估算（PERT）</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>期望值 = (最乐观 + 4×最可能 + 最悲观) / 6</span></div></div><div><div><div>2</div></div><div><span>标准差 = (最悲观 - 最乐观) / 6</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>面试常考</strong>：「项目延期了怎么办？」</p><ol>
<li>先分析原因（范围变更？资源不足？风险发生？）</li>
<li>评估关键路径是否受影响</li>
<li>选择压缩方案：赶工（增加成本）或快速跟进（增加风险）</li>
<li>必要时与干系人协商调整基准（需走变更流程）</li>
</ol></section><section><h3>4. 项目成本管理<a href="#4-项目成本管理"><span>#</span></a></h3><p><strong>核心</strong>：在批准的预算内完成项目</p><p><strong>挣值管理（EVM）</strong> —— <strong>PMP 考试和面试的绝对重点</strong>：</p>

<table><thead><tr><th>指标</th><th>公式</th><th>含义</th></tr></thead><tbody><tr><td><strong>PV</strong>（计划值）</td><td>—</td><td>截止目前，计划完成的工作的价值</td></tr><tr><td><strong>EV</strong>（挣值）</td><td>—</td><td>截止目前，实际完成的工作的价值</td></tr><tr><td><strong>AC</strong>（实际成本）</td><td>—</td><td>截止目前，实际花费的成本</td></tr><tr><td><strong>SV</strong>（进度偏差）</td><td>EV - PV</td><td>&gt;0 进度提前，&lt;0 进度落后</td></tr><tr><td><strong>CV</strong>（成本偏差）</td><td>EV - AC</td><td>&gt;0 成本节约，&lt;0 成本超支</td></tr><tr><td><strong>SPI</strong>（进度绩效指数）</td><td>EV / PV</td><td>&gt;1 提前，&lt;1 落后</td></tr><tr><td><strong>CPI</strong>（成本绩效指数）</td><td>EV / AC</td><td>&gt;1 节约，&lt;1 超支</td></tr><tr><td><strong>EAC</strong>（完工估算）</td><td>BAC / CPI</td><td>按当前效率完成项目的总成本</td></tr><tr><td><strong>ETC</strong>（完工尚需估算）</td><td>EAC - AC</td><td>还需要花多少钱</td></tr><tr><td><strong>VAC</strong>（完工偏差）</td><td>BAC - EAC</td><td>最终是节约还是超支</td></tr></tbody></table><p><strong>记忆口诀</strong>：</p><ul>
<li><strong>SV 和 CV 都是 EV 打头</strong>：EV - PV（进度），EV - AC（成本）</li>
<li><strong>SPI 和 CPI 都是 EV 做分子</strong>：EV/PV（进度），EV/AC（成本）</li>
<li><strong>大于 1 或正数 = 好</strong>，小于 1 或负数 = 差</li>
</ul><p><strong>面试常考</strong>：「CPI = 0.8 意味着什么？」</p><ul>
<li>每花 1 元钱只挣回 0.8 元的价值，成本超支 20%</li>
<li>完工估算 EAC = BAC / 0.8，意味着按当前效率项目将超预算</li>
</ul></section><section><h3>5. 项目质量管理<a href="#5-项目质量管理"><span>#</span></a></h3><p><strong>核心</strong>：满足需求和适用性</p><p><strong>质量 vs 等级</strong>：</p><ul>
<li>质量低是问题，等级低不一定是问题</li>
<li>例：廉价笔记本电脑等级低但质量可以很高</li>
</ul><p><strong>质量管理三部曲</strong>：</p><ol>
<li><strong>规划质量</strong>：确定质量标准和如何满足</li>
<li><strong>管理质量</strong>：执行质量审计（过程层面）</li>
<li><strong>控制质量</strong>：检验产品是否符合标准（产品层面）</li>
</ol><p><strong>七种基本质量工具</strong>（常考）：</p><ol>
<li><strong>因果图</strong>（鱼骨图/石川图）：找根本原因</li>
<li><strong>流程图</strong>：展示过程步骤</li>
<li><strong>检查表</strong>（核对单）：收集数据</li>
<li><strong>直方图</strong>：显示频率分布</li>
<li><strong>帕累托图</strong>：80/20 法则，优先解决主要问题</li>
<li><strong>控制图</strong>：过程是否稳定（上下控制限）</li>
<li><strong>散点图</strong>：两个变量之间的关系</li>
</ol></section><section><h3>6. 项目资源管理<a href="#6-项目资源管理"><span>#</span></a></h3><p><strong>核心</strong>：获取、管理、领导项目团队</p><p><strong>团队发展五阶段（塔克曼模型）</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>形成 → 震荡 → 规范 → 成熟 → 解散</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li><strong>震荡期</strong>冲突最多，是正常的，项目经理要引导</li>
<li><strong>成熟期</strong>效率最高</li>
</ul><p><strong>冲突解决方式</strong>（按效果排序）：</p><ol>
<li><strong>合作/解决问题</strong>（最佳）：双赢</li>
<li><strong>妥协/调和</strong>：各让一步</li>
<li><strong>缓和/包容</strong>：强调一致点</li>
<li><strong>撤退/回避</strong>：暂时搁置</li>
<li><strong>强迫/命令</strong>（最差）：一方赢一方输</li>
</ol><p><strong>面试常考</strong>：「团队成员之间有冲突怎么办？」</p><ul>
<li>先私下了解情况，再让双方当面沟通</li>
<li>引导聚焦共同目标，寻找双赢方案</li>
<li>如果无法解决，升级到职能经理或 HR</li>
</ul></section><section><h3>7. 项目沟通管理<a href="#7-项目沟通管理"><span>#</span></a></h3><p><strong>核心</strong>：在正确的时间把正确的信息给正确的人</p><p><strong>沟通渠道数</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>渠道数 = n(n-1) / 2    （n = 人数）</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>10 人团队：10×9/2 = 45 条渠道</li>
<li>加 1 人变 11 人：11×10/2 = 55 条渠道（增加 10 条）</li>
</ul><p><strong>沟通方法</strong>：</p><ul>
<li><strong>交互式</strong>：面对面、电话、视频会议（双向，最有效）</li>
<li><strong>推式</strong>：邮件、备忘录（单向发送）</li>
<li><strong>拉式</strong>：网站、知识库（接收者主动获取）</li>
</ul></section><section><h3>8. 项目风险管理<a href="#8-项目风险管理"><span>#</span></a></h3><p><strong>核心</strong>：不确定性管理</p><p><strong>风险应对策略</strong>：</p><p><strong>负面风险（威胁）</strong>：</p>

<table><thead><tr><th>策略</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>规避</td><td>消除威胁</td><td>改用成熟技术</td></tr><tr><td>转移</td><td>转嫁给第三方</td><td>买保险、外包</td></tr><tr><td>减轻</td><td>降低概率或影响</td><td>增加测试、备用方案</td></tr><tr><td>接受</td><td>不主动处理</td><td>设应急储备金</td></tr></tbody></table><p><strong>正面风险（机会）</strong>：</p>

<table><thead><tr><th>策略</th><th>说明</th></tr></thead><tbody><tr><td>开拓</td><td>确保机会实现</td></tr><tr><td>分享</td><td>与第三方共享</td></tr><tr><td>提高</td><td>增加概率或影响</td></tr><tr><td>接受</td><td>不主动追求</td></tr></tbody></table><p><strong>风险类型</strong>：</p><ul>
<li><strong>已知-已知</strong>：已识别、已分析 → 计入成本基准</li>
<li><strong>已知-未知</strong>：已识别但不确定 → 应急储备（管理储备之外）</li>
<li><strong>未知-未知</strong>：完全无法预见 → 管理储备（需管理层批准使用）</li>
</ul></section><section><h3>9. 项目采购管理<a href="#9-项目采购管理"><span>#</span></a></h3><p><strong>核心</strong>：从外部获取产品或服务</p><p><strong>合同类型</strong>（常考）：</p>

<table><thead><tr><th>合同类型</th><th>买方风险</th><th>卖方风险</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>固定总价（FFP）</strong></td><td>低</td><td>高</td><td>需求明确</td></tr><tr><td><strong>成本加固定费（CPFF）</strong></td><td>高</td><td>低</td><td>需求不明确</td></tr><tr><td><strong>工料合同（T&amp;M）</strong></td><td>中</td><td>中</td><td>短期/小规模工作</td></tr></tbody></table><p><strong>记忆</strong>：需求越明确，越适合固定总价（卖方承担风险）；需求越模糊，越适合成本补偿（买方承担风险）</p></section><section><h3>10. 项目干系人管理<a href="#10-项目干系人管理"><span>#</span></a></h3><p><strong>核心</strong>：识别并管理干系人期望</p><p><strong>干系人分析矩阵</strong>：</p>

<table><thead><tr><th></th><th>权力高</th><th>权力低</th></tr></thead><tbody><tr><td><strong>利益高</strong></td><td>重点管理</td><td>保持沟通</td></tr><tr><td><strong>利益低</strong></td><td>令其满意</td><td>监督即可</td></tr></tbody></table><p><strong>权力/利益方格</strong>是面试常考工具：用 2×2 矩阵把干系人分为四类，制定不同管理策略。</p></section></section>
<section><h2>四、敏捷项目管理（PMP 新重点）<a href="#四敏捷项目管理pmp-新重点"><span>#</span></a></h2><p>PMP 考试从 2021 年起大幅增加敏捷内容（约 50%），面试也越来越重视。</p><section><h3>敏捷 vs 瀑布<a href="#敏捷-vs-瀑布"><span>#</span></a></h3>

<table><thead><tr><th>维度</th><th>瀑布</th><th>敏捷</th></tr></thead><tbody><tr><td>需求</td><td>早期固定</td><td>持续演进</td></tr><tr><td>交付</td><td>一次性交付</td><td>迭代增量交付</td></tr><tr><td>变更</td><td>控制变更</td><td>拥抱变更</td></tr><tr><td>团队</td><td>项目经理驱动</td><td>自组织团队</td></tr><tr><td>文档</td><td>详尽文档</td><td>够用即可</td></tr></tbody></table></section><section><h3>Scrum 框架（最常用的敏捷方法）<a href="#scrum-框架最常用的敏捷方法"><span>#</span></a></h3><p><strong>三个角色</strong>：</p><ul>
<li><strong>产品负责人（PO）</strong>：管理产品待办列表，代表业务方</li>
<li><strong>Scrum Master（SM）</strong>：服务型领导，移除障碍，教练团队</li>
<li><strong>开发团队</strong>：自组织、跨职能，3-9 人</li>
</ul><p><strong>五个事件</strong>：</p><ol>
<li><strong>Sprint</strong>：固定时间盒（通常 2-4 周），每个 Sprint 产出可用的增量</li>
<li><strong>Sprint 规划会</strong>：确定本 Sprint 做什么</li>
<li><strong>每日站会（Daily Scrum）</strong>：15 分钟同步（昨天做了什么/今天做什么/有什么障碍）</li>
<li><strong>Sprint 评审会</strong>：演示增量，获取反馈</li>
<li><strong>Sprint 回顾会</strong>：团队反思改进</li>
</ol><p><strong>三个工件</strong>：</p><ul>
<li><strong>产品待办列表（Product Backlog）</strong>：所有需求的有序列表</li>
<li><strong>Sprint 待办列表（Sprint Backlog）</strong>：本 Sprint 要完成的工作</li>
<li><strong>增量（Increment）</strong>：Sprint 结束时的可用产品</li>
</ul><p><strong>敏捷中的关键概念</strong>：</p><ul>
<li><strong>用户故事</strong>：作为…，我想要…，以便于…</li>
<li><strong>故事点</strong>：衡量工作量的相对单位</li>
<li><strong>速度（Velocity）</strong>：团队每个 Sprint 完成的故事点数</li>
<li><strong>燃尽图</strong>：展示剩余工作量的趋势</li>
<li><strong>MVP（最小可行产品）</strong>：用最少功能验证假设</li>
</ul></section></section>
<section><h2>五、面试高频问题与答题框架<a href="#五面试高频问题与答题框架"><span>#</span></a></h2><section><h3>1. 「你如何管理一个项目？」<a href="#1-你如何管理一个项目"><span>#</span></a></h3><p><strong>答题框架</strong>（按五大过程组）：</p><blockquote><p>我会按五个阶段来管理：首先是<strong>启动</strong>，明确项目目标、识别关键干系人、获得项目章程授权；然后是<strong>规划</strong>，制定详细的范围、进度、成本、风险等计划；接着是<strong>执行</strong>，协调团队按计划推进；全程<strong>监控</strong>绩效、管理变更；最后<strong>收尾</strong>，验收成果、总结经验教训。</p></blockquote></section><section><h3>2. 「项目延期了怎么处理？」<a href="#2-项目延期了怎么处理"><span>#</span></a></h3><blockquote><ol>
<li>分析延期原因（范围蔓延？资源不足？风险发生？）</li>
<li>评估对关键路径的影响</li>
<li>如果是非关键路径活动，利用浮动时间缓冲</li>
<li>如果是关键路径，考虑赶工（增加资源）或快速跟进（并行活动）</li>
<li>必要时走变更流程调整基准，与干系人沟通新的预期</li>
</ol></blockquote></section><section><h3>3. 「如何管理干系人期望？」<a href="#3-如何管理干系人期望"><span>#</span></a></h3><blockquote><ol>
<li>启动阶段识别所有干系人，用权力/利益方格分类</li>
<li>对高权力高利益的干系人重点管理，定期沟通</li>
<li>建立沟通计划，明确频率、方式、内容</li>
<li>持续收集反馈，及时调整管理策略</li>
</ol></blockquote></section><section><h3>4. 「如何处理范围蔓延？」<a href="#4-如何处理范围蔓延"><span>#</span></a></h3><blockquote><ol>
<li>建立明确的变更控制流程，所有变更必须书面提出</li>
<li>由 CCB 评估变更的影响（范围、进度、成本、质量）</li>
<li>批准的变更更新基准，通知相关干系人</li>
<li>拒绝未经评估的变更请求</li>
<li>核心原则：<strong>没有免费的变更</strong>——增加范围必须调整时间或预算</li>
</ol></blockquote></section><section><h3>5. 「如何估算项目成本/工期？」<a href="#5-如何估算项目成本工期"><span>#</span></a></h3><blockquote><p>常用方法：</p><ul>
<li><strong>类比估算</strong>：参考类似项目（快但不精确）</li>
<li><strong>参数估算</strong>：用统计模型（如每功能点 10 人天）</li>
<li><strong>三点估算</strong>：(乐观 + 4×可能 + 悲观) / 6</li>
<li><strong>自下而上估算</strong>：从工作包逐层汇总（最精确但最耗时）</li>
</ul></blockquote></section><section><h3>6. 「CPI=0.75, SPI=0.9 怎么解读？」<a href="#6-cpi075-spi09-怎么解读"><span>#</span></a></h3><blockquote><ul>
<li>CPI=0.75：成本超支，每花 1 元只产出 0.75 元价值</li>
<li>SPI=0.9：进度落后，只完成了计划工作的 90%</li>
<li>项目同时面临成本超支和进度延迟</li>
<li>需要分析根本原因，制定纠偏措施（赶工/范围调整/资源优化）</li>
</ul></blockquote></section><section><h3>7. 「敏捷和瀑布怎么选？」<a href="#7-敏捷和瀑布怎么选"><span>#</span></a></h3><blockquote><ul>
<li><strong>需求明确、变更少</strong> → 瀑布（建筑、制造）</li>
<li><strong>需求不确定、需快速验证</strong> → 敏捷（软件、创新产品）</li>
<li><strong>混合场景</strong> → 混合方法（前期瀑布规划 + 执行阶段敏捷迭代）</li>
</ul></blockquote></section></section>
<section><h2>六、关键公式速查表<a href="#六关键公式速查表"><span>#</span></a></h2>

<table><thead><tr><th>公式</th><th>用途</th></tr></thead><tbody><tr><td>EV - PV</td><td>进度偏差 SV</td></tr><tr><td>EV - AC</td><td>成本偏差 CV</td></tr><tr><td>EV / PV</td><td>进度绩效指数 SPI</td></tr><tr><td>EV / AC</td><td>成本绩效指数 CPI</td></tr><tr><td>BAC / CPI</td><td>完工估算 EAC（典型偏差）</td></tr><tr><td>AC + (BAC - EV)</td><td>EAC（非典型偏差，一次性）</td></tr><tr><td>(乐观 + 4×可能 + 悲观) / 6</td><td>PERT 期望值</td></tr><tr><td>(悲观 - 乐观) / 6</td><td>PERT 标准差</td></tr><tr><td>n(n-1) / 2</td><td>沟通渠道数</td></tr></tbody></table></section>
<section><h2>七、PMP 考试要点速记<a href="#七pmp-考试要点速记"><span>#</span></a></h2><ul>
<li><strong>项目经理的权力</strong>：职位权力 &lt; 参照权力 &lt; 专家权力（最有效的是专家权力）</li>
<li><strong>镀金（Gold Plating）</strong>：主动做额外工作，<strong>不被提倡</strong>（增加成本和风险）</li>
<li><strong>预防成本 vs 评估成本</strong>：预防成本（培训、过程改进）远比评估成本（返工、检验）划算</li>
<li><strong>渐进明细</strong>：项目早期信息少、不确定性高，随着推进细节逐步清晰</li>
<li><strong>管理储备</strong>：用于应对”未知-未知”风险，使用需管理层批准，<strong>不计入成本基准</strong></li>
<li><strong>应急储备</strong>：用于应对”已知-未知”风险，<strong>计入成本基准</strong>，项目经理有权使用</li>
</ul><hr /><p>以上内容覆盖了 PMP 知识体系的核心框架和面试高频考点。建议结合实际项目经验理解每个知识点，面试时用具体案例支撑理论回答，会更有说服力。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E4%BA%91%E8%AE%A1%E7%AE%97/%E4%B8%89%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%BF%83%E7%AF%87/</id>
      <title type="text">（三）【数据中心篇】</title>
      <published>2026-08-26T00:00:00.000Z</published>
      <updated>2026-08-26T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E4%BA%91%E8%AE%A1%E7%AE%97/%E4%B8%89%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%BF%83%E7%AF%87/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<section><h2>自建机房的概念及其优缺点<a href="#自建机房的概念及其优缺点"><span>#</span></a></h2><blockquote><p>自建机房是企业自主投资建设的专属数据中心，企业自行承担机房的所有工作，包括规划建设类、部署类、运维类。</p></blockquote><section><h3>优点<a href="#优点"><span>#</span></a></h3><ol>
<li>自主可控性高，适配敏感业务需求</li>
<li>数据安全性与隐私性更高：企业可自主部署防火墙、入侵检测系统、数据加密方案，实现全链路数据管控。</li>
</ol></section><section><h3>缺点<a href="#缺点"><span>#</span></a></h3><ol>
<li>前期重资产投入，资金压力大</li>
<li>运维门槛高，人力成本高昂</li>
<li>建设周期长，无法快速响应业务需求</li>
<li>可靠性依赖企业自身能力，风险系数高</li>
</ol></section></section>
<section><h2>传统IDC的概念及其优缺点<a href="#传统idc的概念及其优缺点"><span>#</span></a></h2><blockquote><p>传统IDC(Internet Data Center)是指<strong>专业服务商建设的标准化数据中心</strong>，主要提供<strong>托管和租用</strong>两种服务类型。</p></blockquote><ul>
<li>实体服务器托管：用户自行购买硬件发往运营商机房托管</li>
<li>租用：由IDC数据中心提供实体设备供用户租用</li>
</ul><section><h3>优点<a href="#优点-1"><span>#</span></a></h3><ol>
<li>轻资产投入，降低资金压力</li>
<li>运维专业化，降低技术门槛</li>
<li>快速部署，适配业务灵活扩张</li>
<li>高可靠性与合规性，满足客户信任需求</li>
</ol></section><section><h3>缺点<a href="#缺点-1"><span>#</span></a></h3><ol>
<li>资源共享性，存在性能瓶颈与安全隐患</li>
<li>定制化能力弱，无法匹配特殊业务需求</li>
<li>依赖服务商，存在运营风险</li>
</ol></section></section>
<section><h2>传统IDC和云计算的相似点和差异<a href="#传统idc和云计算的相似点和差异"><span>#</span></a></h2><section><h3>相似点<a href="#相似点"><span>#</span></a></h3><ul>
<li>都租用IT基础设施资源，盈利以收取租用费用为主</li>
<li>目标客户是需要对公众提供互联网服务的客户或为降低企业的IT成本、追求性价比的企业</li>
</ul></section><section><h3>差异<a href="#差异"><span>#</span></a></h3><ol>
<li><strong>租用资源范围差别</strong>：云计算业务提供租用的资源范围广泛延伸，不仅是服务器、带宽，还有存储平台、开发平台等，还有相关的应用软件和成熟化的服务</li>
<li><strong>服务能力差别</strong>：云计算业务提供按需扩展的资源租用服务，提供了更加强大的弹性伸缩能力</li>
<li><strong>资源透明性差异</strong>：云计算业务中，用户并不需要过多地了解资源的物理信息，而更多的焦点是关注自己的业务</li>
<li><strong>运维难度降低</strong>：对客户而言，上云后不需过多关注底层基础架构的实现细节，而更多的是做好与云服务商的沟通，同时结合少量的运维即可</li>
</ol></section></section>
<section><h2>建造数据中心要考虑什么因素？<a href="#建造数据中心要考虑什么因素"><span>#</span></a></h2><ol>
<li><strong>地理因素</strong>：海波高、气温低（环境气温低，方便数据中心散热）、非地震带（一般选址在贵阳、内蒙）</li>
<li><strong>成本因素</strong>：电费</li>
<li><strong>政策因素</strong>：国家规划、当地招商引资</li>
</ol></section>
<section><h2>数据中心的基础设施有哪些？<a href="#数据中心的基础设施有哪些"><span>#</span></a></h2><ol>
<li>IT基础设施</li>
</ol><ul>
<li>计算设备：服务器，是算力的核心载体</li>
<li>存储设备：磁盘阵列(SAN/NAS)、分布式存储集群、对象存储设备</li>
<li>网络设备：核心交换机、接入交换机、路由器、防火墙、负载均衡器。负责连接所有计算/存储设备，实现数据传输与网络隔离</li>
<li>基础软件：操作系统、数据库软件、防病毒软件等</li>
<li>管理支撑软件：机房管理软件、集群管理软件、云平台软件、虚拟化软件等</li>
</ul><ol>
<li>配套基础设施</li>
</ol><ul>
<li>供配电系统</li>
<li>制冷系统</li>
<li>安防和监控系统</li>
<li>消防系统</li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E4%BA%91%E8%AE%A1%E7%AE%97/%E4%BA%8C%E4%BA%91%E8%AE%A1%E7%AE%97%E6%8A%80%E6%9C%AF%E7%AF%87/</id>
      <title type="text">（二）【云计算技术篇】</title>
      <published>2026-08-26T00:00:00.000Z</published>
      <updated>2026-08-26T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E4%BA%91%E8%AE%A1%E7%AE%97/%E4%BA%8C%E4%BA%91%E8%AE%A1%E7%AE%97%E6%8A%80%E6%9C%AF%E7%AF%87/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<section><h2>云计算的技术架构<a href="#云计算的技术架构"><span>#</span></a></h2><blockquote><p>云计算从整体架构来看，分为三个层次：基础架构层、中间层、应用层</p></blockquote><ol>
<li><strong>基础架构层</strong>：主要由服务器、存储设备、网络设备等真实的基础设施硬件虚实结合虚拟化手段，从而构建虚拟化的云计算的资源池</li>
<li><strong>中间层</strong>：在基础架构层的基础上，不同厂商通过中间件、数据库、访问控制、负载均衡等手段，根据自己的云计算解决方案，构建云计算的平台，又称云平台层</li>
<li><strong>应用层</strong>：在搭建好云计算平台的基础上，部署企业相关应用，比如企业的CRM、ERP、OA等企业应用系统</li>
</ol><p></p><figure><img src="/images/%E4%BA%91%E8%AE%A1%E7%AE%97/cloud-2.png" alt="Pasted image 20260422124833" /><figcaption>Pasted image 20260422124833</figcaption></figure><p></p></section>
<section><h2>云计算中的关键技术<a href="#云计算中的关键技术"><span>#</span></a></h2><blockquote><p>虚拟化、分布式数据存储、云平台管理软件以及其它诸如HBase、Hadoop等相关技术</p></blockquote><section><h3>容器与编排技术<a href="#容器与编排技术"><span>#</span></a></h3><ol>
<li>虚拟化技术：KVM</li>
</ol><ul>
<li>是一种将<strong>多台物理服务器虚拟成多台服务器的技术</strong></li>
<li>简单理解就是在一台大型服务器上同时运行多个相互隔离的系统环境，但它们共享底层的硬件资源</li>
<li>核心价值在于<strong>最大化硬件资源利用率</strong></li>
<li>对于拥有多个业务系统的企业来说，能够显著节约成本和优化机房空间</li>
</ul><ol>
<li>容器技术：轻量级应用打包部署（使用Docker实现）</li>
</ol><ul>
<li>是一种轻量级的应用打包技术，能把一个软件应用机及其运行所需的依赖（如程序文件、配置、库文件）全部封装成一个“容器”</li>
<li>这个容器就像一个标准化的“盒子”，无论放到哪种环境都能稳定运行</li>
<li>让应用部署变得迅速、可靠且易于复制</li>
</ul><blockquote><p><strong>容器和Docker是什么关系？</strong>
容器是“隔离沙箱”，Docker是实现容器技术的工具平台，它不是容器本身，而是让容器的创建、运行、分发、管理变得简单易用的一套技术体系，核心能力包括：容器引擎、镜像管理、标准化封装</p><ol>
<li>Kubernetes(K8s)：容器编排管理</li>
</ol></blockquote><ul>
<li>是一款专门管理容器化应用的“智能调度中心”</li>
<li>K8s能自动完成容器的手动管理部署、更新、扩容和故障处理，让大量容器有序运行</li>
<li>本质是“让规模化容器管理更简单、更可靠”</li>
</ul><ol>
<li>网络虚拟化技术 — SDN</li>
</ol><ul>
<li>软件定义网络（Software Defined Network，SDN）</li>
<li>是网络虚拟化的一种实现方式，其核心技术OpenFlow通过网络设备控制面与数据面分离开来，从而实现了网络流量的灵活控制</li>
</ul></section><section><h3>分布式数据存储技术<a href="#分布式数据存储技术"><span>#</span></a></h3><blockquote><p>指的是将数据冗余存储在多台设备上，保证数据的可靠性，通过任务分解和集群保证低成本和高性能</p></blockquote><ol>
<li>HDFS（Hadoop Distributed File System）</li>
</ol><ul>
<li>是Hadoop的分布式文件系统</li>
<li>优势：保存多个副本，提供容错机制，副本丢失或宕机自动恢复</li>
<li>劣势：无法实现低延时数据访问；不适合小文件存储；一个文件只能一个线程写；仅保持文件追加，不支持随即修改</li>
</ul><ol>
<li>CFS（Cloude File Storage）</li>
</ol><ul>
<li>是腾讯公司自主研发的分布式数据存储系统，提供可扩展的共享文件存储服务</li>
<li>高可用、高可靠，适合于大数据分析、媒体处理和内容管理</li>
<li>功能特征：集成管理、自动扩展、安全可靠、按需付费</li>
</ul></section><section><h3>云计算管理平台<a href="#云计算管理平台"><span>#</span></a></h3><ol>
<li>OpenStack</li>
</ol><ul>
<li>可以理解为企业内部的云计算管理平台，将企业内部的服务器、存储、网络等硬件资源统一管理，以类似公有云的方式提供给内部用户使用</li>
<li>简单来说就是把企业机房改造成像阿里云一样的自助式云服务平台，让业务部门能够按需申请和使用IT资源</li>
</ul><ol>
<li>VStation</li>
</ol><ul>
<li>腾讯公司自主研发的云计算管理平台</li>
<li>与OpenStack相比，具有代码量少、支持集群规模大、容灾能力强、运维便捷、人员投入少、性能优的优势</li>
</ul></section></section>
<section><h2>弹性伸缩的概念及其应用<a href="#弹性伸缩的概念及其应用"><span>#</span></a></h2><blockquote><p>弹性伸缩是一种云计算服务模式，用户可以根据自己的业务需求自动扩展或收缩计算资源，以满足不同的业务需求。</p></blockquote><ul>
<li>应用：Web应用程序、移动应用程序、物联网应用程序等。（例如，当一个Web应用程序的访问量突然增加时，可以自动扩展计算资源，以满足用户的需求。当访问量减少时，可以自动收缩计算资源，以降低成本。）</li>
</ul></section>
<section><h2>多租户技术的概念及其应用<a href="#多租户技术的概念及其应用"><span>#</span></a></h2><blockquote><p>多租户技术是指在一套共享的软硬件资源上，同时为多个租户提供服务的技术。租户可以是企业、组织或个人，各租户间数据和操作环境相互隔离、互不干扰，但底层资源由各租户共享，以实现资源利用率的最大化。</p></blockquote><ul>
<li>应用：SaaS是多租户技术最典型的应用场景。主流的SaaS应用（r如钉钉、飞书、腾讯文档等）均基于多租户架构构建，用户无需下载安装软件，通过浏览器即可访问服务。</li>
</ul></section>
<section><h2>无服务计算的概念及其应用<a href="#无服务计算的概念及其应用"><span>#</span></a></h2><blockquote><p>无服务计算是一种云计算服务模式，用户无需管理服务器等基础设施资源，只需要编写和上传自己的代码，由云计算服务提供商负责运行和管理代码。</p></blockquote><ul>
<li>应用
<ol>
<li>开发者可将业务逻辑拆分成多个独立函数，通过API网关暴露接口，构建轻量级后端服务。</li>
<li>物联网设备会产生海量碎片化数据（如传感器采集的温度、湿度数据），且数据上报频率不稳定。无服务函数可被设备数据上报事件触发，实时处理数据（如数据过滤、格式转换、异常报警），无需长期运行服务器等待数据</li>
</ol>
</li>
</ul></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E6%B5%8B%E8%AF%95/%E6%9C%AA%E5%91%BD%E5%90%8D/</id>
      <title type="text">未命名</title>
      <published>2026-08-26T00:00:00.000Z</published>
      <updated>2026-08-26T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E6%B5%8B%E8%AF%95/%E6%9C%AA%E5%91%BD%E5%90%8D/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<p>测试obsidian笔记自动同步至博客功能
</p><figure><img src="https://cloudflare-imgbed-55m.pages.dev/file/1787711511537_4dee724b470df0f9e88d165590c591af.jpg" alt="4dee724b470df0f9e88d165590c591af.jpg" /><figcaption>4dee724b470df0f9e88d165590c591af.jpg</figcaption></figure><p></p>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/%E4%BA%91%E8%AE%A1%E7%AE%97/%E4%B8%80%E4%BA%91%E8%AE%A1%E7%AE%97%E5%9F%BA%E7%A1%80%E7%AF%87/</id>
      <title type="text">（一）【云计算基础篇】</title>
      <published>2026-04-21T00:00:00.000Z</published>
      <updated>2026-08-26T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/%E4%BA%91%E8%AE%A1%E7%AE%97/%E4%B8%80%E4%BA%91%E8%AE%A1%E7%AE%97%E5%9F%BA%E7%A1%80%E7%AF%87/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<section><h2>什么是云计算？<a href="#什么是云计算"><span>#</span></a></h2><ul>
<li>云计算是一种<strong>按使用量付费</strong>的模式，这种模式提供可用的、便捷的、按需的网络访问，进入可配置的计算资源共享池（资源包括网络、服务、存储、应用软件、服务），这些资源能够被快速提供，只需投入很少的理性工作，或与服务供应商进行很少的交互。也就是说，<strong>云计算提供的是一种新的资源按需租用的服务模式，也是一种新型的互联网数据中心业务</strong>。</li>
<li>简单来说：按需购买，按量付费的互联网资源。</li>
</ul></section>
<section><h2>云计算有什么特性？<a href="#云计算有什么特性"><span>#</span></a></h2><ul>
<li><strong>超大规模</strong>：公有云平台动辄就有几十万台甚至上百万台服务器，企业私有云一般拥有数百上千台服务器。</li>
<li><strong>高可靠性</strong>：“云”使用了数据多副本容错、计算节点同构可互换等措施，提高数据的高可靠性。</li>
<li><strong>多租户隔离</strong>：允许多个不同的租户共享底层的硬件资源，但是在上层逻辑上是隔离的。</li>
<li><strong>弹性伸缩</strong>：可根据业务负载的变化自动或手动调整资源规模，满足应用和用户规模增长的需求。</li>
<li><strong>按需服务</strong>：用户可以根据自己的需求随时获取/使用云计算资源，无需人工干预。</li>
<li><strong>资源可监控计量</strong>：云平台提供资源的监控和计量功能，用户只需为实际使用的资源付费，无需承担传统IT架构中硬件采购、机房维护等固定成本。</li>
<li><strong>低成本</strong>：用户无需负担高昂的数据中心管理成本，以较低成本、最短时间获得最有效的资源，提供了极高的性价比。</li>
<li><strong>虚拟化</strong>：支持用户在任意位置，使用各种终端获取应用服务。</li>
</ul></section>
<section><h2>云计算的<strong>三层核心服务</strong><a href="#云计算的三层核心服务"><span>#</span></a></h2><ul>
<li>IaaS：基础设施即服务（提供基础设施资源）</li>
<li>PaaS：平台即服务（提供平台）</li>
<li>SaaS：软件即服务（提供应用程序）
<figure><img src="/images/%E4%BA%91%E8%AE%A1%E7%AE%97/cloud-1.png" alt="Pasted image 20260301161806" /><figcaption>Pasted image 20260301161806</figcaption></figure></li>
<li>灵活性：IaaS &gt; PaaS &gt; SaaS</li>
<li>成本：IaaS &gt; PaaS &gt; SaaS</li>
</ul></section>
<section><h2>云计算中有哪些计费方式？<a href="#云计算中有哪些计费方式"><span>#</span></a></h2><ul>
<li><strong>按需付费</strong>：用户根据自己的实际使用情况付费，使用多少就付多少，价格相对较高。</li>
<li><strong>包年包月</strong>：用户一次性购买一定时间内的计算资源，费用相对较低。</li>
<li><strong>预留实例</strong>：海外云厂商(AWS、Azure)的主流长期计费模式，用户预付一定费用购买特定规格实例的长期使用权。</li>
<li><strong>按竞价方式计费</strong>：云厂商针对闲置物理资源推出的低价计费模式，用户通过“竞拍”方式获取资源，价格远低于按需实例付费。适合可中断、非核心的业务场景。</li>
</ul></section>
<section><h2>云计算的四种模式以及它们之间的区别<a href="#云计算的四种模式以及它们之间的区别"><span>#</span></a></h2><section><h3>四种模式<a href="#四种模式"><span>#</span></a></h3><ul>
<li><strong>公有云：由第三方云服务商提供，通过互联网共享基础设施和服务</strong>
<ul>
<li>核心特性：按需付费、即开即用、资源弹性扩展，安全性依赖服务商的保障机制，无需企业自建基础设施。</li>
</ul>
</li>
<li><strong>私有云：由企业或者第三方建设和管理，由单一云服务用户专享</strong>（可能部署在企业内部或者托管数据中心，具备公有云所有关键特征，但硬件、软件、网络资源由该组织独占使用。）
<ul>
<li>核心特征：企业对资源的完全自主控制能力、较高的安全性水平、支持深度定制、技术灵活性高，但是需要企业自行维护基础设施且成本投入较高。</li>
</ul>
</li>
<li><strong>混合云：结合公有云和私有云</strong>
<ul>
<li>核心特性：整合两者优势，关键数据存储在私有云，利用公有云实现弹性资源扩展，部署复杂度较高。</li>
</ul>
</li>
<li><strong>行业云：由行业内或某个区域内起主导作用或者掌握关键资源的组织建立和维护</strong>（如金融云、政务云、医疗云、卫生云等）</li>
</ul></section><section><h3>四种模式的区别<a href="#四种模式的区别"><span>#</span></a></h3>

<table><thead><tr><th>模式</th><th>典型场景</th><th>业务价值</th><th>客户关注点</th><th>优势</th><th>劣势</th></tr></thead><tbody><tr><td><strong>公有云</strong></td><td>中小企业、电商促销、个人云存储 (如阿里云、AWS)</td><td>零硬件投入、快速扩容、降低门槛</td><td>成本、快速上线、数据主权与合规认证</td><td>规模大、成本低、功能全面</td><td>存在性能波动风险、定制化能力弱</td></tr><tr><td><strong>私有云</strong></td><td>金融核心系统、政府涉密平台 (如 OpenStack)</td><td>数据本地化、满足监管、避免多租户安全风险</td><td>安全与隐私要求高、自主可控、运维能力评估</td><td>支持定制、不影响现有 IT 流程</td><td>成本高、周期长、扩展灵活性差</td></tr><tr><td><strong>混合云</strong></td><td>核心数据在私有云，报表分析或弹性扩容在公有云</td><td>平衡成本与安全、敏感业务本地化、非敏感业务云化</td><td>兼顾合规与效率、关注跨云同步与网络设计</td><td>架构综合、部署灵活、数据本地化</td><td>架构复杂、成本较高、运维难度大</td></tr><tr><td><strong>行业云</strong></td><td>金融云、政务云、医疗云、卫生云</td><td>深度适配行业流程、提供垂直方案、减少重复建设</td><td>行业专属合规 (如等保、医疗隐私)、系统适配性</td><td>针对行业优化、成本较低、共享最佳实践</td><td>支持范围窄、跨行业复用性差、更新慢</td></tr></tbody></table></section></section>
<section><h2>公有云和私有云的对比<a href="#公有云和私有云的对比"><span>#</span></a></h2>

<table><thead><tr><th>功能</th><th>私有云</th><th>公有云</th></tr></thead><tbody><tr><td><strong>服务提供</strong></td><td>根据客户的需要提供</td><td>由服务提供商设置</td></tr><tr><td><strong>运维管理</strong></td><td>负责运维管理的是云服务提供商、客户自己或第三方，或是上述的组合</td><td>由云服务提供商负责运维管理</td></tr><tr><td><strong>安全管理、监控与认证</strong></td><td>由云服务提供商、客户自己或第三方，也或者是它们的不同组合</td><td>由云服务提供商负责安全管理</td></tr><tr><td><strong>弹性与可扩展</strong></td><td>可扩展，但受计算资源规模的限制</td><td>无限可扩展，有服务等级保证</td></tr><tr><td><strong>准备时间</strong></td><td>需要初始化建立</td><td>数分钟</td></tr></tbody></table></section>
<section><h2>私有云的三种部署方式<a href="#私有云的三种部署方式"><span>#</span></a></h2><ol>
<li><strong>本地部署私有云</strong>：部署在企业自有的数据中心内。</li>
</ol><ul>
<li>优点：控制力强，数据完全留在内部</li>
<li>缺点：前期资本投入高，需要专业的运维团队</li>
</ul><ol>
<li><strong>托管私有云</strong>：部署在第三方服务商(如电信运营商、IDC)的机房内</li>
</ol><ul>
<li>优点：无需自建机房</li>
<li>缺点：不如本地部署直接</li>
</ul><ol>
<li><strong>专有云</strong>：这是公有云厂商提供的一种服务，将公有云软硬件一体机放到本地的数据中心，提供和公有云一致的操作体验和API</li>
</ol><ul>
<li>优点：与公有云无缝混合，使用相同的工具和技术栈</li>
<li>缺点：成本较高，且与特定云厂商深度绑定</li>
</ul></section>
<section><h2>云计算的优缺点<a href="#云计算的优缺点"><span>#</span></a></h2><section><h3>优点<a href="#优点"><span>#</span></a></h3><ol>
<li>对于消费者</li>
</ol><ul>
<li>降低前期投入成本和日常使用成本</li>
<li>提高了系统的可靠性、安全性、用户体验</li>
<li>使用户可以专注于自己的核心业务和市场</li>
<li>快速响应对计算资源的弹性需要</li>
</ul><ol>
<li>对于社会</li>
</ol><ul>
<li>降低能耗、减少排放</li>
<li>提高IT设备使用率，降低设备数量，信息技术产业进一步分工合理</li>
<li>有利于全社会共享信息，打破信息孤岛</li>
</ul></section><section><h3>缺点<a href="#缺点"><span>#</span></a></h3><ul>
<li>严重依赖网络</li>
<li>数据可能泄露的环节增多（安全性）</li>
<li>用户对技术和数据的掌控度下降</li>
</ul></section></section>
<section><h2>云计算的演进<a href="#云计算的演进"><span>#</span></a></h2><ol>
<li><strong>积累阶段</strong>（1959——1999）：虚拟化、网络、分布式、并行等技术成熟。</li>
<li><strong>初步阶段</strong>（2006——2009）：Saleforce成立，成为全球最早的一家SaaS公司；同年Loudcloud成立，成为全球第一家IaaS服务商。</li>
<li><strong>形成阶段</strong>（2009——2015）：IaaS、PaaS和SaaS三种模式全部出现，IT/电信/互联网行业纷纷进军云计算领域。</li>
<li><strong>发展阶段</strong>（2009——2015）：云的种类逐渐完善，传统企业开始上云。</li>
<li><strong>现今阶段</strong>（2015——）：政府和公共服务也逐渐上云，云的产品和功能逐渐健全，形成了云的标准化。</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/linux/linux-%E5%AE%9E%E7%94%A8%E6%93%8D%E4%BD%9C/</id>
      <title type="text">Linux 实用操作</title>
      <published>2026-04-20T00:00:00.000Z</published>
      <updated>2026-04-20T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/linux/linux-%E5%AE%9E%E7%94%A8%E6%93%8D%E4%BD%9C/"/>
      <summary type="text">了解 Linux实用操作</summary>
      <content type="html"><![CDATA[<section><h2>各类小技巧快捷键<a href="#各类小技巧快捷键"><span>#</span></a></h2><section><h3>强制停止<code>ctrl + c</code><a href="#强制停止ctrl--c"><span>#</span></a></h3><ul>
<li>强制停止某些程序的运行</li>
<li>命令输入错误也可以通过该快捷键退出当前输入</li>
</ul></section><section><h3>退出或登出<code>ctrl + d</code><a href="#退出或登出ctrl--d"><span>#</span></a></h3><ul>
<li>退出当前账户的登录</li>
<li>退出某些特定程序的专属界面</li>
<li><strong>不能用于退出vi/vim</strong></li>
</ul></section><section><h3>历史命令搜索<a href="#历史命令搜索"><span>#</span></a></h3><ol>
<li><code>history</code> ：查看历史输入过的命令</li>
<li><code>！命令前缀</code> ：自动执行上一次匹配前缀的命令</li>
<li><code>ctrl + r</code> ：输入内容去匹配历史命令
<ul>
<li>回车键可以直接执行</li>
<li>键盘左右键，可以得到此命令(不执行)</li>
</ul>
</li>
</ol></section><section><h3>光标移动快捷键<a href="#光标移动快捷键"><span>#</span></a></h3><ol>
<li><code>ctrl + a</code> ：跳到命令开头</li>
<li><code>ctrl + e</code> ：跳到命令结尾</li>
<li><code>ctrl + 键盘左键</code> ：向左跳一个单词</li>
<li><code>ctrl + 键盘右键</code> ：向右跳一个单词</li>
</ol></section><section><h3>清屏<a href="#清屏"><span>#</span></a></h3><ol>
<li><code>ctrl + l</code> ：清空终端内容</li>
<li><code>clear</code> ：作用同上</li>
</ol></section></section>
<section><h2>软件安装<a href="#软件安装"><span>#</span></a></h2><section><h3>CentOS系统——yum命令<a href="#centos系统yum命令"><span>#</span></a></h3><ol>
<li>yum:RPM包软件管理器，用于自动化安装配置Linux软件，并可以自动解决依赖问题</li>
<li>语法：<code>yum [-y] [install | remove | search] 软件名称</code>
<ul>
<li>-y选项：自动确认，无需手动确认安装或卸载过程</li>
<li>install 安装，remove 卸载，search 搜索</li>
<li><strong>需要root权限，需要联网</strong></li>
</ul>
</li>
</ol></section><section><h3>Ubantu系统——apt命令<a href="#ubantu系统apt命令"><span>#</span></a></h3><ul>
<li>语法：<code>apt [-y] [install | remove | search] 软件名称</code>
<ul>
<li>-y选项：自动确认，无需手动确认安装或卸载过程</li>
<li>install 安装，remove 卸载，search 搜索</li>
<li><strong>需要root权限，需要联网</strong></li>
</ul>
</li>
</ul></section></section>
<section><h2>systemctl命令控制软件启动关闭<a href="#systemctl命令控制软件启动关闭"><span>#</span></a></h2><ol>
<li>能够被systemctl管理的软件，一般也称之为：服务</li>
<li>语法：<code>systemctl start | stop | status | enable | disable 服务名</code>
<ul>
<li>start启动，stop关闭，status查看状态，enable开启开机自启，disable关闭开机自启</li>
</ul>
</li>
<li>系统内置的服务举例：
<ul>
<li>NetworkManager：主网络服务</li>
<li>network：副网络服务</li>
<li>firewalld：防火墙服务</li>
<li>sshd,ssh服务</li>
</ul>
</li>
<li>除了内置的服务以外，部分第三方软件安装后也可以用systemctl进行控制</li>
</ol></section>
<section><h2>软链接<a href="#软链接"><span>#</span></a></h2><ol>
<li>软链接可以将文件、文件夹链接到其他位置</li>
<li>链接只是一个指向，并不是物理移动，类似于Windows系统中的“快捷方式”</li>
<li>语法：<code>ln -s 参数1 参数2</code>
<ul>
<li>-s选项：创建软链接</li>
<li>参数1：被链接的文件或文件夹</li>
<li>参数2：要链接去的目的地</li>
</ul>
</li>
</ol></section>
<section><h2>日期和时区<a href="#日期和时区"><span>#</span></a></h2><section><h3>date命令<a href="#date命令"><span>#</span></a></h3><ol>
<li>作用：在命令行中查看系统的时间</li>
<li>语法：<code>date [-d] [+格式化字符串]</code>
<ul>
<li>-d选项：按照给定的字符串显示日期，一般用于日期计算</li>
<li>格式化字符串：通过特定的字符串标记，来控制显示的日期格式
<ul>
<li>%Y 年</li>
<li>%y 年份后两位数字</li>
<li>%m 月份</li>
<li>%d 日</li>
<li>%H 小时</li>
<li>%M 分钟</li>
<li>%S 秒</li>
<li>%s 自1970-01-01 00:00:00 UTC到现在的秒数</li>
</ul>
</li>
<li>举例：
<ul>
<li>按照2026-01-01的格式显示日期：<code>date +%Y-%m-%d</code></li>
<li>按照2026-01-01 10:00:00 的格式显示日期：<code>date "+%Y-%m-%d %H:%M:%S"</code></li>
</ul>
</li>
</ul>
</li>
</ol></section><section><h3>date命令用于日期加减<a href="#date命令用于日期加减"><span>#</span></a></h3><ul>
<li><code>date -d "+1 day" +%Y%m%d</code> 显示后一天的日期</li>
<li><code>date -d "-1 day" +%Y%m%d</code> 显示前一天的日期</li>
<li><code>date -d "-1 month" +%Y%m%d</code> 显示上一月的日期</li>
<li><code>date -d "+1 month" +%Y%m%d</code> 显示下一月的日期</li>
<li><code>date -d "-1 year" +%Y%m%d</code> 显示前一年的日期</li>
<li><code>date -d "+1 year" +%Y%m%d</code> 显示下一年的日期</li>
</ul></section><section><h3>修改Linux时区<a href="#修改linux时区"><span>#</span></a></h3><ol>
<li>使用root权限，执行如下命令
<ul>
<li>删除系统自带的localtime文件：<code>rm -f /etc/localtime</code></li>
<li>创建软链接：<code>sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime</code></li>
</ul>
</li>
</ol><blockquote><p>(将系统自带的localtime文件删除，并将/usr/share/zoneinfo/Asia/Shanghai链接为localtime文件即可)</p></blockquote></section><section><h3>ntp程序校准系统时间<a href="#ntp程序校准系统时间"><span>#</span></a></h3><section><h4>自动校准<a href="#自动校准"><span>#</span></a></h4><ol>
<li>安装ntp：<code>yum -y install ntp</code></li>
<li>启动并设置开机自启
<code>systemctl start ntpd</code>
<code>systemctl enable ntpd</code></li>
<li>ntpd启动后会定期联网校准系统时间</li>
</ol></section><section><h4>手动校准(<strong>需要root权限</strong>)<a href="#手动校准需要root权限"><span>#</span></a></h4><ul>
<li><code>ntpdate -u ntp.aliyun.com</code></li>
</ul></section></section></section>
<section><h2>ip地址和主机名<a href="#ip地址和主机名"><span>#</span></a></h2><section><h3>ip地址<a href="#ip地址"><span>#</span></a></h3><ul>
<li><code>ifconfig</code> 查看本机ip地址</li>
<li>如无法使用ifconfig命令，可以安装：<code>yum -y install net-tools</code></li>
<li>特殊ip地址
<ul>
<li>127.0.0.1，这个ip地址用于指代本机</li>
<li>0.0.0.0
<ul>
<li>可以用于指代本机</li>
<li>可以在端口绑定中用来确定绑定关系</li>
<li>在一些IP地址限制中，表示所有IP的意思，如放行规则设置为0.0.0.0，表示允许任意IP访问</li>
</ul>
</li>
</ul>
</li>
</ul></section><section><h3>主机名<a href="#主机名"><span>#</span></a></h3><ol>
<li>查看主机名：<code>hostname</code></li>
<li>修改主机名(<strong>需root</strong>)：<code>hostnamectl set-hostname 主机名</code></li>
</ol></section></section>
<section><h2>网络请求和下载<a href="#网络请求和下载"><span>#</span></a></h2><section><h3>ping命令<a href="#ping命令"><span>#</span></a></h3><ol>
<li>作用：检查指定的网络服务器是否是可联通状态</li>
<li>语法：<code>ping [-c num] ip或主机名</code>
<ul>
<li>-c选项：检查的次数，不使用则无限次数持续检查</li>
<li>参数：ip或主机名，被检查的服务器的ip地址或主机名地址</li>
</ul>
</li>
</ol></section><section><h3>wget命令<a href="#wget命令"><span>#</span></a></h3><ol>
<li>作用：是非交互的文件下载器，可以在命令行内下载网络文件</li>
<li>语法：<code>wget [-b] url</code>
<ul>
<li>-b选项：可选，后台下载，会将日志写入当前工作目录的wget-log文件</li>
<li>url参数：下载链接</li>
</ul>
</li>
<li>通过tail命令可以监控后台下载进度：<code>tail -f wget-log</code></li>
<li>无论下载是否完成，都会生成要下载的文件，如果下载未完成，请及时清理未完成的不可用文件</li>
</ol></section><section><h3>curl命令<a href="#curl命令"><span>#</span></a></h3><ol>
<li>作用：发送http请求，可用于下载文件、获取信息等</li>
<li>语法：<code>curl [-O] url</code>
<ul>
<li>-O选项：用于下载文件，当url是下载链接时，可以使用此选项保存文件</li>
<li>url参数：要发起请求的网络地址</li>
</ul>
</li>
</ol></section></section>
<section><h2>端口<a href="#端口"><span>#</span></a></h2><ul>
<li>是设备与外界通讯交流的出入口。可以分为物理端口和虚拟端口</li>
</ul><section><h3>Linux端口<a href="#linux端口"><span>#</span></a></h3><ol>
<li>支持65535个端口，分为3类
<ul>
<li>公认端口：1~1023,通常用于一些系统内置或知名程序的预留使用，如SSH服务的22端口，HTTPS服务的443端口(非特殊需要，不要占用这个范围的端口)</li>
<li>注册端口：1024~49151，通常可以随意使用，用于松散的绑定一些程序/服务</li>
<li>动态端口：49152~65535，通常不会固定绑定程序，而是对程序对外进行网络链接时，用于临时使用</li>
</ul>
</li>
</ol></section><section><h3>查看端口占用<a href="#查看端口占用"><span>#</span></a></h3><section><h4>nmap查看端口占用<a href="#nmap查看端口占用"><span>#</span></a></h4><ol>
<li>安装nmap：<code>yum -y install nmap</code></li>
<li>语法：<code>nmap 被查看的IP地址</code></li>
</ol></section><section><h4>netstat查看指定端口占用<a href="#netstat查看指定端口占用"><span>#</span></a></h4><ol>
<li>安装netstat：<code>yum -y install net-tools</code></li>
<li>语法：<code>netstat -anp | grep 端口号</code></li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[sakura@localhost ~]$ netstat -anp | </span><span>grep</span><span> </span><span>111</span></div></div><div><div><div>2</div></div><div><span>(</span><span>Not</span><span> </span><span>all</span><span> </span><span>processes</span><span> </span><span>could</span><span> </span><span>be</span><span> </span><span>identified,</span><span> </span><span>non-owned</span><span> </span><span>process</span><span> </span><span>info</span></div></div><div><div><div>3</div></div><div><span> </span><span>will</span><span> </span><span>not</span><span> </span><span>be</span><span> </span><span>shown,</span><span> </span><span>you</span><span> </span><span>would</span><span> </span><span>have</span><span> </span><span>to</span><span> </span><span>be</span><span> </span><span>root</span><span> </span><span>to</span><span> </span><span>see</span><span> </span><span>it</span><span> </span><span>all.</span><span>)</span></div></div><div><div><div>4</div></div><div><span>tcp</span><span>        </span><span>0</span><span>      </span><span>0</span><span> </span><span>0.0.0.0:111</span><span>             </span><span>0.0.0.0:</span><span>*</span><span>               </span><span>LISTEN</span><span>      </span><span>-</span></div></div><div><div><div>5</div></div><div><span>tcp6</span><span>       </span><span>0</span><span>      </span><span>0</span><span> </span><span>:::111</span><span>                  </span><span>:::</span><span>*</span><span>                    </span><span>LISTEN</span><span>      </span><span>-</span></div></div><div><div><div>6</div></div><div><span>udp</span><span>        </span><span>0</span><span>      </span><span>0</span><span> </span><span>0.0.0.0:111</span><span>             </span><span>0.0.0.0:</span><span>*</span><span>                           </span><span>-</span></div></div><div><div><div>7</div></div><div><span>udp6</span><span>       </span><span>0</span><span>      </span><span>0</span><span> </span><span>:::111</span><span>                  </span><span>:::</span><span>*</span><span>                                </span><span>-</span></div></div><div><div><div>8</div></div><div><span>unix</span><span>  </span><span>3</span><span>      [ </span><span>]</span><span>         </span><span>STREAM</span><span>     </span><span>CONNECTED</span><span>     </span><span>33111</span><span>    </span><span>-</span><span>                    </span><span>@/tmp/dbus-90Vst45C</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section></section>
<section><h2>进程管理<a href="#进程管理"><span>#</span></a></h2><section><h3>进程<a href="#进程"><span>#</span></a></h3><ol>
<li>程序运行在操作系统中，是被操作系统所管理的</li>
<li>为管理运行的程序，每一个程序在运行时，都会被操作系统注册为系统中的一个进程并为每个进程都分配一个独有的进程ID(进程号)</li>
</ol></section><section><h3>ps命令查看进程<a href="#ps命令查看进程"><span>#</span></a></h3><ol>
<li>语法：<code>ps [-e -f]</code>
<ul>
<li>-e选项：显示出全部的进程</li>
<li>-f选项：以完全格式化的形式展示信息(展示全部信息)</li>
<li>一般来说，固定用法是：<code>ps -ef</code> 列出全部进程的全部信息</li>
<li>命令输出的信息格式从左到右分别是：
<ul>
<li>UID：进程所属的用户ID</li>
<li>PID：进程的进程号ID</li>
<li>PPID：进程的父ID(启动此进程的其他进程)</li>
<li>C：此进程的CPU占用率(百分比)</li>
<li>STIME：进程的启动时间</li>
<li>TTY：启动此进程的终端序号，若显示？，表示非终端启动</li>
<li>TIME：进程占用的CPU时间</li>
<li>CMD：进程对应的名称或启动路径或启动命令</li>
</ul>
</li>
</ul>
</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[sakura@localhost ~]$ ps -ef</span></div></div><div><div><div>2</div></div><div><span>UID</span><span>         </span><span>PID</span><span>   </span><span>PPID</span><span>  </span><span>C</span><span> </span><span>STIME</span><span> </span><span>TTY</span><span>          </span><span>TIME</span><span> </span><span>CMD</span></div></div><div><div><div>3</div></div><div><span>root</span><span>          </span><span>1</span><span>      </span><span>0</span><span>  </span><span>0</span><span> </span><span>02:34</span><span> </span><span>?</span><span>        </span><span>00:00:02</span><span> </span><span>/usr/lib/systemd/systemd</span><span> </span><span>--switched-roo</span></div></div><div><div><div>4</div></div><div><span>root</span><span>          </span><span>2</span><span>      </span><span>0</span><span>  </span><span>0</span><span> </span><span>02:34</span><span> </span><span>?</span><span>        </span><span>00:00:00</span><span> [kthreadd]</span></div></div><div><div><div>5</div></div><div><span>root</span><span>          </span><span>4</span><span>      </span><span>2</span><span>  </span><span>0</span><span> </span><span>02:34</span><span> </span><span>?</span><span>        </span><span>00:00:00</span><span> [kworker/0:0H]</span></div></div><div><div><div>6</div></div><div><span>root</span><span>          </span><span>6</span><span>      </span><span>2</span><span>  </span><span>0</span><span> </span><span>02:34</span><span> </span><span>?</span><span>        </span><span>00:00:00</span><span> [ksoftirqd/0]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>配合管道符可以实现查看指定进程
<ul>
<li><code>ps -ef | grep 进程名称/进程号/进程ID</code></li>
</ul>
</li>
</ol></section><section><h3>kill命令关闭进程<a href="#kill命令关闭进程"><span>#</span></a></h3><ol>
<li>语法：<code>kill [-9] 进程ID</code>
<ul>
<li>-9选项：表示强制关闭进程</li>
</ul>
</li>
</ol></section></section>
<section><h2>主机状态监控<a href="#主机状态监控"><span>#</span></a></h2><section><h3>top命令查看系统资源占用<a href="#top命令查看系统资源占用"><span>#</span></a></h3><ol>
<li>语法：<code>top</code></li>
<li>默认每5秒刷新一次，按q或ctrl + c退出</li>
</ol></section><section><h3>磁盘信息监控<a href="#磁盘信息监控"><span>#</span></a></h3><section><h4>df命令<a href="#df命令"><span>#</span></a></h4><ol>
<li>查看硬盘的使用情况</li>
<li>语法：<code>df [-h]</code>
<ul>
<li>-h选项：以更加人性化的单位显示</li>
</ul>
</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[sakura@localhost ~]$ df -h</span></div></div><div><div><div>2</div></div><div><span>Filesystem</span><span>      </span><span>Size</span><span>  </span><span>Used</span><span> </span><span>Avail</span><span> </span><span>Use%</span><span> </span><span>Mounted</span><span> </span><span>on</span></div></div><div><div><div>3</div></div><div><span>devtmpfs</span><span>        </span><span>471M</span><span>     </span><span>0</span><span>  </span><span>471M</span><span>   </span><span>0%</span><span> </span><span>/dev</span></div></div><div><div><div>4</div></div><div><span>tmpfs</span><span>           </span><span>487M</span><span>     </span><span>0</span><span>  </span><span>487M</span><span>   </span><span>0%</span><span> </span><span>/dev/shm</span></div></div><div><div><div>5</div></div><div><span>tmpfs</span><span>           </span><span>487M</span><span>  </span><span>8.5M</span><span>  </span><span>478M</span><span>   </span><span>2%</span><span> </span><span>/run</span></div></div><div><div><div>6</div></div><div><span>tmpfs</span><span>           </span><span>487M</span><span>     </span><span>0</span><span>  </span><span>487M</span><span>   </span><span>0%</span><span> </span><span>/sys/fs/cgroup</span></div></div><div><div><div>7</div></div><div><span>/dev/sda3</span><span>        </span><span>18G</span><span>  </span><span>4.2G</span><span>   </span><span>14G</span><span>  </span><span>24%</span><span> </span><span>/</span></div></div><div><div><div>8</div></div><div><span>/dev/sda1</span><span>       </span><span>297M</span><span>  </span><span>152M</span><span>  </span><span>145M</span><span>  </span><span>52%</span><span> </span><span>/boot</span></div></div><div><div><div>9</div></div><div><span>tmpfs</span><span>            </span><span>98M</span><span>   </span><span>24K</span><span>   </span><span>98M</span><span>   </span><span>1%</span><span> </span><span>/run/user/1000</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>iostat命令<a href="#iostat命令"><span>#</span></a></h4><ol>
<li>作用：查看CPU、磁盘的相关信息</li>
<li>语法：<code>iostat [-x] [num1] [num2]</code>
<ul>
<li>-x选项：显示更多信息</li>
<li>num1参数：数字，刷新间隔</li>
<li>num2参数：数字，刷新次数</li>
</ul>
</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[sakura@localhost ~]$ iostat -x</span></div></div><div><div><div>2</div></div><div><span>Linux</span><span> </span><span>3.10.0-1160.el7.x86_64</span><span> (localhost.localdomain)   04/20/2026   _x86_64_  (</span><span>1</span><span> </span><span>CPU</span><span>)</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>avg-cpu:</span><span>  </span><span>%user</span><span>   </span><span>%nice</span><span> </span><span>%system</span><span> </span><span>%iowait</span><span>  </span><span>%steal</span><span>   </span><span>%idle</span></div></div><div><div><div>5</div></div><div><span>           </span><span>0.65</span><span>    </span><span>0.00</span><span>    </span><span>0.32</span><span>    </span><span>0.02</span><span>    </span><span>0.00</span><span>   </span><span>99.01</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>Device:</span><span>         </span><span>rrqm/s</span><span>   </span><span>wrqm/s</span><span>     </span><span>r/s</span><span>     </span><span>w/s</span><span>    </span><span>rkB/s</span><span>    </span><span>wkB/s</span><span> </span><span>avgrq-sz</span><span> </span><span>avgqu-sz</span><span>   </span><span>await</span><span> </span><span>r_await</span><span> </span><span>w_await</span><span>  </span><span>svctm</span><span>  </span><span>%util</span></div></div><div><div><div>8</div></div><div><span>sda</span><span>               </span><span>0.04</span><span>     </span><span>0.95</span><span>    </span><span>2.33</span><span>    </span><span>0.32</span><span>   </span><span>125.87</span><span>     </span><span>6.79</span><span>   </span><span>100.22</span><span>     </span><span>0.00</span><span>    </span><span>0.78</span><span>    </span><span>0.79</span><span>    </span><span>0.66</span><span>   </span><span>0.28</span><span>   </span><span>0.07</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>//rKB/s：每秒发送到设备的读取请求数</span></div></div><div><div><div>11</div></div><div><span>//wKB/s：每秒发送到设备的写入请求数</span></div></div><div><div><div>12</div></div><div><span>//%util：磁盘利用率</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h3>网络状态监控<a href="#网络状态监控"><span>#</span></a></h3><section><h4>sar命令<a href="#sar命令"><span>#</span></a></h4><ol>
<li>作用：查看网络的相关统计(sar命令非常复杂，这里仅简单用于统计网络)</li>
<li>语法：<code>sar -n DEV num1 num2</code>
<ul>
<li>-n选项：查看网络</li>
<li>DEV选项：查看网络接口</li>
<li>num1参数：数字，刷新间隔(不填就查看一次结束)</li>
<li>num2参数：数字，刷新次数(不填无限次数)</li>
</ul>
</li>
</ol></section></section></section>
<section><h2>环境变量<a href="#环境变量"><span>#</span></a></h2><ul>
<li>环境变量是操作系统在运行的时候，记录的一些关键信息，用以辅助系统运行</li>
<li>环境变量是一种KeyValue型结构，即名称和值</li>
</ul><section><h3>env命令<a href="#env命令"><span>#</span></a></h3><ol>
<li>作用：查看当前系统中的环境变量</li>
<li>语法：<code>env</code></li>
</ol></section><section><h3>环境变量：PATH<a href="#环境变量path"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[sakura@localhost ~]$ env | </span><span>grep</span><span> </span><span>PATH</span></div></div><div><div><div>2</div></div><div><span>PATH</span><span>=</span><span>/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/sakura/.local/bin:/home/sakura/bin</span></div></div><div><div><div>3</div></div><div><span>WINDOWPATH</span><span>=</span><span>1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>PATH记录了系统执行任何命令的搜索路径，路径之间以：隔开</li>
<li>当执行任何命令，都会按照顺序，从上述路径中搜索要执行的程序的本体。比如执行cd命令，就从第三个目录/usr/bin中搜索到了cd命令，并执行</li>
</ul></section><section><h3>$符号<a href="#符号"><span>#</span></a></h3><ol>
<li>作用：取“变量”的值</li>
<li>用法：<code>echo $PATH</code></li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>[sakura@localhost ~]$ echo </span><span>$PATH</span></div></div><div><div><div>2</div></div><div><span>/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/sakura/.local/bin:/home/sakura/bin</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>//当和其他内容混合在一起的时候，可以通过</span><span>{}</span><span>来标注的变量是谁</span></div></div><div><div><div>5</div></div><div><span>[sakura@localhost ~]$ echo </span><span><span>${</span><span>PATH</span><span>}</span></span><span>ABC</span></div></div><div><div><div>6</div></div><div><span>/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/sakura/.local/bin:/home/sakura/binABC</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>自行设置环境变量<a href="#自行设置环境变量"><span>#</span></a></h3><ol>
<li>临时设置：<code>export 变量名=变量值</code></li>
<li>永久生效
<ul>
<li>针对当前用户生效，配置在当前的用户的 ~/bashrc 文件中</li>
<li>针对所有用户生效，配置在系统的 /etc/profile 文件中</li>
<li>通过语法: <code>source 配置文件</code>，进行立刻生效</li>
</ul>
</li>
</ol></section></section>
<section><h2>Linux文件的上传和下载<a href="#linux文件的上传和下载"><span>#</span></a></h2><section><h3>利用FinalShell工具<a href="#利用finalshell工具"><span>#</span></a></h3><ul>
<li>略</li>
</ul></section><section><h3>rz，rs命令<a href="#rzrs命令"><span>#</span></a></h3><ol>
<li>安装：<code>yum -y install lrzsz</code></li>
<li>rz命令
<ul>
<li>作用：实现文件上传</li>
<li>语法：<code>rz</code></li>
</ul>
</li>
<li>sz命令
<ul>
<li>作用：实现文件下载</li>
<li>语法：<code>sz 要下载的文件</code></li>
<li>文件会自动下载到桌面的 fsdownload 文件夹中</li>
</ul>
</li>
</ol></section></section>
<section><h2>Linux文件的压缩和解压<a href="#linux文件的压缩和解压"><span>#</span></a></h2><section><h3>Linux常见的压缩格式<a href="#linux常见的压缩格式"><span>#</span></a></h3><ul>
<li>tar格式：归档文件，简单的将文件整合到一个文件内，无压缩效果</li>
<li>gzip格式：gzip文件，不仅能整合到一个文件内，同时还有压缩效果</li>
</ul></section><section><h3>tar命令<a href="#tar命令"><span>#</span></a></h3><ul>
<li>语法：<code>tar [-c -v -x -f -z -C] 参数1 参数2 ... 参数N</code>
<ul>
<li>-c：创建压缩文件，用于压缩模式</li>
<li>-v：显示压缩、解压过程，用于查看进度</li>
<li>-x：解压模式</li>
<li>-f：要创建的文件，或要解压的文件</li>
<li>-z：gzip模式，不使用-z就是普通tarball格式</li>
<li>-C：选择解压的目的地，用于解压模式</li>
</ul>
</li>
<li>注意
<ul>
<li><strong>-z选项：如果使用的话，一般处于选项位的第一个</strong></li>
<li><strong>-f选项：必须在选项位的最后一个</strong></li>
<li><strong>-C选项：单独使用，和解压所需的其他参数分开</strong></li>
</ul>
</li>
</ul><section><h4>tar压缩<a href="#tar压缩"><span>#</span></a></h4><ul>
<li>常用的压缩组合命令
<ul>
<li><code>tar -cvf test.tar 1.txt 2.txt 3.txt</code>
<ul>
<li>将1.txt，2.txt，3.txt压缩到test.tar文件内</li>
</ul>
</li>
<li><code>tar -zcvf test.tar.gz 1.txt 2.txt 3.txt</code>
<ul>
<li>将1.txt，2.txt，3.txt压缩到test.tar文件内，使用gzip模式</li>
</ul>
</li>
</ul>
</li>
</ul></section><section><h4>tar解压<a href="#tar解压"><span>#</span></a></h4><ul>
<li>常用的解压组合命令
<ul>
<li><code>tar -xvf test.tar</code>
<ul>
<li>解压test.tar，将文件解压至当前目录</li>
</ul>
</li>
<li><code>tar -xvf test.tar -C /home/sakura</code>
<ul>
<li>解压test.tar，将文件解压至指定目录(/home/sakura)</li>
</ul>
</li>
<li><code>tar -zxvf test.tar.gz -C /home/sakura</code>
<ul>
<li>以gzip模式解压test.tar.gz，将文件解压至指定目录(/home/sakura)</li>
</ul>
</li>
</ul>
</li>
</ul></section></section><section><h3>zip、unzip命令<a href="#zipunzip命令"><span>#</span></a></h3><section><h4>zip命令压缩文件<a href="#zip命令压缩文件"><span>#</span></a></h4><ol>
<li>语法：<code>zip [-r] 参数1 参数2 ... 参数N</code>
<ul>
<li>-r：被压缩的包含文件夹时，需要使用-r</li>
</ul>
</li>
<li>示例
<ul>
<li><code>zip test.zip a.txt b.txt c.txt</code>
<ul>
<li>将a.txt，b.txt，c.txt压缩到test.zip文件内</li>
</ul>
</li>
<li><code>zip -r test.zip test a.txt</code>
<ul>
<li>将test文件夹和a.txt文件，压缩到test.zip文件内</li>
</ul>
</li>
</ul>
</li>
</ol></section><section><h4>unzip命令解压文件<a href="#unzip命令解压文件"><span>#</span></a></h4><ol>
<li>语法：<code>unzip [-d] 参数</code>
<ul>
<li>-d：指定要解压到的位置</li>
<li>参数：被解压的zip压缩包文件</li>
</ul>
</li>
<li>示例
<ul>
<li><code>unzip test.zip</code> 将test.zip解压到当前目录</li>
<li><code>unzip test.zip -d /home/sakua</code> 将test.zip解压到指定文件夹内(/home/sakura)</li>
</ul>
</li>
</ol></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/linux/linux%E7%94%A8%E6%88%B7%E5%92%8C%E6%9D%83%E9%99%90/</id>
      <title type="text">Linux用户和权限</title>
      <published>2026-04-20T00:00:00.000Z</published>
      <updated>2026-04-20T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/linux/linux%E7%94%A8%E6%88%B7%E5%92%8C%E6%9D%83%E9%99%90/"/>
      <summary type="text">了解 Linux用户和权限</summary>
      <content type="html"><![CDATA[<section><h2>Linux的root用户<a href="#linux的root用户"><span>#</span></a></h2><ol>
<li>Linux系统的超级管理员用户是：root用户</li>
<li>普通用户的权限，一般在其HOME目录内是不受限的，一旦出了HOME目录，大多数地方，普通用户仅有只读和执行权限，无修改权限</li>
<li>su命令：可以切换用户
<code>su [-] [用户名]</code>
<ul>
<li>- ：表示切换后加载环境变量，建议带上</li>
<li>用户可以省略，省略默认切换到root</li>
</ul>
</li>
<li>exit命令
<ul>
<li>切换用户后，可以通过exit命令返回上一个用户，也可以使用快捷键：ctrl + d</li>
</ul>
</li>
<li>sudo命令：可以让一条普通命令带有root权限
<code>sudo 其他命令</code>
<ul>
<li>需要以root用户执行visudo命令，增加配置方可让普通用户有sudo命令的执行权限</li>
</ul>
</li>
</ol></section>
<section><h2>用户、用户组<a href="#用户用户组"><span>#</span></a></h2><ol>
<li>Linux系统中
<ul>
<li>可以配置多个用户/用户组</li>
<li>用户可以加入多个多个用户组中</li>
</ul>
</li>
<li>Linux中关于权限的管控级别有2个
<ul>
<li>针对用户的权限控制</li>
<li>针对用户组的权限控制
针对某文件，可以控制用户的权限，也可以控制用户组的权限</li>
</ul>
</li>
</ol><section><h3>用户组管理<a href="#用户组管理"><span>#</span></a></h3><p><strong>以下命令需root用户执行</strong></p><ol>
<li>创建用户组
<code>groupadd 用户组</code></li>
<li>删除用户组
<code>groupdel 用户组</code></li>
</ol></section><section><h3>用户管理<a href="#用户管理"><span>#</span></a></h3><p><strong>以下命令需root用户执行</strong></p><ol>
<li>创建用户
<code>useradd [-g -d] 用户</code>
<ul>
<li>-g选项：指定用户的组，不指定则会创建同名组并自动加入，指定-g需要组已经存在，如已存在同名组，必须使用-g</li>
<li>-d选项：指定用户的HOME路径，不指定，HOME目录默认在：/home/用户名</li>
</ul>
</li>
<li>删除用户
<code>userdel [-r] 用户</code>
<ul>
<li>-r：删除用户的HOME目录，不使用-r，删除用户时候，HOME目录保留</li>
</ul>
</li>
<li>查看用户所属组
<code>id [用户名]</code>
<ul>
<li>参数：用户名，指被查看的用户，如果不提供则查看自身</li>
</ul>
</li>
<li>修改用户所属组
<code>usermod -aG 用户组 用户名</code>
<ul>
<li>将指定用户加入指定组</li>
</ul>
</li>
<li>getent命令
<ul>
<li><code>getent passwd</code>
<ul>
<li>查看当前系统中有哪些用户</li>
<li>输出信息的格式：用户名：密码(x)：用户ID：组ID：描述信息(无用)：HOME目录：执行终端(默认bash)</li>
</ul>
</li>
<li><code>getent group</code>
<ul>
<li>查看当前系统中有哪些用户组</li>
<li>输出信息的格式：组名称：组认证(显示为x)：组ID</li>
</ul>
</li>
</ul>
</li>
</ol></section></section>
<section><h2>查看权限控制信息<a href="#查看权限控制信息"><span>#</span></a></h2><ol>
<li>
<p>认识权限信息
通过<code>ls -l</code>命令可以以列表形式查看内容，并显示权限细节
<img alt="" loading="lazy" width="878" height="389" src="/_astro/5.Dp_HXIGb_ZCEOxe.webp" /></p>
<ul>
<li>序号1：表示文件、文件夹的权限控制信息</li>
<li>序号2：表示文件、文件夹所属用户</li>
<li>序号3：表示文件、文件夹所属用户组
<img alt="" loading="lazy" width="1208" height="384" src="/_astro/6.DnuH_4w9_ZbNxNH.webp" />
举例：dwxr-xr-x表示</li>
<li>这是一个文件夹，首字母d表示</li>
<li>所属用户的权限是：有r有w有x，rwx</li>
<li>所属用户组的权限是：有r无w有x，r-x</li>
<li>其他用户的权限是：有r无w有x，r-x</li>
</ul>
<p><strong>rwx代表什么？</strong></p>
<ul>
<li>r：读权限
<ul>
<li>针对文件：可以查看文件内容</li>
<li>针对文件夹：可以查看文件夹内容，如<code>ls</code>命令</li>
</ul>
</li>
<li>w：写权限
<ul>
<li>针对文件：可以修改该文件</li>
<li>针对文件夹：可以在文件夹内创建、删除、改名等操作</li>
</ul>
</li>
<li>x：执行权限
<ul>
<li>针对文件：可以将该文件作为程序执行</li>
<li>针对文件夹：可以更改工作目录到该文件夹，即cd进入</li>
</ul>
</li>
</ul>
</li>
</ol></section>
<section><h2>chmod命令<a href="#chmod命令"><span>#</span></a></h2><ol>
<li>作用：修改文件/文件夹的权限信息(<strong>只有文件/文件夹的所属用户/root用户可以修改</strong>)</li>
<li>语法
<code>chmod [-R] 权限 文件或文件夹</code>
<ul>
<li>-R选项：对文件夹内的全部内容应用同样的操作</li>
<li>示例：<code>chmod u=rwx,g=rx,o=x, hello.txt</code>,将权限修改为ewxr-x—x
<ul>
<li>其中：u表示user所属用户权限，g表示group组权限，o表示other其他用户权限</li>
</ul>
</li>
</ul>
</li>
<li>权限的数字序号
<ul>
<li>权限可以用3位数字来代表，第一位数字表示用户权限，第二位表示用户组权限，第三位表示其他用户权限</li>
<li>数字的细节如下：r记为4，w记为2，x记为1
<ul>
<li>0：无任何权限，即---</li>
<li>1：仅有x权限，即—x</li>
<li>2：仅有w权限，即-w-</li>
<li>3~7：类比…</li>
</ul>
</li>
<li>示例：<code>chmod 751 hello.txt</code>
<ul>
<li>751代表：rwx(7)r-x(5)—x(1)</li>
</ul>
</li>
</ul>
</li>
</ol></section>
<section><h2>chown命令<a href="#chown命令"><span>#</span></a></h2><ol>
<li>作用：修改文件/文件夹的所属用户和用户组(此命令只适用于root用户执行)</li>
<li>语法
<code>chown [-R] [用户][:][用户组] 文件或文件夹</code>
<ul>
<li>-R选项：同chmod,对文件夹内全部内容应用相同规则</li>
<li>用户选项：修改所属用户</li>
<li>用户组选项：修改所属用户组</li>
<li>：选项：用于分隔用户和用户组</li>
</ul>
</li>
<li>示例
<ul>
<li><code>chown root hello.txt</code> 将hello.txt所属用户修改为root</li>
<li><code>chown :root hello.txt</code> 将hello.txt所属用户组修改为root</li>
<li><code>chown root:sakura hello.txt</code> 将hello.txt所属用户修改为root，用户组修改为sakura</li>
<li><code>chown -R root test</code> 将文件夹test的所属用户修改为root并对文件夹内全部内容应用同样规则</li>
</ul>
</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/linux/linux%E5%9F%BA%E7%A1%80%E5%91%BD%E4%BB%A4/</id>
      <title type="text">Linux基础命令</title>
      <published>2026-04-19T00:00:00.000Z</published>
      <updated>2026-04-19T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/linux/linux%E5%9F%BA%E7%A1%80%E5%91%BD%E4%BB%A4/"/>
      <summary type="text">了解 Linux基础命令</summary>
      <content type="html"><![CDATA[<section><h2>Linux系统的目录结构<a href="#linux系统的目录结构"><span>#</span></a></h2><ol>
<li>Linux的目录结构——树形结构
<ul>
<li>没有盘符的概念，只有一个根目录/，所有文件都在它下面</li>
</ul>
</li>
<li>Linux路径的描述方式
<ul>
<li>Linux系统中，路径路径之间的层级关系用 /</li>
<li>Windows系统中，路径之间的层级关系用 \</li>
<li>例：在根目录下有一个文件夹test，文件夹内有一个文件hello.txt，请描述文件的路径——   /test/hello.txt</li>
</ul>
</li>
</ol></section>
<section><h2>Linux命令入门<a href="#linux命令入门"><span>#</span></a></h2><section><h3>Linux命令基础<a href="#linux命令基础"><span>#</span></a></h3><ol>
<li>命令，命令行
<ul>
<li>命令：Linux操作指令，是系统内置的程序，可以以字符化的形式去使用</li>
<li>命令行：Linux终端，可以提供字符化的操作页面供命令执行</li>
</ul>
</li>
<li>Linux命令的通用格式
<code>command [-options] [parameter]</code>
<ul>
<li>command：命令本身</li>
<li>options：可选选项，控制命令的行为细节</li>
<li>parameter：可选参数，控制命令的指向目标</li>
</ul>
</li>
</ol></section><section><h3>ls命令入门<a href="#ls命令入门"><span>#</span></a></h3><ol>
<li>ls命令的作用：在命令行中，展示当前工作目录（默认HOME目录）下的内容（文件或文件夹）</li>
<li>HOME目录：每一个用户在Linux系统的专属目录</li>
<li>当前工作目录：Linux命令行在执行命令时，需要一个工作目录，打开命令行程序（终端）默认设置工作目录在用户的HOME目录</li>
</ol></section><section><h3>ls命令的参数和选项<a href="#ls命令的参数和选项"><span>#</span></a></h3><p><code>ls [-a -l -h] [Linux路径]</code></p><ul>
<li>ls不使用参数：表示列出当前工作目录的内容，即用户的HOME目录</li>
<li>ls的选项
<ul>
<li>-a：可以展示出隐藏的内容</li>
<li>-l：以列表的形式展示内容，并展示更多细节</li>
<li>-h：必须和-l一起使用，能够显示文件的大小单位</li>
<li>-a，-l，-h 可以组合使用</li>
</ul>
</li>
<li>ls的参数
<ul>
<li>可以指定要查看的文件夹（目录）的内容，如果不给定参数，就查看当前工作目录的内容</li>
</ul>
</li>
</ul></section></section>
<section><h2>目录切换相关命令（cd/pwd）<a href="#目录切换相关命令cdpwd"><span>#</span></a></h2><ol>
<li>cd命令：切换当前工作目录
<code>cd [Linux路径]</code>
<ul>
<li>没有选项只有参数，表示目标路径</li>
<li>使用参数，切换到指定路径</li>
<li>不使用参数，切换工作目录到当前用户的HOME</li>
</ul>
</li>
<li>pwd命令：输出当前所在的工作目录
<code>pwd</code>
<ul>
<li>没有选项和参数，直接使用</li>
</ul>
</li>
</ol></section>
<section><h2>相对路径，绝对路径和特殊路径符<a href="#相对路径绝对路径和特殊路径符"><span>#</span></a></h2><ol>
<li>绝对路径：以根目录做起点，描述路径的方式，路径以 / 开头</li>
<li>相对路径：以当前目录做起点，描述路径的方式，路径不需以 / 开头</li>
<li>特殊路径符
<ul>
<li>. 表示当前目录。如cd . 或 cd ./Desktop</li>
<li>.. 表示上一级目录。如cd .. 或 cd ../..</li>
<li>~ 表示用户的HOME目录。如cd ~ 或 cd ~/Desktop</li>
</ul>
</li>
</ol></section>
<section><h2>创建目录命令（mkdir）<a href="#创建目录命令mkdir"><span>#</span></a></h2><ol>
<li>mkdir命令：创建新的目录和文件夹
<code>mkdir [-p] Linux路径</code>
<ul>
<li>参数必填，表示要创建的目录的路径，相对，绝对，特殊路径符都可以使用</li>
<li>-p：可选，表示自动创建不存在的父目录，适用于创建连续多层级的目录</li>
</ul>
</li>
</ol></section>
<section><h2>文件操作命令（touch/cat/more/cp/mv/rm）<a href="#文件操作命令touchcatmorecpmvrm"><span>#</span></a></h2><ol>
<li>touch命令：用于创建一个新的文件
<code>touch Linux路径</code>
<ul>
<li>参数必填，表示要创建的目录的路径，相对，绝对，特殊路径符都可以使用</li>
</ul>
</li>
<li>cat命令：用于查看文件内容
<code>cat Linux路径</code>
<ul>
<li>参数必填，表示要创建的目录的路径，相对，绝对，特殊路径符都可以使用</li>
</ul>
</li>
<li>more命令：用于查看文件内容，可翻页查看
<code>more Linux路径</code>
<ul>
<li>参数必填，表示要创建的目录的路径，相对，绝对，特殊路径符都可以使用</li>
<li>使用空格进行翻页，使用q退出查看</li>
</ul>
</li>
<li>cp(copy)命令：用于复制文件或文件夹
<code>cp [-r] 参数1 参数2</code>
<ul>
<li>-r：可选，用于复制文件夹，表示递归</li>
<li>参数1：Linux路径，表示被复制的文件或文件夹</li>
<li>参数2：Linux路径，表示要复制去的地方</li>
</ul>
</li>
<li>mv(move)命令：用于移动文件或文件夹
<code>mv 参数1 参数2</code>
<ul>
<li>参数1：Linux路径，表示被移动的文件或文件夹</li>
<li>参数2：Linux路径，表示要移动去的地方，如果目标不存在，则进行改名</li>
</ul>
</li>
<li>rm(remove命令)：用于删除文件或文件夹
<code>rm [-r -f] 参数1 参数2 ... 参数N</code>
<ul>
<li>-r：可选项，用于文件夹删除</li>
<li>-f：可选项，用于强制删除（不提示，一般用于root用户）</li>
<li>参数：表示被删除的文件或文件夹路径，支持多个，空格隔开</li>
<li>参数也支持通配符 * ，用以做模糊匹配</li>
</ul>
</li>
</ol></section>
<section><h2>which/find 命令<a href="#whichfind-命令"><span>#</span></a></h2><ol>
<li>which命令：查找命令的程序文件
<code>which 要查找的命令</code>
<ul>
<li>无需选项，只需要参数表示查找哪个命令</li>
</ul>
</li>
<li>find命令：用于查找指定的文件
<ul>
<li>按文件名查找：<code>find 起始路径 -name "被查找文件名"</code></li>
<li>支持通配符</li>
<li>按文件大小查找： <code>find 起始路径 -size +(-) n[k M G]</code></li>
</ul>
</li>
</ol></section>
<section><h2>grep/wc/管道符<a href="#grepwc管道符"><span>#</span></a></h2><ol>
<li>grep命令：从文件中通过关键字过滤文件行
<code>grep [-n] 关键字 文件路径</code>
<ul>
<li>-n：可选，表示在结果中显示匹配的行的行号</li>
<li>关键字：必填，建议使用 “ ” 将关键字包围</li>
<li>文件路径：必填，表示要过滤内容的文件路径，可作为管道符的输入</li>
</ul>
</li>
<li>wc命令：统计文件的行数，单词数量，字节数，字符数等
<code>wc [-c -m -l -w] 文件路径</code>
<ul>
<li>不带选项的默认统计：行数，单词数，字节数</li>
<li>-c字节数，-m字符数，-l行数，-w单词数</li>
<li>文件路径：被统计的文件路径，可作为管道符的输入</li>
</ul>
</li>
<li>管道符 | ：将管道符左边命令的结果作为右边命令的输入</li>
</ol></section>
<section><h2>echo/tail/重定向符<a href="#echotail重定向符"><span>#</span></a></h2><ol>
<li>echo命令：在命令行内输出指定内容
<code>echo 输出的内容</code>
<ul>
<li>无需选项，只有一个参数，表示要输出的内容，复杂内容可以用 “ “ 包围</li>
<li>反引号符 ` `：被反引号包围的内容，会被作为命令执行，而非普通字符。</li>
</ul>
</li>
<li>tail命令：查看文件尾部的内容，并可以持续跟踪
<code>tail [-f -num] Linux路径</code>
<ul>
<li>-f：持续跟踪</li>
<li>-num：启动的时候查看尾部多少行，默认10（-2，-3，…-x）</li>
<li>Linux路径：表示被查看的文件</li>
</ul>
</li>
<li>重定向符
<ul>
<li>&gt; ：将左侧命令的结果，覆盖写入到符号右侧指定的文件中</li>
<li>&gt;&gt; ：将左侧命令的结果，追加写入到符号右侧指定的文件中</li>
</ul>
</li>
</ol></section>
<section><h2>vi/vim编辑器<a href="#vivim编辑器"><span>#</span></a></h2><ol>
<li>vi/vim编辑器：命令行模式下的文本编辑器，用来编辑文件。vim是vi的升级版，一般用vim即可，包含vi全部功能</li>
<li>基础命令
<code>vi 文件路径</code>
<code>vim 文件路径</code></li>
<li>三种运行模式
<ul>
<li>命令模式：默认模式，可以通过键盘快捷键控制文件内容</li>
<li>输入模式：通过命令模式进入，可以对输入内容进行编辑，按esc退回命令模式</li>
<li>底线命令模式：通过命令模式进入，可以对文件进行保存，关闭等操作</li>
</ul>
</li>
<li><img alt="" loading="lazy" width="735" height="506" src="/_astro/2.D7Pm7KRp_17aAPO.webp" />
<img alt="" loading="lazy" width="1084" height="544" src="/_astro/3.j-BA1kKX_ZSBPqF.webp" />
<img alt="" loading="lazy" width="1521" height="575" src="/_astro/4.BpCTMYik_Z1SWeaf.webp" /></li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/windows/windows%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8E%E6%89%B9%E5%A4%84%E7%90%86/</id>
      <title type="text">Windows 命令行与批处理</title>
      <published>2026-04-19T00:00:00.000Z</published>
      <updated>2026-04-19T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/windows/windows%E5%91%BD%E4%BB%A4%E8%A1%8C%E4%B8%8E%E6%89%B9%E5%A4%84%E7%90%86/"/>
      <summary type="text">了解 Windows 命令行与批处理</summary>
      <content type="html"><![CDATA[<section><h1>命令行<a href="#命令行"><span>#</span></a></h1><section><h2>cmd与PowerShell<a href="#cmd与powershell"><span>#</span></a></h2><p>cmd是power shell的子集。</p></section><section><h2>说明<a href="#说明"><span>#</span></a></h2><ul>
<li>不区分大小写</li>
<li>按上下键可以查看历史命令</li>
<li>Tab键可以自动补全</li>
<li>使用<code>/?</code>或<code>/help</code>可以查看命令用法，用法中的<code>[]</code>代表可选</li>
<li>命令基本格式<code>命令 参数列表 </code></li>
</ul></section><section><h2>基本命令<a href="#基本命令"><span>#</span></a></h2>

<table><thead><tr><th>命令</th><th>功能</th><th>举例</th></tr></thead><tbody><tr><td><code>echo</code></td><td>显示</td><td></td></tr><tr><td><code>pause</code></td><td>暂停</td><td><code>pause &gt; nul</code></td></tr><tr><td><code>cls</code></td><td>清屏</td><td></td></tr><tr><td><code>exit</code></td><td>退出</td><td></td></tr></tbody></table></section><section><h2>常用命令<a href="#常用命令"><span>#</span></a></h2>

<table><thead><tr><th>命令</th><th>功能</th><th>举例</th></tr></thead><tbody><tr><td><code>type</code></td><td>显示文件内容</td><td><code>type 1.txt</code></td></tr><tr><td><code>find</code></td><td>查找字符串</td><td><code>find "abc" 2.txt</code></td></tr><tr><td><code>fc</code></td><td>比较文件内容</td><td><code>fc 1.txt 2.txt</code></td></tr><tr><td><code>date</code></td><td>显示/修改日期</td><td><code>date /T</code></td></tr><tr><td><code>time</code></td><td>显示/修改时间</td><td><code>time /T</code></td></tr><tr><td><code>ping</code></td><td>网络连接测试</td><td><code>ping www.baidu.com</code></td></tr><tr><td><code>ipconfig</code></td><td>TCP/IP信息</td><td></td></tr><tr><td><code>set /a</code></td><td>计算表达式</td><td><code>set /a 6%4</code></td></tr><tr><td><code>shutdown</code></td><td>关机</td><td><code>shutdown /s /t 60</code></td></tr></tbody></table><section><h3>多命令<a href="#多命令"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>&amp;  </span><span>顺序执行多条命令，不管命令是否执行成功</span></div></div><div><div><div>2</div></div><div><span>&amp;&amp; </span><span>顺序执行多条命令，前面的命令错误则不执行后面的命令</span></div></div><div><div><div>3</div></div><div><span>|| </span><span>顺序执行多条命令，碰到执行正确的命令后将不执行后面的命令</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>abc</span><span> &amp; </span><span>dir</span><span> &amp; </span><span>bcd</span></div></div><div><div><div>6</div></div><div><span>abc</span><span> &amp;&amp; </span><span>dir</span><span> &amp;&amp; </span><span>bcd</span></div></div><div><div><div>7</div></div><div><span>abc</span><span> || </span><span>dir</span><span> || </span><span>bcd</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>重定向<a href="#重定向"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>&gt;  输出重定向</span></div></div><div><div><div>2</div></div><div><span>&gt;&gt; 追加重定向</span></div></div><div><div><div>3</div></div><div><span>&lt;  输入重定向</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>date</span><span> </span><span>/t</span><span> &gt; </span><span>1.txt</span></div></div><div><div><div>6</div></div><div><span>time</span><span> /t &gt;&gt; 1.txt</span></div></div><div><div><div>7</div></div><div><span>find</span><span> </span><span>"/"</span><span> &lt; </span><span>1.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>管道<a href="#管道"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>| </span><span>前一个命令的输出作为后一个命令的输入</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>dir</span><span> | </span><span>find</span><span> </span><span>"txt"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section><section><h2>文件操作命令<a href="#文件操作命令"><span>#</span></a></h2><p><strong>路径分隔符</strong>：<code>\</code></p><p><strong>当前目录</strong>：<code>.</code></p><p><strong>上一级目录</strong>：<code>..</code></p>

<table><thead><tr><th>命令</th><th>功能</th><th>举例</th></tr></thead><tbody><tr><td><code>dir</code></td><td>显示文件夹内容</td><td><code>dir /A</code></td></tr><tr><td><code>tree</code></td><td>显示文件夹树形结构</td><td><code>tree /F</code></td></tr><tr><td><code>盘符:</code></td><td>进入指定磁盘</td><td><code>c:</code> <code>d:</code></td></tr><tr><td><code>cd</code></td><td>切换目录</td><td><code>cd test</code> <code>cd ..</code></td></tr><tr><td><code>mkdir</code>或<code>md</code></td><td>创建目录</td><td><code>mkdir test</code></td></tr><tr><td><code>rmdir</code>或<code>rd</code></td><td>删除目录</td><td><code>rmdir test</code></td></tr><tr><td><code>del</code></td><td>删除文件</td><td><code>del 1.txt</code> <code>del /q/a/f *.txt</code></td></tr><tr><td><code>rename</code>或<code>ren</code></td><td>重命名</td><td><code>rename a.txt b.txt</code></td></tr><tr><td><code>copy</code></td><td>复制文件</td><td><code>copy a\1.txt b\1.txt</code></td></tr><tr><td><code>move</code></td><td>移动文件/重命名</td><td><code>move a\1.txt 2.txt</code></td></tr></tbody></table></section></section>
<section><h1>批处理<a href="#批处理"><span>#</span></a></h1><section><h2>基本结构<a href="#基本结构"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>@echo</span><span> </span><span>off</span><span>    </span><span># 关闭回显</span></div></div><div><div><div>2</div></div><div><span>......</span></div></div><div><div><div>3</div></div><div><span>pause</span><span>      </span><span># 暂停。或 pause &gt; nul</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>运行方式<a href="#运行方式"><span>#</span></a></h2><ul>
<li>双击运行</li>
<li>在cmd中运行（可以传参）</li>
</ul></section><section><h2>变量<a href="#变量"><span>#</span></a></h2><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 设置变量</span></div></div><div><div><div>2</div></div><div><span>set</span><span> </span><span>变量名=变量值</span></div></div><div><div><div>3</div></div><div><span>set</span><span> </span><span>/a</span><span> </span><span>变量名=表达式</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 取消变量</span></div></div><div><div><div>6</div></div><div><span>set</span><span> </span><span>变量名=</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span># 打印变量</span></div></div><div><div><div>9</div></div><div><span>set</span><span> </span><span>变量名</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># 列出变量</span></div></div><div><div><div>12</div></div><div><span>set</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span># 引用变量</span></div></div><div><div><div>15</div></div><div><span>%变量名%</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span># 输入变量值</span></div></div><div><div><div>18</div></div><div><span>set</span><span> </span><span>/p</span><span> </span><span>变量名=提示文字</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>预定义的变量<a href="#预定义的变量"><span>#</span></a></h3>

<table><thead><tr><th>变量名</th><th>值</th></tr></thead><tbody><tr><td><code>%cd%</code></td><td>当前目录</td></tr><tr><td><code>%date%</code></td><td>当前日期</td></tr><tr><td><code>%time%</code></td><td>当前时间</td></tr><tr><td><code>%random%</code></td><td>0~32767之间的随机数</td></tr><tr><td><code>%path%</code></td><td>环境变量</td></tr><tr><td><code>%errorlevel%</code></td><td>上一个命令的返回值，0表示执行成功</td></tr><tr><td><code>%0</code>、<code>%1</code>、<code>%2</code>……</td><td>参数。<code>%0</code>表示命令名</td></tr></tbody></table></section></section><section><h2>调用<a href="#调用"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>call</span><span> </span><span>可执行文件</span><span> [参数列表]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>条件<a href="#条件"><span>#</span></a></h2><div><div><div><figure><figcaption><span></span><span>Terminal window</span><span>展开</span><span>收起</span></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> [not] 条件 (</span></div></div><div><div><div>2</div></div><div><span>  </span><span>语句块</span></div></div><div><div><div>3</div></div><div><span>) else (</span></div></div><div><div><div>4</div></div><div><span>  </span><span>语句块</span></div></div><div><div><div>5</div></div><div><span>)</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 条件：</span></div></div><div><div><div>8</div></div><div><span>errorlevel</span><span> </span><span>number</span><span>  </span><span># 上一个程序的返回值大于等于number则为true</span></div></div><div><div><div>9</div></div><div><span>string1</span><span>=</span><span>=string2</span><span>  </span><span># 两个字符串匹配则为true</span></div></div><div><div><div>10</div></div><div><span>exist</span><span> </span><span>filename</span><span>    </span><span># 文件名存在则为true</span></div></div><div><div><div>11</div></div><div><span>[/I] string1 compare-op string2    </span><span># 比较数值或字符串</span></div></div><div><div><div>12</div></div><div><span>  </span><span># /I  忽略大小写</span></div></div><div><div><div>13</div></div><div><span>  </span><span># compare-op包括：</span></div></div><div><div><div>14</div></div><div><span>  </span><span>#  equ  等于</span></div></div><div><div><div>15</div></div><div><span>  </span><span>#   neq  不等于</span></div></div><div><div><div>16</div></div><div><span>  </span><span>#  lss  小于</span></div></div><div><div><div>17</div></div><div><span>  </span><span>#  leq  小于等于</span></div></div><div><div><div>18</div></div><div><span>  </span><span>#  gtr  大于</span></div></div><div><div><div>19</div></div><div><span>  </span><span>#  geq  大于等于</span></div></div><div><div><div>20</div></div><div><span>defined</span><span> </span><span>variable</span><span>  </span><span># 如果定义了该变量则为true</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h2>跳转<a href="#跳转"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>goto</span><span> </span><span>label</span><span>    </span><span># 跳转到标签所在行</span></div></div><div><div><div>2</div></div><div><span>:label</span><span>      </span><span># 标签以冒号开头</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>循环<a href="#循环"><span>#</span></a></h2><p><strong>基本语法</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> %%i in (</span><span>set</span><span>) </span><span>do</span><span> (</span></div></div><div><div><div>2</div></div><div><span>  </span><span>语句块</span></div></div><div><div><div>3</div></div><div><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 循环变量用 %% 指定，只能是单字母</span></div></div><div><div><div>6</div></div><div><span># set表示文件集合（也可以是空文件），元素之间以逗号或空格分隔，支持通配符</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>for</span><span> %%i in (</span><span>a,b,c</span><span>) </span><span>do</span><span> (</span><span>echo</span><span> </span><span>%%i</span><span>)</span></div></div><div><div><div>9</div></div><div><span>for</span><span> %%i in (*.*) </span><span>do</span><span> (</span><span>echo</span><span> </span><span>%%i</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>扩展语法</strong></p><ul>
<li>
<p>只遍历文件夹</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> /d %%i in (</span><span>set</span><span>) </span><span>do</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>遍历指定文件夹下的所有文件，不指定则表示当前目录。如果set为<code>.</code>表示枚举目录树</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> /r [[drive:]path] %%i in (</span><span>set</span><span>) </span><span>do</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>for</span><span> /r %%i in (*) </span><span>do</span><span> (</span><span>echo</span><span> </span><span>%%i</span><span>)</span></div></div><div><div><div>4</div></div><div><span>for</span><span> /r d:</span><span>\u</span><span>p %%i in (</span><span>.</span><span>) </span><span>do</span><span> (</span><span>echo</span><span> </span><span>%%i</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>遍历数字序列</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> /l %%i in (</span><span>start,step,end</span><span>) </span><span>do</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
<li>
<p>遍历文件内容</p>
<div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>for</span><span> /f [</span><span>"options"</span><span>] %%i in (</span><span>set</span><span>) </span><span>do</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
</li>
</ul></section><section><h2>通配符<a href="#通配符"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>?    </span><span># 单个字符</span></div></div><div><div><div>2</div></div><div><span>*    </span><span># 任意字符</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>例子<a href="#例子"><span>#</span></a></h2><p>按顺序为文件夹中的文件命名。</p><p><a href="https://blog.csdn.net/Dandelion_gong/article/details/78067501" target="_blank">批处理中for循环的自增自减问题_Dandelion_gong的博客-CSDN博客_bat for 自增</a></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>@echo</span><span> </span><span>off</span></div></div><div><div><div>2</div></div><div><span>setlocal</span><span> </span><span>enabledelayedexpansion</span></div></div><div><div><div>3</div></div><div><span>set</span><span> </span><span>cnt=</span><span>0</span></div></div><div><div><div>4</div></div><div><span>for</span><span> %%i in (</span><span>%1</span><span>) </span><span>do</span><span> (</span></div></div><div><div><div>5</div></div><div><span>    </span><span>rename</span><span> </span><span>%%i</span><span> </span><span>!cnt!.txt</span></div></div><div><div><div>6</div></div><div><span>    </span><span>set</span><span> </span><span>/a</span><span> </span><span>cnt=!cnt!</span><span> </span><span>+</span><span> </span><span>1</span></div></div><div><div><div>7</div></div><div><span>)</span></div></div><div><div><div>8</div></div><div><span>pause</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/guide/firefly-wiki-link/</id>
      <title type="text">Firefly Wiki Link 内部链接示例</title>
      <published>1970-01-03T00:00:00.000Z</published>
      <updated>1970-01-03T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/guide/firefly-wiki-link/"/>
      <summary type="text">在 Firefly 文章中使用 Obsidian 风格的 Wiki Link 内部链接，并自动生成文章链接卡片。</summary>
      <content type="html"><![CDATA[<p>Firefly 支持在 Markdown、MDX 文章中使用 Obsidian 风格的 Wiki Link 内部链接。链接目标填写文章的 slug 或文件路径，都不需要包含扩展名，具体匹配规则见下文「链接目标的三种写法」。</p>
<section><h2>文章链接卡片<a href="#文章链接卡片"><span>#</span></a></h2><p><code>[[slug]]</code> 单独成段时，会自动读取目标文章的标题、描述、发布时间、分类、标签和封面，渲染为链接卡片：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span><span>[</span><span>firefly</span><span>]</span><span>]</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>[</span><span>[</span><span>guide/index</span><span>]</span><span>]</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>[</span><span>[</span><span>markdown-extended</span><span>]</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><a href="/posts/firefly/">firefly</a></p><p><a href="/posts/guide/">guide/index</a></p><p><a href="/posts/markdown-extended/">markdown-extended</a></p></section>
<section><h2>行内链接<a href="#行内链接"><span>#</span></a></h2><p><code>[[slug]]</code> 出现在正文中间时，渲染为普通链接，链接文字自动使用目标文章的标题</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>请参阅 [</span><span>[</span><span>firefly</span><span>]</span><span>] 了解主题特性。</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>请参阅 <a href="/posts/firefly/">firefly</a> 了解主题特性。</p></section>
<section><h2>自定义显示标题<a href="#自定义显示标题"><span>#</span></a></h2><p>在 <code>|</code> 后填写链接的显示文字。行内链接会用它替换文章标题；单独成段时依然渲染为卡片，卡片标题使用自定义文字，描述、时间、分类、标签和封面仍然读取目标文章：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>请参阅 [</span><span>[</span><span>firefly|主题介绍</span><span>]</span><span>] 了解主题特性。</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>[[firefly|Firefly 主题介绍]]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>请参阅 <a href="/posts/firefly/">主题介绍</a> 了解主题特性。</p><p><a href="/posts/firefly/">Firefly 主题介绍</a></p><p>一个例外：如果 <code>|</code> 后的文字只是把链接目标又抄了一遍（<code>[[guide/index|index]]</code>），会被当作无效别名忽略，仍然显示文章标题。Obsidian 在插入的链接时会自动补上这样的别名，避免笔记里显示一长串路径，这个例外就是为它准备的。</p></section>
<section><h2>链接目标的三种写法<a href="#链接目标的三种写法"><span>#</span></a></h2><p>用 Obsidian 管理文章时，把 <code>src/content/posts</code> 目录本身作为 Obsidian 仓库（vault）打开。下文提到的「仓库根目录」都指这个目录，它正好也是 Firefly 解析链接路径的起点。</p><p>链接目标按以下顺序匹配：</p>

<table><thead><tr><th>写法</th><th>示例</th><th>在 Obsidian 中</th></tr></thead><tbody><tr><td>frontmatter 的 <code>slug</code></td><td><code>[[firefly-wiki-link]]</code></td><td>✗ 不支持</td></tr><tr><td>文件路径（相对仓库根目录）</td><td><code>[[guide/firefly-layout-system]]</code></td><td>✓ 需改设置（推荐）</td></tr><tr><td>裸文件名（仓库内唯一时）</td><td><code>[[firefly-layout-system]]</code></td><td>✓ 默认即是</td></tr></tbody></table><section><h3>第一种：slug<a href="#第一种slug"><span>#</span></a></h3><p><code>slug</code> 写法在 Obsidian 中不支持，<code>slug</code> 是 Firefly 自己的概念，Obsidian 不读取 frontmatter 里的 <code>slug</code>，所以按 slug 写的链接在 Obsidian 里既不会自动补全，也点不动，只有构建出的站点上能正常跳转。如果你主要在 Obsidian 里写作，用下面两种写法。</p></section><section><h3>第二种：文件路径（推荐）<a href="#第二种文件路径推荐"><span>#</span></a></h3><p>文件路径写法需要改 Obsidian 设置，在 <code>设置 → 文件与链接 → 链接 → 内部链接类型</code> 中选择<strong>基于仓库根目录的绝对路径</strong>，Obsidian 插入的链接才会带上目录：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span><span>[</span><span>guide/firefly-layout-system|firefly-layout-system</span><span>]</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><a href="/posts/guide/firefly-layout-system/">guide/firefly-layout-system</a></p><p>因为仓库根目录就是 <code>src/content/posts</code>，Obsidian 写出的这个路径和 Firefly 需要的路径完全一致，不需要任何额外转换。</p><p>上面这行末尾的 <code>|firefly-layout-system</code> 是 Obsidian 自动补的别名，Firefly 会忽略它，卡片标题仍然取文章的 title，除非你把它修改和文件名不一致，才会变成你修改后的别名。</p><p>内部链接类型下拉框里的<strong>基于当前笔记的相对路径</strong>只在同目录内可用：它给同目录文章生成的是裸文件名，能正常匹配；但跨目录时会生成 <code>../</code> 前缀，Firefly 无法解析，链接会按原文显示。</p></section><section><h3>第三种：裸文件名<a href="#第三种裸文件名"><span>#</span></a></h3><p>「内部链接类型」的默认值就是<strong>尽可能简短的形式</strong>：只要文件名在整个仓库里唯一，Obsidian 插入的链接就只有文件名，不带目录。这种写法不需要改任何设置，效果和写完整路径完全一致：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span><span>[</span><span>firefly-layout-system</span><span>]</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><a href="/posts/firefly-layout-system/">firefly-layout-system</a></p><p>文件名重名时这种写法会失效，构建日志里会给出提示，改用完整文件路径即可，所以推荐直接修改 Obsidian 内部链接类型设置为<strong>基于仓库根目录的绝对路径</strong>，使用第二种文件路径写法，一劳永逸</p></section></section>
<section><h2>链接到其他文章的标题<a href="#链接到其他文章的标题"><span>#</span></a></h2><p>在文章 slug 后添加 <code>#标题</code>。带标题锚点的链接始终渲染为普通链接：</p><p><a href="/posts/code-examples/#%E8%AF%AD%E6%B3%95%E9%AB%98%E4%BA%AE">查看代码块语法高亮</a></p><p><a href="/posts/guide/firefly-layout-system/#%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5">guide/firefly-layout-system#相关链接#相关链接</a></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span><span>[</span><span>code-examples#语法高亮|查看代码块语法高亮</span><span>]</span><span>]</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>[</span><span>[</span><span>guide/firefly-layout-system#相关链接|firefly-layout-system</span><span>]</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>标题锚点使用与页面标题相同的 slug 规则，因此中文、空格和大小写都会按页面实际生成的 ID 处理。</p></section>
<section><h2>链接到本页标题<a href="#链接到本页标题"><span>#</span></a></h2><p>省略文章 slug，只填写标题即可链接到当前文章：</p><p><a href="#%E6%9C%AC%E9%A1%B5%E7%9B%AE%E6%A0%87">跳转到本页目标</a></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[</span><span>[</span><span>#本页目标|跳转到本页目标</span><span>]</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>本页目标<a href="#本页目标"><span>#</span></a></h2><p>这是本页 Wiki Link 指向的标题。</p></section>
<section><h2>不支持附件嵌入<a href="#不支持附件嵌入"><span>#</span></a></h2><p>附件嵌入语法目前不会被转换，会按原文显示：</p><p>![[image.png]]</p><p>行内代码和代码块中的 <code>[[firefly]]</code> 也不会被转换。</p></section>]]></content>
    </entry>
    <entry>
      <id>https://sakura-hu.top/posts/markdown-plantuml/</id>
      <title type="text">Markdown PlantUML 图表</title>
      <published>1970-01-01T00:00:00.000Z</published>
      <updated>1970-01-01T00:00:00.000Z</updated>
      <author><name>Sakura</name></author>
      <link rel="alternate" href="https://sakura-hu.top/posts/markdown-plantuml/"/>
      <summary type="text">用于验证 Firefly 中 PlantUML 插件渲染、主题切换与交互能力的示例文章。</summary>
      <content type="html"><![CDATA[<section><h2>Markdown 中 PlantUML 图表指南<a href="#markdown-中-plantuml-图表指南"><span>#</span></a></h2><p>PlantUML 是一种使用纯文本描述图表的工具。你只需要写一段结构化语法，就可以生成时序图、类图、用例图、活动图等常见工程图。</p><p>它特别适合写在技术博客和项目文档里：</p><ul>
<li>图表和正文一起版本管理，便于协作与审阅</li>
<li>修改图只需要改文本，适合频繁迭代</li>
<li>能和 Markdown 无缝结合，保持文档统一</li>
</ul><p>在 Firefly 中，<code>plantuml</code> 代码块会在构建阶段编码并生成服务器 SVG 地址，页面端再根据亮暗主题自动切换图源，并支持缩放、拖拽和全屏交互。</p><p>如果你想快速上手，可以记住这个最小模板：</p><div><div><img alt="@startuml
Alice -&gt; Bob: Hello
Bob --&gt; Alice: Hi
@enduml" src="https://www.plantuml.com/plantuml/svg/SoWkIImgAStDuNBCoKnELT2rKt3AJx9Iy4ZDoSddSifFKj3LjLC0orafU6HoEQJcfPJcvW00" loading="lazy" /></div></div></section>
<section><h2>活动图示例<a href="#活动图示例"><span>#</span></a></h2><div><div><img alt="@startuml
start
:用户提交订单;
if (库存充足?) then (是)
	:冻结库存;
	:创建支付单;
	if (支付成功?) then (是)
		:生成发货单;
		:通知仓库拣货;
	else (否)
		:取消订单;
		:释放库存;
	endif
else (否)
	:提示缺货;
endif
stop
@enduml" src="https://www.plantuml.com/plantuml/svg/NOuz2i9048NhtLEaZ5VO57eLmHM56CN4taODYYHYbA15mFz6BHI5ZNYQp6oyXMI38dRpcFTzl9BjbDjEfs5ITL0cWmEwT_JyH6pIqmh6CuFMgveEOWh747gzz7Of5ZIdnYrDn_1Se8H1FvRnD6yOMNQN40iCpaaSAW7937b6ruTXz6SWJ0PHzl4cwNMV8uIzkWiPRHDbnj4wlUuDIhXfSqq7VwSmyEPuSpyxCsOmnE3vdSAjIhrAVnc6dYytGZw4ykK5sscsQ8bRbKx3V0C0" loading="lazy" /></div></div></section>
<section><h2>状态图示例<a href="#状态图示例"><span>#</span></a></h2><div><div><img alt="@startuml
[*] --&gt; 草稿

草稿 --&gt; 待审核 : 提交
待审核 --&gt; 草稿 : 驳回
待审核 --&gt; 已发布 : 审核通过
已发布 --&gt; 已归档 : 到期归档
已发布 --&gt; 草稿 : 撤回修改

state 已发布 {
	[*] --&gt; 可见
	可见 --&gt; 隐藏 : 手动隐藏
	隐藏 --&gt; 可见 : 恢复展示
}

已归档 --&gt; [*]
@enduml" src="https://www.plantuml.com/plantuml/svg/SoWkIImgAStDuOhMYbNGrRLJUD7R-Np5VYuk20qMUhglzUcwXSyMx52mKdZMF-79hYLS22647WKhXPShDp-TFGzDzkdsJK_x9pxTqQnWfG0HVjamwyN-TYw414pPtadF5Yu6AUlOy6pEV0WNJHdShcUJbZoTFU_9_dNFfkpauYekIIn9HRAgceiJvgEd_UjVB6_aueJGOA6NioQycDuFCgIp-sdN2WYNYnCgZD04Kj6uwEcItgSRfpvViekhbeiBuKoGicYjM2w7rBoKqjmS0000" loading="lazy" /></div></div></section>
<section><h2>用例图示例<a href="#用例图示例"><span>#</span></a></h2><div><div><img alt="@startuml
left to right direction
actor 游客
actor 用户
actor 管理员

rectangle 博客系统 {
	usecase &quot;浏览文章&quot; as UC1
	usecase &quot;搜索内容&quot; as UC2
	usecase &quot;发表评论&quot; as UC3
	usecase &quot;点赞收藏&quot; as UC4
	usecase &quot;审核评论&quot; as UC5
	usec" src="https://www.plantuml.com/plantuml/svg/PP3BJi9058RNdASuOSz2Raj3uZFm09CoOfFI9krqPTm88XjJI8WQGv1s8ud9i02qoFFCvJ6CCf8EBh_pvJyvvsz5Z8GixddWqKk6BC3GxLunxBWXTPWR-40S5eGeyrpmp82QBkNeymzugiP3CNa6-8aGl-jH50ylWcTglLVxLxo6KXnHXqGKotARwBUHVBnNxuioaWZR5sT5FPwfJIQ6TuBlZAuMj4WcEbtgrK3pBwDh1QrkTtexbzCF_PGONI-cUIeNkPLksCj5thTEQy8DmE5rh5JEVu-ri0g7BexMmXDRix4En-hCfF5lqeGMzJjnp_i6" loading="lazy" /></div></div></section>
<section><h2>组件图示例<a href="#组件图示例"><span>#</span></a></h2><div><div><img alt="@startuml
package &quot;Firefly Site&quot; {
	[Astro App] as App
	[Markdown Parser] as Parser
	[PlantUML Encoder] as Encoder
	[Theme Switcher] as Theme
	[Search Indexer] as Search
}

cloud &quot;PlantUML Server&quot; as " src="https://www.plantuml.com/plantuml/svg/JL51QiCm43nxNp7u7dht8IIK5WeD15oVJ0-oj8sDPKcLvAIbzEz5YX9wqZ2pxCoiTY4A7zT5anDo5YT6_Jnv_j3VQAV8DNwewlSXUekzS-yG8RrKzGVXPsKl1aVX0_kiN25L_L4B4xl3AvwCjAge1LFLlusyCDhB5ELOn4nHrRSil1pnOXH_5UbAqI-Hr7PLgE_RM_PdzdMsxaY9A0OH6FMZDP5DH1kjvomNXcZl73QRROcA1Yu1BAKD5JwDbBnem1d19TzrqHYqbNEWsq3Urw61vy-LGqGud-v6rwuDGcuB9VpyeAVJ616yl4-LsWs6TT8ABdt4b6xmFz2jLZ8I2iw666Z7HgsB_WC0" loading="lazy" /></div></div></section>
<section><h2>部署图示例<a href="#部署图示例"><span>#</span></a></h2><div><div><img alt="@startuml
node &quot;User Device&quot; {
	artifact &quot;Browser&quot;
}

node &quot;CDN / Edge&quot; {
	artifact &quot;Static Assets&quot;
}

node &quot;Cloudflare Worker&quot; {
	artifact &quot;SSR Handler&quot;
}

node &quot;PlantUML Service&quot; {
	artifact &quot;SVG Re" src="https://www.plantuml.com/plantuml/svg/ROz1Jy9044RFxg_uqZlfdOD1WS11r53Hyz2TraAxYpDJE1Z_kqb3J6azxtkpxvkfaLZRr2v4pqXsoe85dwkS4toxEnAh2ieDoQF4Yx8axiTTsVdY6IcMlXoYcP5LEHvKsRGlrB7rHKt2-8XoP1bwsHPh2hw--UQrfc2xpHCobd-wilSLjXmyozNoPBGdPIGl-mFdXioYq2XoGtBqyH8mZy4uMEV-hSHaSZySWYbMopSSDCrLqwefHtYl7rC8VxMiXXELx6uVEtnKq3daSOfgezlzcPYYOCi_uIigXHhekNGp3hvjwby0" loading="lazy" /></div></div></section>
<section><h2>ER 图示例<a href="#er-图示例"><span>#</span></a></h2><div><div><img alt="@startuml
entity User {
	*id : uuid &lt;&lt;PK&gt;&gt;
	--
	username : varchar
	email : varchar
	created_at : datetime
}

entity Post {
	*id : uuid &lt;&lt;PK&gt;&gt;
	--
	author_id : uuid &lt;&lt;FK&gt;&gt;
	title : varchar
	content : " src="https://www.plantuml.com/plantuml/svg/VP2x2W9134NhoLUa5lO71X51iB6niPRiJc07vY6JZ0_KVvTTKHGVRKwIavkvA1MjCG0dzNh2ZN31CvY9TsYnLkzmEbslPZCmJGEc2fT4aT7YdahNKm73aNnuBNI5ITbjIT6Y8sNraU4A3ySwY_vnKDK-b-qhN8vGlOOtSvUJSXeioaS5iwjjyDB_KIzoZCF4R_ikYtvn3y6_bDliJa_zpzpZKo-NfiddUtYBX-ALvOqyRhDuNoGmzdxGdWJcd5oDuGO0" loading="lazy" /></div></div></section>
<section><h2>时序图示例（登录与刷新令牌）<a href="#时序图示例登录与刷新令牌"><span>#</span></a></h2><div><div><img alt="@startuml
autonumber
actor User as 用户
participant Web as 前端页面
participant API as 网关接口
participant Auth as 认证服务
database Redis as 会话缓存

用户 -&gt; 前端页面 : 输入账号密码并提交
前端页面 -&gt; 网关接口 : POST /login
网关接口 -&gt; 认证服务 : " src="https://www.plantuml.com/plantuml/svg/bP3BJi9068NtynJ_thGIbrqqk7GbyHANPYYZ4A4bxVG1M70D5GrAn42u2CPee2HUY0BwCioql8L1t0ma1jVVEJFVVu8MnIQr4t64RMheTY9CJ8GrQfXmQ14Ji0NUrGFF3L4IcpIcnP9Oft14mdF2yextvCvQhxDwM-3RePsVvkIIfP_vUPULxaHkq-WyuFSxlflYDOSLMYY2AGvZYy0UYSIiEPwERtstxetBh7U3qC8395Nu5nJmFyiitVLVxbbfoDoCrqonzpTUkfX-T90GbLH12HG8xUuVm6RSE8tfI42IAiY10hpPcZqMMRR7dJuIcAGAfg00orHPkWicEJ695JscnXdHbpghBbZJY6Kjmh2nL1RbLZVO2WJ0guxOv9hdBbYXWP0io-AJ_bUMrneWop9QRvPVW_M6-VFObO7yIPyxxNyEn2i3uQArHk6LmNJKyV9552Hwn4x4lm40" loading="lazy" /></div></div></section>
<section><h2>C4 风格容器图示例<a href="#c4-风格容器图示例"><span>#</span></a></h2><div><div><img alt="@startuml
!includeurl https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml

Person(user, &quot;博客访客&quot;, &quot;阅读文章与搜索内容&quot;)

System_Boundary(system, &quot;Firefly Blog&quot;) {
	Container(we" src="https://www.plantuml.com/plantuml/svg/PL7BJjj055tNNp7robP3l67L5I7b9GKHnQ6egyYE9udBn8x6Oq9KLQeeeLMbGDHIYWfL6fTAIAXs7w2cGOAVoTZENrHZfsaYTZFtddlkkUVCMKJ1nAuYxhvk596jGHiZK26aPZsK9ApKasMTL6pLjY0kcWQ11aaMpQfKGuh1ncOieY5TbTApCrbMMb_DI5N58X1BwTb2sZI8eXiG9sji1PU5s38DWP4b04zR7wdhXEujTHq-0VZXSJFqhlsZLy75vq5lts-V1fSEtMjIzmylSfpSi0Yi5kPDsz0Kt12iw9y0_AAEOGarm3mooxm8dd7tndk5EbGJWD-0AaZLQcn9oYBO10-0l0KHWQpYztxwdNV3xjNmapFexG_wPuF-MrwSOZ7nPYHPbdDW8_gmoJGoRQs450n7HOjLwTbkqDxpJrlqJJUuy6BwqFlj_zYPOdsa2YD76LNy0Z8nSQHgLS6RcbatW0JMrAUm6BUKSjIatuxfpldOgV1gr--tQVEb__wxttAdjLjGmSKAqnuzm8gXmMr6abNAiAGRMkJ1GNjmSqARZDF_yYAuT7YHUpxsV66R2FyIPvEZf84CyLPiHN3OyL-tWVnu2T2Jcx1xpXBBGJJAEeuXT6-77rm6Nyxdip8lHeYeDtOuDWeme-D8xi17oF_UXTurFJYAtPZa75_kTxw6daERluQ7RkYr9Z4JLyMRO_Ni47P19lLaRJrVo2mivWkvbQNbl21oSz3Gx2hw2m00" loading="lazy" /></div></div></section>]]></content>
    </entry>
</feed>
