diff --git a/README.md b/README.md index 1e3acab..2f29c7a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@
-
 ███████╗ █████╗ ███████╗██╗   ██╗    ██╗   ██╗██╗██████╗ ███████╗
@@ -115,9 +114,11 @@ Easy-Vibe 通过以下几个阶段,带你从 0 到 1:
 | **第一阶段** | AI 编程入门、产品思维、原型设计  | 互动小游戏、Web 应用原型(新手入门 & PM) |
 | **第二阶段** | 全栈开发、AI 集成、数据库        | 完整的全栈 AI 应用                        |
 | **第三阶段** | claude code 进阶、小程序安卓开发 | 生产级多平台应用                          |
+| **附录**     | 帮你理解计算机、人工智能基础概念 | 9 大知识领域、80+ 交互式专题              |
 
 ## 🔥 News
 
+- **[2026-02-25]** 更新[附录知识库](https://datawhalechina.github.io/easy-vibe/zh-cn/appendix/),涵盖 9 大知识领域、80+ 交互式专题。
 - **[2026-01-27]** 新增 Android 和 iOS 平台应用开发教程。
 - **[2026-01-19]** 发布 Prompt Engineering、AI 演进史、鉴权设计、Git 原理等一系列交互式演示组件,大幅提升可视化学习体验。
 
@@ -136,11 +137,101 @@ Easy-Vibe 通过以下几个阶段,带你从 0 到 1:
   Learning Map
 
-### 总附录 +### 📚 附录知识库 -[AI 能力词典:常见 AI 核心概念与名词、场景解释](docs/zh-cn/appendix/ai-capability-dictionary.md) +> 涵盖 **9 大知识领域**、**80+ 交互式专题**,以动画和可视化组件帮助你直观理解从计算机底层到 AI 前沿的核心概念。 +> +> 👉 [查看完整附录](https://datawhalechina.github.io/easy-vibe/zh-cn/appendix/) · [AI 能力词典](https://datawhalechina.github.io/easy-vibe/zh-cn/appendix/8-artificial-intelligence/ai-capability-dictionary) -### 一、新手入门 & PM - 从游戏到 Web 应用原型 + + + + + + + + + + + + + + + + +
+ 💻 计算机基础

+ • 从晶体管到 CPU
+ • 操作系统
+ • 数据的编码、存储与传输
+ • 网络:两台电脑如何对话
+ • 数据结构
+ • 算法思维入门 +
+ 🔧 开发工具

+ • Git:代码的时光机
+ • 命令行与 Shell 脚本
+ • 包管理器
+ • 调试的艺术
+ • 正则表达式
+ • 环境变量与 PATH +
+ 🌐 浏览器与前端

+ • JavaScript 语言深入
+ • 浏览器渲染管道
+ • 前端框架对比
+ • 图形与动画
+ • 网页性能的度量与优化
+ • 前端工程化全貌 +
+ 🖥️ 服务器与后端

+ • HTTP 协议
+ • API 设计哲学
+ • 认证与授权体系
+ • 并发、异步与多线程
+ • 消息队列与事件驱动
+ • 后端分层架构 +
+ 📊 数据

+ • SQL
+ • 数据库原理
+ • 数据埋点与用户行为采集
+ • 数据分析基础
+ • A/B 测试与实验驱动
+ • 数据可视化与仪表盘 +
+ 🏗️ 架构与系统设计

+ • 从单体到微服务的演进
+ • 分布式系统的挑战
+ • 高可用与容灾
+ • 系统设计方法论 +
+ ☁️ 基础设施与运维

+ • Docker 容器化
+ • Kubernetes 编排
+ • CI / CD 自动化
+ • 域名、DNS 与 HTTPS
+ • 监控、日志与告警
+ • 基础设施即代码 +
+ 🤖 人工智能

+ • 大语言模型的工作原理
+ • Transformer 与注意力机制
+ • RAG 架构
+ • AI Agent 与工具调用
+ • 提示词工程
+ • 图像生成原理 +
+ 🎯 工程素养

+ • 代码质量与重构
+ • 测试策略
+ • 设计模式
+ • 安全思维与攻防基础
+ • 技术文档写作
+ • 开源协作 +
+ +### 一、零基础入门 | 章节 | 关键内容 | 状态 | | :----------------------------------------------------------------------------------------------- | :------------------------------------------------ | :--- | diff --git a/assets/header.png b/assets/header.png deleted file mode 100644 index aaedae4..0000000 Binary files a/assets/header.png and /dev/null differ diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 8819f81..40d9d85 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -613,13 +613,13 @@ export default defineConfig({ text: '编程语言图谱', link: '/zh-cn/appendix/1-computer-fundamentals/programming-languages' }, - { - text: '类型系统入门', - link: '/zh-cn/appendix/1-computer-fundamentals/type-systems' - }, { text: '编译原理入门', link: '/zh-cn/appendix/1-computer-fundamentals/compilers' + }, + { + text: '类型系统入门', + link: '/zh-cn/appendix/1-computer-fundamentals/type-systems' } ] }, @@ -725,6 +725,10 @@ export default defineConfig({ text: '前端工程化全貌', link: '/zh-cn/appendix/3-browser-and-frontend/frontend-engineering' }, + { + text: '前端项目架构设计', + link: '/zh-cn/appendix/3-browser-and-frontend/frontend-project-architecture' + }, { text: '无障碍与国际化', link: '/zh-cn/appendix/3-browser-and-frontend/a11n-i18n' @@ -807,6 +811,10 @@ export default defineConfig({ text: '后端分层架构', link: '/zh-cn/appendix/4-server-and-backend/backend-layered-architecture' }, + { + text: '后端项目架构设计', + link: '/zh-cn/appendix/4-server-and-backend/backend-project-architecture' + }, { text: '领域特定语言(DSL)', link: '/zh-cn/appendix/4-server-and-backend/domain-specific-languages' diff --git a/docs/.vitepress/theme/components/appendix/async-task-queues/AsyncComparisonDemo.vue b/docs/.vitepress/theme/components/appendix/async-task-queues/AsyncComparisonDemo.vue new file mode 100644 index 0000000..8e10de2 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/async-task-queues/AsyncComparisonDemo.vue @@ -0,0 +1,143 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/async-task-queues/AsyncTaskFlowDemo.vue b/docs/.vitepress/theme/components/appendix/async-task-queues/AsyncTaskFlowDemo.vue new file mode 100644 index 0000000..0ac9ecc --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/async-task-queues/AsyncTaskFlowDemo.vue @@ -0,0 +1,197 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/async-task-queues/TaskRetryDemo.vue b/docs/.vitepress/theme/components/appendix/async-task-queues/TaskRetryDemo.vue new file mode 100644 index 0000000..4a39e74 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/async-task-queues/TaskRetryDemo.vue @@ -0,0 +1,168 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/async-task-queues/TaskWorkerDemo.vue b/docs/.vitepress/theme/components/appendix/async-task-queues/TaskWorkerDemo.vue new file mode 100644 index 0000000..1a864cd --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/async-task-queues/TaskWorkerDemo.vue @@ -0,0 +1,190 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/AppLaunchDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/AppLaunchDemo.vue new file mode 100644 index 0000000..9061342 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/AppLaunchDemo.vue @@ -0,0 +1,337 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/BiosUefiDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/BiosUefiDemo.vue new file mode 100644 index 0000000..380bef8 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/BiosUefiDemo.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/BiosUefiInteractiveDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/BiosUefiInteractiveDemo.vue new file mode 100644 index 0000000..bd3b282 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/BiosUefiInteractiveDemo.vue @@ -0,0 +1,599 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/BootProcessDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/BootProcessDemo.vue index 187bff9..dcc1544 100644 --- a/docs/.vitepress/theme/components/appendix/computer-fundamentals/BootProcessDemo.vue +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/BootProcessDemo.vue @@ -1,15 +1,129 @@ diff --git a/docs/.vitepress/theme/components/appendix/computer-fundamentals/OSBootInteractiveDemo.vue b/docs/.vitepress/theme/components/appendix/computer-fundamentals/OSBootInteractiveDemo.vue new file mode 100644 index 0000000..534793a --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/computer-fundamentals/OSBootInteractiveDemo.vue @@ -0,0 +1,763 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/file-storage/CDNAccelerationDemo.vue b/docs/.vitepress/theme/components/appendix/file-storage/CDNAccelerationDemo.vue new file mode 100644 index 0000000..5a74c25 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/file-storage/CDNAccelerationDemo.vue @@ -0,0 +1,123 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/file-storage/FileStorageTypeDemo.vue b/docs/.vitepress/theme/components/appendix/file-storage/FileStorageTypeDemo.vue new file mode 100644 index 0000000..c72d1fe --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/file-storage/FileStorageTypeDemo.vue @@ -0,0 +1,114 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/file-storage/FileUploadFlowDemo.vue b/docs/.vitepress/theme/components/appendix/file-storage/FileUploadFlowDemo.vue new file mode 100644 index 0000000..1d393ed --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/file-storage/FileUploadFlowDemo.vue @@ -0,0 +1,135 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/project-architecture/ArchitectureComparisonDemo.vue b/docs/.vitepress/theme/components/appendix/project-architecture/ArchitectureComparisonDemo.vue new file mode 100644 index 0000000..9286e14 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/project-architecture/ArchitectureComparisonDemo.vue @@ -0,0 +1,551 @@ + + + + + diff --git a/docs/.vitepress/theme/components/appendix/rate-limiting/BackpressureDemo.vue b/docs/.vitepress/theme/components/appendix/rate-limiting/BackpressureDemo.vue new file mode 100644 index 0000000..98e9871 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/rate-limiting/BackpressureDemo.vue @@ -0,0 +1,173 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/rate-limiting/RateLimitAlgorithmDemo.vue b/docs/.vitepress/theme/components/appendix/rate-limiting/RateLimitAlgorithmDemo.vue new file mode 100644 index 0000000..2520728 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/rate-limiting/RateLimitAlgorithmDemo.vue @@ -0,0 +1,219 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/rate-limiting/RateLimiterDemo.vue b/docs/.vitepress/theme/components/appendix/rate-limiting/RateLimiterDemo.vue new file mode 100644 index 0000000..63dbcdf --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/rate-limiting/RateLimiterDemo.vue @@ -0,0 +1,204 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/search-engines/InvertedIndexDemo.vue b/docs/.vitepress/theme/components/appendix/search-engines/InvertedIndexDemo.vue new file mode 100644 index 0000000..08450d6 --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/search-engines/InvertedIndexDemo.vue @@ -0,0 +1,145 @@ + + + + + + diff --git a/docs/.vitepress/theme/components/appendix/search-engines/SearchRelevanceDemo.vue b/docs/.vitepress/theme/components/appendix/search-engines/SearchRelevanceDemo.vue new file mode 100644 index 0000000..62a55cb --- /dev/null +++ b/docs/.vitepress/theme/components/appendix/search-engines/SearchRelevanceDemo.vue @@ -0,0 +1,126 @@ + + + + + + diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js index 2f33b83..5d905e4 100644 --- a/docs/.vitepress/theme/index.js +++ b/docs/.vitepress/theme/index.js @@ -206,7 +206,11 @@ import LearningStrategyDemo from './components/appendix/computer-fundamentals/Le import VibeCodingFlowDemo from './components/appendix/computer-fundamentals/VibeCodingFlowDemo.vue' import PowerOnDemo from './components/appendix/computer-fundamentals/PowerOnDemo.vue' import BootProcessDemo from './components/appendix/computer-fundamentals/BootProcessDemo.vue' +import BiosUefiDemo from './components/appendix/computer-fundamentals/BiosUefiDemo.vue' +import BiosUefiInteractiveDemo from './components/appendix/computer-fundamentals/BiosUefiInteractiveDemo.vue' +import AppLaunchDemo from './components/appendix/computer-fundamentals/AppLaunchDemo.vue' import DesktopDemo from './components/appendix/computer-fundamentals/DesktopDemo.vue' +import OSBootInteractiveDemo from './components/appendix/computer-fundamentals/OSBootInteractiveDemo.vue' import BrowserArchitectureDemo from './components/appendix/computer-fundamentals/BrowserArchitectureDemo.vue' import URLRequestDemo from './components/appendix/computer-fundamentals/URLRequestDemo.vue' import RenderingDemo from './components/appendix/computer-fundamentals/RenderingDemo.vue' @@ -758,6 +762,28 @@ import IncidentCommandDemo from './components/appendix/incident-response/Inciden import AlertEscalationDemo from './components/appendix/incident-response/AlertEscalationDemo.vue' import PostmortemDemo from './components/appendix/incident-response/PostmortemDemo.vue' +// Async Task Queues Components +import AsyncTaskFlowDemo from './components/appendix/async-task-queues/AsyncTaskFlowDemo.vue' +import TaskWorkerDemo from './components/appendix/async-task-queues/TaskWorkerDemo.vue' +import TaskRetryDemo from './components/appendix/async-task-queues/TaskRetryDemo.vue' +import AsyncComparisonDemo from './components/appendix/async-task-queues/AsyncComparisonDemo.vue' + +// File Storage Components +import FileStorageTypeDemo from './components/appendix/file-storage/FileStorageTypeDemo.vue' +import FileUploadFlowDemo from './components/appendix/file-storage/FileUploadFlowDemo.vue' +import CDNAccelerationDemo from './components/appendix/file-storage/CDNAccelerationDemo.vue' + +// Rate Limiting Components +import RateLimitAlgorithmDemo from './components/appendix/rate-limiting/RateLimitAlgorithmDemo.vue' +import BackpressureDemo from './components/appendix/rate-limiting/BackpressureDemo.vue' + +// Search Engines Components Registration +import InvertedIndexDemo from './components/appendix/search-engines/InvertedIndexDemo.vue' +import SearchRelevanceDemo from './components/appendix/search-engines/SearchRelevanceDemo.vue' + +// Project Architecture Components +import ArchitectureComparisonDemo from './components/appendix/project-architecture/ArchitectureComparisonDemo.vue' + export default { extends: DefaultTheme, Layout, @@ -969,7 +995,11 @@ export default { app.component('VibeCodingFlowDemo', VibeCodingFlowDemo) app.component('PowerOnDemo', PowerOnDemo) app.component('BootProcessDemo', BootProcessDemo) + app.component('BiosUefiDemo', BiosUefiDemo) + app.component('BiosUefiInteractiveDemo', BiosUefiInteractiveDemo) + app.component('AppLaunchDemo', AppLaunchDemo) app.component('DesktopDemo', DesktopDemo) + app.component('OSBootInteractiveDemo', OSBootInteractiveDemo) app.component('BrowserArchitectureDemo', BrowserArchitectureDemo) app.component('URLRequestDemo', URLRequestDemo) app.component('RenderingDemo', RenderingDemo) @@ -1533,6 +1563,28 @@ export default { app.component('IncidentCommandDemo', IncidentCommandDemo) app.component('AlertEscalationDemo', AlertEscalationDemo) app.component('PostmortemDemo', PostmortemDemo) + + // Async Task Queues Components Registration + app.component('AsyncTaskFlowDemo', AsyncTaskFlowDemo) + app.component('TaskWorkerDemo', TaskWorkerDemo) + app.component('TaskRetryDemo', TaskRetryDemo) + app.component('AsyncComparisonDemo', AsyncComparisonDemo) + + // File Storage Components Registration + app.component('FileStorageTypeDemo', FileStorageTypeDemo) + app.component('FileUploadFlowDemo', FileUploadFlowDemo) + app.component('CDNAccelerationDemo', CDNAccelerationDemo) + + // Rate Limiting Components Registration + app.component('RateLimitAlgorithmDemo', RateLimitAlgorithmDemo) + app.component('BackpressureDemo', BackpressureDemo) + + // Search Engines Components Registration + app.component('InvertedIndexDemo', InvertedIndexDemo) + app.component('SearchRelevanceDemo', SearchRelevanceDemo) + + // Project Architecture Components Registration + app.component('ArchitectureComparisonDemo', ArchitectureComparisonDemo) }, setup() { const route = useRoute() diff --git a/docs/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking.md b/docs/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking.md index c035d7d..b036cf2 100644 --- a/docs/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking.md +++ b/docs/zh-cn/appendix/1-computer-fundamentals/algorithm-thinking.md @@ -69,6 +69,11 @@ **生活类比**:猜数字游戏。我想一个 1-100 的数,你每次猜中间,我告诉你大了还是小了。最多猜 7 次就能猜中(因为 2⁷ = 128 > 100)。 ::: +👇 **动手试试看**: +下面这个演示展示了二分查找的工作原理,你可以选择顺序查找或二分查找来对比: + + + ### 1.2 为什么二分查找这么快? | 数据量 | 线性查找 | 二分查找 | @@ -78,7 +83,17 @@ | 1,000,000 | 1,000,000 次 | 20 次 | | 1,000,000,000 | 1,000,000,000 次 | 30 次 | -::: tip 💡 对数增长的威力 +::: tip � 逐行解读这张表 +**第一列(数据量)**:要查找的数据有多少。可以看到数据量从 100 增长到 10 亿(扩大了 1000 万倍!) + +**第二列(线性查找)**:最"笨"的方法,从第一个开始一个一个找。查找次数等于数据量,数据量越大,查找次数越多。 + +**第三列(二分查找)**:聪明的方法,每次排除一半。查找次数只和数据量的对数有关,即使 10 亿数据也只需要 30 次! + +**对比结论**:当数据量达到 100 万时,线性查找需要 100 万次,二分查找只需要 20 次——差距达 5 万倍! +::: + +::: tip � 对数增长的威力 二分查找的时间复杂度是 O(log n),这意味着: - 10 亿数据,最多查找 30 次 @@ -102,6 +117,20 @@ | **归并排序** | O(n log n) | 稳定排序 | 需要稳定性的场景 | | **堆排序** | O(n log n) | 原地排序 | 内存受限场景 | +::: tip 📊 逐行解读这张表 +**冒泡排序**:最基础的排序算法,就像水底的气泡往上冒一样。简单易懂,但速度最慢。适合学习排序思想,不适合实际使用。 + +**选择排序**:每次选出最小的放到前面。也很简单,但无论数据是否有序都要做同样多的比较。 + +**插入排序**:像打扑克牌时整理手牌一样。把每个元素插入到前面已经排好序的部分中。对近乎有序的数据效率很高。 + +**快速排序**:实际开发中最常用的排序。平均情况下最快,但最坏情况(数据已经有序)会退化到 O(n²)。 + +**归并排序**:采用"分而治之"的思想,总是 O(n log n),但需要额外空间。适合需要稳定排序的场景。 + +**堆排序**:利用堆这种数据结构的排序,原地排序(不需要额外空间),但实际运行往往比快速排序慢。 +::: + ### 2.2 为什么快速排序"快"? ::: tip 💡 快速排序的原理 @@ -120,6 +149,11 @@ **生活类比**:整理书架。先抽出一本书,把比它薄的放左边,比它厚的放右边。然后对左右两堆分别重复这个过程。 ::: +👇 **动手试试看**: +下面这个演示展示了排序算法的可视化,你可以生成数组,观察冒泡排序和快速排序的过程对比: + + + --- ## 3. 递归:自己调用自己 @@ -153,6 +187,16 @@ function factorial(n) { | **性能** | 稍慢(函数调用开销) | 更快 | | **适用场景** | 树遍历、分治算法 | 简单重复任务 | +::: tip 📊 逐行解读这张表 +**代码简洁度**:递归通常只需要几行代码就能表达复杂的逻辑(如遍历树结构),而用循环可能需要更多的变量和嵌套。 + +**内存消耗**:递归会使用"调用栈"来保存每一层的信息,就像叠盘子一样,每递归一层就多一个盘子。循环则不需要这种开销。 + +**性能**:每次函数调用都有开销(参数传递、栈操作等),所以递归通常比循环慢一些。 + +**适用场景**:递归擅长处理本身就是递归结构的问题(如文件树、DOM 树);循环擅长简单的重复操作(如遍历数组)。 +::: + ::: warning ⚠️ 递归的陷阱 **栈溢出**:递归层次太深,调用栈空间耗尽。 @@ -162,6 +206,11 @@ function factorial(n) { - 限制递归深度 ::: +👇 **动手试试看**: +下面这个演示展示了递归的调用过程,观察函数如何自己调用自己: + + + --- ## 4. 贪心算法:每步选最优 @@ -197,6 +246,11 @@ function factorial(n) { **教训**:贪心算法简单高效,但不总是能得到最优解。使用前要证明问题满足贪心条件。 ::: +👇 **动手试试看**: +下面这个演示展示了贪心算法的实际效果,你可以尝试不同的硬币组合,观察贪心策略的表现: + + + --- ## 5. 算法设计范式 @@ -208,6 +262,21 @@ function factorial(n) { | **动态规划** | 记录子问题的解 | 背包问题、最短路径 | 有重叠子问题 | | **回溯** | 试错,走不通就回退 | 八皇后、全排列 | 搜索问题 | +::: tip 📊 逐行解读这张表 +**分治**:把大问题拆成小问题,分别解决后再合并。就像整理房间,先分成客厅、卧室、厨房分别打扫,最后整体整洁。 + +**贪心**:每步都选当前最好的,不考虑长远后果。像吃饭时先挑最喜欢吃的菜,可能不是最优的吃法,但速度快。 + +**动态规划**:记住中间结果,避免重复计算。像记笔记,下次遇到同样问题直接查答案,不用重新推导。 + +**回溯**:走不通就退回来重试。像走迷宫,此路不通就返回上一个路口尝试别的路。 +::: + +👇 **动手试试看**: +下面这个演示展示了不同算法设计范式的特点和应用场景: + + + --- ## 6. 总结:算法是解决问题的艺术 diff --git a/docs/zh-cn/appendix/1-computer-fundamentals/data-structures.md b/docs/zh-cn/appendix/1-computer-fundamentals/data-structures.md index df0b1d4..77dea1e 100644 --- a/docs/zh-cn/appendix/1-computer-fundamentals/data-structures.md +++ b/docs/zh-cn/appendix/1-computer-fundamentals/data-structures.md @@ -1,230 +1,293 @@ # 数据结构 ::: tip 前言 -**如何高效地组织和存储数据?** 你可能遇到过这样的困惑:为什么有些程序处理几万条数据很快,有些处理几百条就卡住了?答案往往在于数据结构的选择。本章带你理解常见数据结构的特点和适用场景。 +**程序 = 数据结构 + 算法。** 前面我们学了 CPU 如何执行指令、操作系统如何管理资源。但程序要处理的核心对象是**数据**——用户信息、商品列表、社交关系……这些数据怎么在内存里组织,直接决定了程序的快慢。你可能遇到过这样的困惑:为什么有些程序处理几万条数据很快,有些处理几百条就卡住了?答案往往就在于**数据结构的选择**。 ::: **这篇文章会带你学什么?** 学完这章后,你将获得: -- **选型决策能力**:知道什么时候用数组快速访问,什么时候用链表灵活插入 -- **性能分析视角**:能判断性能问题是数据结构选择不当,还是算法效率低下 +- **直觉判断力**:看到一个需求,脑子里自动浮现该用什么数据结构 +- **性能分析视角**:能判断性能瓶颈是数据结构选错了,还是算法效率低 - **权衡思维**:理解"空间换时间"与"时间换空间",知道没有完美的数据结构 -- **后续学习基础**:为数据库、缓存系统、搜索引擎等技术打下基础 +- **代码阅读能力**:看到 HashMap、Stack、Queue 这些词不再陌生 +- **后续学习基础**:为数据库索引、缓存系统、搜索引擎等技术打下基础 | 章节 | 内容 | 核心概念 | |-----|------|---------| -| **第 1 章** | 线性结构 | 数组、链表、栈、队列 | -| **第 2 章** | 哈希结构 | 哈希表、冲突处理 | -| **第 3 章** | 树形结构 | 二叉树、B树、堆 | -| **第 4 章** | 图结构 | 有向图、无向图、遍历算法 | +| **第 1 章** | 全景图 | 四大类数据结构、分类标准 | +| **第 2 章** | 线性结构 | 数组、链表、栈、队列 | +| **第 3 章** | 哈希表 | 哈希函数、冲突处理、O(1) 查找 | +| **第 4 章** | 树形结构 | 二叉树、文件系统树、DOM 树 | +| **第 5 章** | 图结构 | 有向图、无向图、遍历算法 | +| **第 6 章** | 性能对比 | 时间复杂度、空间复杂度 | +| **第 7 章** | 选型指南 | 场景分析、决策流程 | --- -## 0. 全景图:数据结构是什么? +## 1. 全景图:数据结构是什么? 想象你要整理一堆书: -- **堆在地上**:找书要一本本翻(链表) -- **按编号放书架**:直接去对应位置拿(数组) -- **按类别分柜子**:先找柜子再找书(哈希表) -- **按书名排序**:二分查找,每次排除一半(树) +- **堆在地上**:找书要一本本翻——这就是最原始的存储 +- **按编号放书架**:直接去对应位置拿——这就是**数组** +- **按类别分柜子**:先确定柜子再找书——这就是**哈希表** +- **按书名排序放多层架**:每次排除一半——这就是**树** -不同的整理方式,找书的效率天差地别。**数据结构就是数据的"整理方式"**。 +不同的整理方式,找书的效率天差地别。**数据结构就是数据的"整理方式"**——它决定了数据怎么存、怎么找、怎么改。 - + -**常见数据结构分类:** +所有数据结构可以归为四大类: -| 类型 | 特点 | 典型代表 | 适用场景 | -|------|------|---------|---------| -| **线性结构** | 数据排成一排 | 数组、链表、栈、队列 | 顺序处理、历史记录 | -| **哈希结构** | 键值对映射 | 哈希表 | 快速查找、缓存 | -| **树形结构** | 层次关系 | 二叉树、B树 | 排序、搜索、文件系统 | -| **图结构** | 网状关系 | 有向图、无向图 | 社交网络、路径规划 | +| 类型 | 数据关系 | 典型代表 | 生活类比 | +|------|---------|---------|---------| +| **线性结构** | 一对一,排成一排 | 数组、链表、栈、队列 | 火车车厢、排队队伍 | +| **哈希结构** | 键→值映射 | 哈希表、字典、集合 | 图书馆索引卡片 | +| **树形结构** | 一对多,层级关系 | 二叉树、B树、堆 | 家族族谱、文件夹 | +| **图结构** | 多对多,网状关系 | 有向图、无向图 | 地铁线路图、社交网络 | -::: tip 📊 逐行解读这张表 -**线性结构**:最简单的数据组织方式,数据一个接一个排列。数组适合随机访问,链表适合频繁插入删除。 - -**哈希结构**:用"键"直接找到"值",查找速度最快。但需要处理"冲突"问题(两个键映射到同一位置)。 - -**树形结构**:有层次关系的数据。二叉搜索树适合排序和搜索,B树适合磁盘存储(数据库索引)。 - -**图结构**:最复杂的结构,表示任意的关系网络。社交网络、地图导航都用图来建模。 +::: tip 为什么要学这么多种? +因为**没有万能的数据结构**。每种结构都是在"查找速度"、"插入速度"、"内存占用"之间做权衡。就像你不会用书包装家具,也不会用卡车送一封信——选对工具,事半功倍。 ::: --- -## 1. 线性结构:最基础的组织方式 +## 2. 线性结构:最基础的组织方式 -### 1.1 数组:连续存储 +线性结构是最直觉的数据组织方式——数据一个接一个排列,就像火车车厢。但"怎么连接"和"从哪端操作"的不同,产生了四种变体,各有各的绝活。 -::: tip 💡 数组的特点 -**数组**是一块连续的内存空间,每个元素大小相同。 + -**优点**: -- 随机访问快:`arr[100]` 直接计算地址,O(1) -- 缓存友好:连续存储,CPU 缓存命中率高 +### 2.1 数组 vs 链表:两种截然不同的存储方式 -**缺点**: -- 插入删除慢:需要移动后面所有元素,O(n) -- 大小固定:需要预先分配空间 +数组和链表是最基础的两种线性结构,它们的核心区别在于**内存布局**: -**生活类比**:一排编号的储物柜,每个柜子大小相同。找第 10 号柜子直接去,但要在中间插入一个柜子,后面的都要往后挪。 +| 对比维度 | 数组 | 链表 | +|---------|------|------| +| **内存布局** | 连续的一整块 | 散落在各处,用指针串起来 | +| **访问第 n 个** | 直接算地址,O(1) | 从头一个个找,O(n) | +| **中间插入** | 后面的都要挪,O(n) | 改两个指针就行,O(1) | +| **大小** | 创建时就固定了 | 随时可以增长 | +| **生活类比** | 一排编号储物柜 | 寻宝游戏的线索链 | + +::: tip 什么时候用数组?什么时候用链表? +- **数据量已知、频繁按位置访问** → 数组(比如学生成绩表、像素矩阵) +- **数据量未知、频繁插入删除** → 链表(比如播放列表、撤销历史) +- **不确定?** → 先用数组。大多数场景下,数组的缓存友好性带来的性能优势更大 ::: -### 1.2 链表:节点相连 +### 2.2 栈和队列:加了"规矩"的线性结构 -::: tip 💡 链表的特点 -**链表**由一系列节点组成,每个节点包含数据和指向下一个节点的指针。 +栈和队列本质上就是数组或链表,只是**限制了操作方式**。看起来功能变少了,但正是这种限制让它们有了明确的用途: -**优点**: -- 插入删除快:只需修改指针,O(1) -- 大小灵活:可以动态增长 +| 结构 | 规则 | 操作 | 类比 | 你写的代码里在哪? | +|------|------|------|------|-----------------| +| **栈** | 后进先出 (LIFO) | push / pop | 一摞盘子 | 函数调用栈、浏览器后退、Ctrl+Z 撤销 | +| **队列** | 先进先出 (FIFO) | enqueue / dequeue | 排队买票 | 任务调度、消息队列、打印队列 | -**缺点**: -- 访问慢:要从头开始遍历,O(n) -- 额外空间:每个节点需要存储指针 - -**生活类比**:寻宝游戏,每个线索指向下一个地点。要找第 10 个线索,必须从第 1 个开始一步步找。 -::: - -### 1.3 栈和队列:受限的线性结构 - -| 结构 | 规则 | 操作 | 类比 | 应用 | -|------|------|------|------|------| -| **栈** | 后进先出 (LIFO) | push/pop | 一摞盘子 | 函数调用、撤销操作 | -| **队列** | 先进先出 (FIFO) | enqueue/dequeue | 排队买票 | 任务调度、消息队列 | - -::: tip 💡 为什么要有"受限"的结构? -栈和队列看起来比数组、链表功能少,但正是这种"限制"让它们有明确的用途: - -- **栈**:函数调用时,最后调用的函数最先返回 -- **队列**:任务调度时,先来的任务先处理 - -限制带来简洁,简洁带来高效。 +::: tip 为什么"限制"反而是好事? +想象一个只有"放盘子"和"拿盘子"两个操作的栈——你永远不会拿错顺序。**限制带来确定性,确定性带来可靠性。** 函数调用栈就是靠"后进先出"保证最后调用的函数最先返回,如果允许随意访问中间的函数,程序就乱套了。 ::: --- -## 2. 哈希表:最快的查找 +## 3. 哈希表:最快的查找 -### 2.1 哈希表原理 +线性结构的查找都不够快——数组要遍历 O(n),即使排好序用二分查找也要 O(log n)。有没有一种结构能做到 **O(1) 直接找到**?有,就是哈希表。 -::: tip 💡 哈希表如何工作? -**哈希表**通过"哈希函数"把键映射到数组索引。 + -**工作流程**: -1. 输入键(如 "apple") -2. 哈希函数计算:`hash("apple") = 3` -3. 直接去数组索引 3 的位置找 +### 3.1 哈希表的核心思想 -**冲突处理**: -- 两个不同的键可能映射到同一索引 -- 解决方法:链地址法(同一位置用链表存储多个值) +哈希表的原理其实很简单: -**生活类比**:图书馆按书名首字母分柜子。"Apple" 开头的书都放 A 柜,"Banana" 开头的放 B 柜。找书时先确定柜子,再在柜子里找。 -::: +1. 你给一个**键**(比如 "apple") +2. **哈希函数**把键算成一个数字(比如 `hash("apple") = 3`) +3. 直接去数组的第 3 个位置找——不用遍历,一步到位 -### 2.2 哈希表的时间复杂度 +这就像图书馆的索引系统:你不用在一排排书架上找,查索引卡片就能直接定位到书的位置。 -| 操作 | 平均情况 | 最坏情况 | -|------|---------|---------| +### 3.2 哈希冲突:两个键撞车了怎么办? + +两个不同的键可能算出同一个索引——这叫**哈希冲突**。就像两本书的索引号相同,都指向同一个位置。 + +| 解决方法 | 原理 | 类比 | +|---------|------|------| +| **链地址法** | 同一位置用链表存多个值 | 同一个柜子里放多本书 | +| **开放寻址法** | 冲突了就往后找空位 | 柜子满了就放隔壁柜子 | + +### 3.3 哈希表的性能 + +| 操作 | 平均情况 | 最坏情况(全部冲突) | +|------|---------|-------------------| | **查找** | O(1) | O(n) | | **插入** | O(1) | O(n) | | **删除** | O(1) | O(n) | -::: warning ⚠️ 什么时候会退化? -当所有键都映射到同一个索引时,哈希表退化为链表,所有操作变成 O(n)。 +::: warning 什么时候会退化? +当所有键都映射到同一个索引时,哈希表退化为链表,所有操作变成 O(n)。避免方法:选择好的哈希函数 + 动态扩容(负载因子超过阈值时扩容)。 +::: -**避免方法**: -- 选择好的哈希函数 -- 动态扩容(负载因子超过阈值时扩容) +::: tip 哈希表在你的代码里无处不在 +- JavaScript 的 `{}` 对象和 `Map` → 哈希表 +- Python 的 `dict` → 哈希表 +- Java 的 `HashMap` → 哈希表 +- 数据库的索引 → 底层也用哈希 + +你每次写 `user["name"]` 或 `map.get("key")`,背后都是哈希表在工作。 ::: --- -## 3. 树:层次结构 +## 4. 树形结构:层级关系的表达 -### 3.1 二叉搜索树 +哈希表查找快,但数据是无序的。如果你需要**既能快速查找,又能保持数据有序**,就需要树形结构了。 + +树的核心特征:每个节点可以有多个"孩子",但只有一个"父亲"(根节点除外)。这种一对多的层级关系,在现实中随处可见。 + + + +### 4.1 二叉搜索树:有序的树 + +二叉搜索树有一个简单但强大的规则:**左小右大**。 -::: tip 💡 二叉搜索树的规则 -**二叉搜索树**是一种特殊的二叉树: - 左子树的所有值 < 根节点 - 右子树的所有值 > 根节点 -**查找过程**: -1. 从根节点开始 -2. 如果目标值 < 当前值,往左走 -3. 如果目标值 > 当前值,往右走 -4. 每次比较排除一半节点 +查找时,每次比较都能排除一半节点,时间复杂度 O(log n)。就像猜数字游戏——"比 50 大还是小?""大。""比 75 大还是小?"——每次排除一半。 -**时间复杂度**:O(log n),但最坏情况(变成链表)是 O(n) -::: +### 4.2 平衡树:防止退化 -### 3.2 平衡树 +二叉搜索树有个问题:如果数据按顺序插入(1, 2, 3, 4, 5),树会退化成一条链,查找变回 O(n)。平衡树通过自动调整结构来避免这个问题: -为了防止二叉搜索树退化,引入了**平衡树**: +| 类型 | 平衡策略 | 特点 | 典型应用 | +|------|---------|------|---------| +| **AVL 树** | 严格平衡(高度差 ≤ 1) | 查找最快,插入删除稍慢 | 需要频繁查找的场景 | +| **红黑树** | 近似平衡 | 综合性能好 | Java TreeMap、Linux 内核 | +| **B 树** | 多路平衡,一个节点存多个值 | 减少磁盘 I/O | 数据库索引 | -| 类型 | 平衡方式 | 特点 | -|------|---------|------| -| **AVL 树** | 严格平衡(高度差 ≤ 1) | 查找最快,插入删除稍慢 | -| **红黑树** | 近似平衡 | 综合性能好,应用最广 | -| **B 树** | 多路平衡 | 适合磁盘存储,数据库索引 | - ---- - -## 4. 如何选择数据结构? - -| 需求 | 推荐结构 | 原因 | -|------|---------|------| -| **快速随机访问** | 数组 | O(1) 索引访问 | -| **频繁插入删除** | 链表 | O(1) 插入删除 | -| **快速查找** | 哈希表 | O(1) 平均查找 | -| **有序数据** | 平衡树 | O(log n) 查找,保持有序 | -| **最近使用** | 栈 | LIFO 特性 | -| **任务排队** | 队列 | FIFO 特性 | - -::: tip 💡 选择数据结构的心法 -**没有最好的数据结构,只有最合适的数据结构。** - -选择时要考虑: -1. **主要操作是什么?** 查找?插入?删除? -2. **数据量多大?** 小数据量差别不大,大数据量要慎重 -3. **数据有序吗?** 有序数据可以用二分查找 -4. **内存限制?** 某些结构需要额外空间 +::: tip 树在你的代码里在哪? +- **文件系统**:文件夹嵌套就是树结构 +- **HTML DOM**:`` → `` → `
` → `

` 就是一棵树 +- **数据库索引**:B+ 树让百万级数据的查找只需要 3-4 次磁盘读取 +- **JSON/XML**:嵌套的数据格式本质上就是树 ::: --- -## 5. 总结:数据结构是程序的基础 +## 5. 图结构:复杂关系的网络 -让我们用一个比喻总结各种数据结构: +树只能表示"一对多"的层级关系。但现实中很多关系是"多对多"的——你的朋友也有朋友,城市之间有多条路可以走。这种**任意节点之间都可能有连接**的结构,就是图。 -| 结构 | 比喻 | 核心特点 | -|------|------|---------| -| **数组** | 编号储物柜 | 访问快,插入慢 | -| **链表** | 寻宝线索 | 插入快,访问慢 | -| **栈** | 一摞盘子 | 后进先出 | -| **队列** | 排队队伍 | 先进先出 | -| **哈希表** | 分类柜子 | 查找最快 | -| **树** | 家族族谱 | 层次结构 | + -::: tip 💡 核心启示 -**数据结构决定了程序的效率上限。** +### 5.1 图的三种形态 -- 选对数据结构,问题迎刃而解 -- 选错数据结构,再好的算法也无济于事 +| 类型 | 特点 | 类比 | 典型应用 | +|------|------|------|---------| +| **无向图** | 边没有方向,A→B 等于 B→A | 微信好友(互相的) | 社交网络、通信网络 | +| **有向图** | 边有方向,A→B 不等于 B→A | 微博关注(单向的) | 网页链接、依赖关系 | +| **带权图** | 边有权重(距离、费用等) | 城市间的公路(有里程数) | 地图导航、最短路径 | -理解数据结构,就是理解"如何高效地组织数据"。这是每个程序员的基本功。 +### 5.2 图的遍历 + +图的遍历比线性结构复杂,因为可能有环(A→B→C→A),需要记录"已访问"的节点: + +| 遍历方式 | 策略 | 类比 | 适用场景 | +|---------|------|------|---------| +| **BFS(广度优先)** | 先访问所有邻居,再访问邻居的邻居 | 水波纹扩散 | 最短路径、层级遍历 | +| **DFS(深度优先)** | 一条路走到底,走不通再回头 | 走迷宫 | 路径搜索、连通性判断 | + +::: tip 图在现实中的应用 +- **地图导航**:城市是节点,道路是边,导航就是在图上找最短路径 +- **社交网络**:用户是节点,关注/好友是边,"你可能认识的人"就是图算法推荐的 +- **包管理器**:npm/pip 的依赖关系就是有向图,`npm install` 就是在做图的拓扑排序 ::: --- +## 6. 性能对比:一张表看清所有数据结构 + +学了这么多数据结构,它们的性能到底差多少?下面这个交互式对比能帮你建立直觉: + + + +**核心性能对比表:** + +| 数据结构 | 访问 | 查找 | 插入 | 删除 | 空间 | +|---------|------|------|------|------|------| +| **数组** | O(1) | O(n) | O(n) | O(n) | O(n) | +| **链表** | O(n) | O(n) | O(1) | O(1) | O(n) | +| **栈/队列** | O(n) | O(n) | O(1) | O(1) | O(n) | +| **哈希表** | — | O(1) | O(1) | O(1) | O(n) | +| **二叉搜索树** | — | O(log n) | O(log n) | O(log n) | O(n) | +| **图** | — | O(V+E) | O(1) | O(E) | O(V+E) | + +::: tip 怎么读这张表? +- **O(1)**:不管数据量多大,操作时间恒定——最快 +- **O(log n)**:数据量翻倍,时间只多一步——很快 +- **O(n)**:数据量翻倍,时间也翻倍——一般 +- **O(V+E)**:取决于节点数和边数——图的特殊表示 + +注意:这些都是**平均情况**。最坏情况下,哈希表会退化到 O(n),二叉搜索树也会退化到 O(n)。 +::: + +--- + +## 7. 选型指南:该用哪种数据结构? + +学了这么多数据结构,面对实际需求时该怎么选?关键是**从需求出发**,问自己几个问题: + +1. **最频繁的操作是什么?** 查找?插入?删除?遍历? +2. **数据之间有什么关系?** 一对一?一对多?多对多? +3. **数据量有多大?** 几十条和几百万条的最优选择可能完全不同 +4. **需要有序吗?** 是否需要按某种顺序遍历数据 + + + +**快速决策流程:** + +| 你的需求 | 推荐结构 | 原因 | +|---------|---------|------| +| 按位置快速访问 | 数组 | O(1) 随机访问 | +| 频繁在中间插入删除 | 链表 | O(1) 插入删除,不用移动元素 | +| 后进先出(撤销、递归) | 栈 | LIFO 语义天然匹配 | +| 先进先出(任务队列) | 队列 | FIFO 语义天然匹配 | +| 按键快速查找 | 哈希表 | O(1) 平均查找 | +| 有序数据 + 快速查找 | 二叉搜索树 | O(log n) 查找且保持有序 | +| 复杂多对多关系 | 图 | 能表达任意节点间的连接 | + +::: tip 实际开发中的经验法则 +- **80% 的场景**用数组和哈希表就够了 +- **需要有序**时考虑树 +- **关系复杂**时考虑图 +- **不确定?** 先用最简单的,遇到性能问题再换。过早优化是万恶之源 +::: + +--- + +## 总结 + +> 数据结构是程序的骨架。**数组**像一排编号储物柜,按位置取东西最快;**链表**像寻宝线索链,插入删除最灵活;**哈希表**像图书馆索引,按名字找东西最快;**树**像家族族谱,表达层级关系且保持有序;**图**像地铁线路图,表达任意复杂的网状关系。没有最好的数据结构,只有最合适的——关键是理解每种结构的优势和代价,根据实际需求做出权衡。 + +--- + ## 延伸阅读 -- **数据结构实现**:自己动手实现各种数据结构,加深理解 -- **高级数据结构**:学习跳表、布隆过滤器、并查集等 -- **数据库索引**:了解 B+ 树在数据库中的应用 -- **缓存设计**:学习 LRU 缓存如何结合哈希表和链表 +| 主题 | 推荐资源 | +|------|---------| +| 数据结构可视化 | [VisuAlgo](https://visualgo.net/) - 动画演示各种数据结构和算法 | +| 算法与数据结构 | 《算法图解》- Aditya Bhargava,图文并茂适合入门 | +| 深入理解 | 《数据结构与算法分析》- Mark Allen Weiss | +| 刷题练习 | [LeetCode](https://leetcode.cn/) - 按数据结构分类练习 | + +--- + +## 下一步 + +现在你已经掌握了数据结构的核心知识。接下来可以继续学习: + +- **[算法思维](./algorithm-thinking.md)**:学会用排序、搜索、递归、动态规划等算法思维解决问题 +- **[编程语言](./programming-languages.md)**:了解不同编程语言如何实现这些数据结构 diff --git a/docs/zh-cn/appendix/1-computer-fundamentals/power-on-to-web.md b/docs/zh-cn/appendix/1-computer-fundamentals/power-on-to-web.md index d215cec..3884157 100644 --- a/docs/zh-cn/appendix/1-computer-fundamentals/power-on-to-web.md +++ b/docs/zh-cn/appendix/1-computer-fundamentals/power-on-to-web.md @@ -52,47 +52,7 @@ CPU 接收到复位信号后,把内部所有寄存器和缓存清零,从一 ## 2. BIOS/UEFI:硬件的自检 -### 2.1 什么是 BIOS/UEFI? - -**BIOS(Basic Input/Output System)** 是电脑启动后第一个运行的程序,存储在主板的一个**只读芯片**中。 - -**UEFI(Unified Extensible Firmware Interface)** 是 BIOS 的升级版,更安全、更现代。现在的电脑大多使用 UEFI。 - -### 2.2 BIOS/UEFI 做了什么? - -1. **硬件自检(POST)**:检查内存、显卡、键盘等部件是否正常 -2. **初始化硬件**:设置硬件工作模式 -3. **启动顺序**:按照设定顺序,尝试从硬盘/U 盘/网络启动 - -``` -BIOS/UEFI 工作流程: -┌─────────────────────────────────────┐ -│ 1. 硬件自检 (POST) │ -│ - 检查内存是否正常 │ -│ - 检查显卡是否正常 │ -│ - 检查键盘/鼠标是否正常 │ -├─────────────────────────────────────┤ -│ 2. 初始化硬件 │ -│ - 设置硬件工作模式 │ -│ - 配置中断向量表 │ -├─────────────────────────────────────┤ -│ 3. 寻找启动设备 │ -│ - 按启动顺序查找可启动设备 │ -│ - 读取启动扇区 │ -└─────────────────────────────────────┘ -``` - -如果发现问题,主板会发出**蜂鸣声**(不同次数代表不同错误)。 - -### 2.3 启动顺序 - -BIOS/UEFI 会按照设定的**启动顺序**查找启动设备: - -1. 硬盘(最常见) -2. U 盘/光盘(重装系统时用) -3. 网络( PXE 启动,企业批量部署用) - -找到第一个可启动设备后,读取它的**启动扇区(Boot Sector)**,把控制权交给操作系统。 + --- @@ -104,94 +64,7 @@ BIOS/UEFI 会按照设定的**启动顺序**查找启动设备: ## 3. 操作系统启动:从内核到桌面 -### 3.1 什么是操作系统? - -**操作系统(Operating System,简称 OS)** 是管理计算机硬件和软件资源的程序集合。它就像一个"大管家",帮我们管理内存、CPU、文件等资源,让我们不需要直接和硬件打交道。 - -常见的操作系统: - -| 操作系统 | 特点 | 典型设备 | -|---------|------|---------| -| **Windows** | 生态丰富,兼容性好 | 桌面电脑、笔记本 | -| **macOS** | 苹果生态,流畅稳定 | Mac 电脑 | -| **Linux** | 开源免费,服务器首选 | 服务器、嵌入式设备 | -| **Android** | 移动端 Linux | 手机、平板 | -| **iOS** | 苹果移动端 | iPhone、iPad | - -### 3.2 操作系统的启动过程 - -当你从硬盘启动时,操作系统的启动过程如下: - - - -#### 第一步:引导程序(Bootloader) - -硬盘的第一个扇区存放着**引导程序(Bootloader)**,它的任务是把操作系统内核加载到内存中。 - -- **Windows**:Bootloader 叫 `bootmgr` -- **Linux**:常见的引导程序有 `GRUB`、`rEFInd` 等 - -``` -引导程序工作流程: -┌─────────────────────────────────────┐ -│ 1. 读取硬盘分区表 │ -│ 2. 找到操作系统分区 │ -│ 3. 加载操作系统内核到内存 │ -│ 4. 跳转到内核入口点 │ -└─────────────────────────────────────┘ -``` - -#### 第二步:内核加载(Kernel) - -操作系统**内核(Kernel)** 是操作系统的核心,负责管理内存、CPU、进程等核心功能。 - -``` -内核的主要功能: -┌─────────────────────────────────────┐ -│ • 进程管理 - 创建/调度进程 │ -│ • 内存管理 - 分配/回收内存 │ -│ • 文件系统 - 管理文件存储 │ -│ • 设备驱动 - 控制硬件设备 │ -│ • 网络通信 - 处理网络协议 │ -└─────────────────────────────────────┘ -``` - -#### 第三步:系统服务启动 - -内核加载后,会启动各种**系统服务**: - -- **Windows 服务**:更新服务、安全中心、打印机服务 -- **Linux 服务**:SSH 服务、网络服务、图形界面(GNOME、KDE) - -``` -Windows 启动过程: -BIOS → MBR → bootmgr → winload.exe → ntoskrnl.exe → 系统服务 → 桌面 - -Linux 启动过程: -BIOS → GRUB → vmlinuz (内核) → systemd → 系统服务 → 桌面环境 -``` - -#### 第四步:显示桌面 - -最后,操作系统启动**图形界面(GUI)**,显示桌面: - -- **Windows**:explorer.exe(资源管理器)显示桌面 -- **Linux**:GNOME、KDE、XFCE 等桌面环境 -- **macOS**:Finder 显示桌面 - -``` -桌面出现的过程: -┌─────────────────────────────────────┐ -│ 1. 显卡驱动加载 │ -│ 2. 显示服务器启动 │ -│ (Windows: Desktop Window Manager)│ -│ (Linux: X Server / Wayland) │ -│ 3. 桌面环境启动 │ -│ 4. 显示桌面背景和图标 │ -└─────────────────────────────────────┘ -``` - - + --- diff --git a/docs/zh-cn/appendix/1-computer-fundamentals/programming-languages.md b/docs/zh-cn/appendix/1-computer-fundamentals/programming-languages.md index a93b281..cc19c33 100644 --- a/docs/zh-cn/appendix/1-computer-fundamentals/programming-languages.md +++ b/docs/zh-cn/appendix/1-computer-fundamentals/programming-languages.md @@ -99,7 +99,7 @@ SELECT name FROM users WHERE active = true - **JavaScript(弱类型)**:`"11"` — 悄悄帮你转了 - **Python(强类型)**:`TypeError` — 让你自己想清楚 -想深入了解类型系统?→ [类型系统与编译原理入门](./type-systems-compilers) +想深入了解类型系统?→ [类型系统入门](./type-systems) | [编译原理入门](./compilers) --- @@ -147,6 +147,7 @@ SELECT name FROM users WHERE active = true ::: **下一步学习**: -- [类型系统与编译原理入门](./type-systems-compilers) - 深入理解类型系统和编译过程 +- [编译原理入门](./compilers) - 深入理解编译过程和代码优化 +- [类型系统入门](./type-systems) - 深入理解类型系统和类型安全 - [数据结构](./data-structures) - 理解数据的组织方式 - [算法思维入门](./algorithm-thinking) - 学习解决问题的方法 diff --git a/docs/zh-cn/appendix/2-development-tools/debugging-art.md b/docs/zh-cn/appendix/2-development-tools/debugging-art.md index 415935d..e08db22 100644 --- a/docs/zh-cn/appendix/2-development-tools/debugging-art.md +++ b/docs/zh-cn/appendix/2-development-tools/debugging-art.md @@ -1,2 +1,432 @@ # 调试的艺术 -> 待实现 + +::: tip 前言 +**代码写完了,运行报错——然后呢?** 很多新手在这一步就卡住了,盯着屏幕不知所措。调试(Debug)是编程中最核心的技能之一,甚至比写代码本身更重要。因为写代码只占开发时间的 30%,剩下的 70% 都在理解问题、定位 Bug、验证修复。 +::: + +**这篇文章会带你学什么?** + +学完这章后,你将获得: + +- **调试思维**:建立系统化的问题定位方法,不再"瞎猜" +- **错误阅读能力**:看懂报错信息,从错误堆栈中快速定位问题 +- **常用调试方法**:掌握二分法、橡皮鸭、最小复现等经典调试技巧 +- **工具使用能力**:了解断点调试、日志调试、网络调试等工具的使用场景 +- **AI 辅助调试**:学会用 AI 加速调试过程,但不依赖 AI + +| 章节 | 内容 | 核心概念 | +|-----|------|---------| +| **第 1 章** | 读懂错误信息 | 错误类型、堆栈追踪 | +| **第 2 章** | 经典调试方法 | 二分法、橡皮鸭、最小复现 | +| **第 3 章** | 调试工具箱 | 断点、日志、网络抓包 | +| **第 4 章** | AI 时代的调试 | AI 辅助 + 人工判断 | +| **第 5 章** | 调试心态与习惯 | 防御性编程、调试日志 | + +--- + +## 0. 全景图:调试是一种科学方法 + +调试不是"碰运气",而是一个严谨的科学过程。物理学家做实验的方法论,完全适用于调试: + +1. **观察现象**:程序出了什么问题?报了什么错? +2. **提出假设**:可能是什么原因导致的? +3. **设计实验**:怎么验证这个假设? +4. **验证结论**:假设对了就修复,错了就换一个假设 + +::: tip 调试的黄金法则 +- **先复现,再修复**:不能稳定复现的 Bug,修了也不知道是不是真的修好了 +- **一次只改一个变量**:同时改多处,就不知道是哪个改动解决了问题 +- **相信证据,不相信直觉**:你觉得"不可能是这里的问题",往往就是这里的问题 +- **最近改了什么?**:80% 的 Bug 都是最近的改动引入的 +::: + +--- + +## 1. 读懂错误信息:报错不是敌人,是线索 + +新手最常犯的错误:看到报错就慌,直接关掉或者忽略。其实,**错误信息是程序在告诉你哪里出了问题**——它是你最好的朋友。 + +### 1.1 错误的三大类型 + +| 类型 | 什么时候出现 | 举例 | 严重程度 | +|-----|------------|------|---------| +| **语法错误** | 代码还没运行就报错 | 少了括号、拼错关键字 | 最容易修 | +| **运行时错误** | 代码运行到某一行崩溃 | 访问不存在的变量、除以零 | 中等难度 | +| **逻辑错误** | 代码能运行,但结果不对 | 计算公式写错、条件判断反了 | 最难发现 | + +### 1.2 如何阅读错误堆栈 + +以 JavaScript 为例,一个典型的错误信息: + +``` +TypeError: Cannot read properties of undefined (reading 'name') + at getUserName (app.js:15:23) + at handleClick (app.js:42:10) + at HTMLButtonElement. (app.js:58:5) +``` + +**从上往下读**: + +1. **第一行**:错误类型 + 错误描述 → `TypeError`,试图读取 `undefined` 的 `name` 属性 +2. **第二行**:出错的函数和位置 → `getUserName` 函数,`app.js` 第 15 行第 23 列 +3. **后续行**:调用链 → 谁调用了这个函数?`handleClick` → 按钮点击事件 + +::: tip 阅读堆栈的口诀 +**从上往下找原因,从下往上找源头。** 第一行告诉你"出了什么错",最后一行告诉你"从哪里开始的"。 +::: + +### 1.3 常见错误类型速查 + +| 错误名称 | 含义 | 常见原因 | +|---------|------|---------| +| `SyntaxError` | 语法错误 | 括号不匹配、少了逗号 | +| `TypeError` | 类型错误 | 对 `undefined`/`null` 做操作 | +| `ReferenceError` | 引用错误 | 使用了未声明的变量 | +| `RangeError` | 范围错误 | 数组越界、递归太深 | +| `NetworkError` | 网络错误 | API 请求失败、跨域问题 | +| `404 Not Found` | 资源不存在 | URL 写错、文件被删除 | +| `500 Internal Server Error` | 服务器内部错误 | 后端代码崩溃 | + +### 1.4 Python 错误信息对比 + +Python 的堆栈和 JavaScript 相反——**从下往上读**: + +```python +Traceback (most recent call last): + File "main.py", line 10, in + result = calculate(data) + File "main.py", line 5, in calculate + return data["price"] * data["quantity"] +KeyError: 'quantity' +``` + +**最后一行**才是错误原因:`KeyError: 'quantity'`,字典里没有 `quantity` 这个键。 + +::: tip 不同语言,同一个思路 +不管什么语言,错误信息都包含三个关键信息:**什么错**(错误类型)、**哪里错**(文件和行号)、**为什么错**(错误描述)。学会提取这三个信息,就能读懂任何语言的报错。 +::: + +--- + +## 2. 经典调试方法:前人总结的智慧 + +这些方法不需要任何工具,只需要你的大脑。它们是所有高级调试技巧的基础。 + +### 2.1 二分法调试 + +**核心思想**:把问题范围缩小一半,再缩小一半,直到找到根源。 + +**场景**:代码很长,不知道哪一段出了问题。 + +**步骤**: + +1. 在代码中间加一个 `console.log`(或 `print`) +2. 如果中间点之前就出错了 → 问题在上半部分 +3. 如果中间点之后才出错 → 问题在下半部分 +4. 对出错的那一半,重复上述步骤 + +``` +100 行代码出了 Bug + ↓ 在第 50 行加 log +问题在 50-100 行 + ↓ 在第 75 行加 log +问题在 50-75 行 + ↓ 在第 62 行加 log +问题在第 60-62 行! +``` + +::: tip 二分法的威力 +100 行代码,最多只需要 7 次(log₂100 ≈ 7)就能定位到具体行。1000 行也只需要 10 次。 +::: + +### 2.2 橡皮鸭调试法 + +**核心思想**:把问题一行一行地"讲"给别人听(或者一只橡皮鸭),讲着讲着你自己就发现问题了。 + +**为什么有效?** 因为"写代码"和"解释代码"用的是大脑的不同区域。当你被迫用语言描述每一步逻辑时,那些你"以为对了"的假设会暴露出来。 + +**实践方法**: + +1. 打开出问题的代码 +2. 逐行解释:"这一行做了什么?为什么要这么做?" +3. 当你说出"嗯,这里应该是……等等"的时候,Bug 往往就在那里 + +### 2.3 最小复现 + +**核心思想**:把复杂的问题简化到最小,只保留能触发 Bug 的最少代码。 + +**为什么重要?** + +- 复杂系统中,Bug 可能被其他代码"掩盖" +- 最小复现能排除干扰因素,让问题一目了然 +- 也方便你向别人求助——没人愿意看你 500 行代码 + +**步骤**: + +1. 创建一个新的空文件 +2. 只复制和问题相关的代码 +3. 逐步删减,直到删掉任何一行 Bug 就消失 +4. 剩下的就是 Bug 的根源 + +### 2.4 回退法(Git Bisect) + +**核心思想**:如果代码"之前是好的,现在坏了",那就找到是哪次提交引入的问题。 + +```bash +# Git 自带的二分查找工具 +git bisect start +git bisect bad # 标记当前版本有 Bug +git bisect good abc123 # 标记某个正常的旧版本 +# Git 会自动切换到中间的提交,你测试后告诉它 good 或 bad +# 重复几次就能找到引入 Bug 的那次提交 +``` + +::: tip 调试方法选择指南 +| 情况 | 推荐方法 | +|-----|---------| +| 不知道哪一段代码出错 | 二分法 | +| 逻辑看起来对但结果不对 | 橡皮鸭 | +| 复杂系统中的 Bug | 最小复现 | +| "之前好好的突然坏了" | 回退法 / Git Bisect | +::: + +--- + +## 3. 调试工具箱:用对工具事半功倍 + +方法论是基础,但好的工具能让调试效率翻倍。 + +### 3.1 console.log / print:最朴素也最实用 + +**适用场景**:快速查看变量值、确认代码执行到了哪里。 + +```javascript +// JavaScript +console.log('函数被调用了,参数是:', data) +console.log('计算结果:', result) +console.table(arrayData) // 表格形式展示数组/对象 +``` + +```python +# Python +print(f"当前值: {value}") +print(f"类型: {type(data)}") # 检查数据类型 +``` + +**进阶技巧**: + +| 方法 | 用途 | +|-----|------| +| `console.log()` | 普通输出 | +| `console.warn()` | 黄色警告,容易在大量日志中找到 | +| `console.error()` | 红色错误 | +| `console.table()` | 表格展示数组和对象 | +| `console.time()` / `console.timeEnd()` | 测量代码执行时间 | +| `console.trace()` | 打印调用堆栈 | + +### 3.2 断点调试:逐行执行,看清每一步 + +**适用场景**:逻辑复杂,需要一步步跟踪代码执行过程。 + +**在浏览器中**(Chrome DevTools): + +1. 打开开发者工具(F12)→ Sources 面板 +2. 找到源代码文件,点击行号设置断点 +3. 触发相关操作,代码会在断点处暂停 +4. 用控制按钮逐步执行: + - **继续**(F8):运行到下一个断点 + - **单步跳过**(F10):执行当前行,不进入函数内部 + - **单步进入**(F11):进入函数内部 + - **单步跳出**(Shift+F11):跳出当前函数 + +**在 VS Code 中**: + +1. 点击行号左侧设置断点(红色圆点) +2. 按 F5 启动调试 +3. 在"变量"面板查看所有变量的当前值 +4. 在"监视"面板添加你关心的表达式 + +::: tip 断点 vs console.log +**console.log** 适合快速验证,用完就删。**断点调试**适合深入分析复杂逻辑。两者不是替代关系,而是互补关系。 +::: + +### 3.3 网络调试:前后端之间的问题 + +**适用场景**:页面显示不对,但不确定是前端的问题还是后端返回的数据有问题。 + +**Chrome DevTools → Network 面板**: + +| 查看内容 | 能发现什么问题 | +|---------|--------------| +| **状态码** | 404(地址错)、500(服务器崩了)、403(没权限) | +| **请求参数** | 前端发送的数据对不对 | +| **响应数据** | 后端返回的数据格式对不对 | +| **请求时间** | 哪个接口太慢,拖慢了页面 | +| **请求头** | Token 有没有带、Content-Type 对不对 | + +**调试口诀**:先看状态码,再看请求参数,最后看响应数据。 + +### 3.4 调试工具选择速查 + +| 问题类型 | 推荐工具 | +|---------|---------| +| 变量值不对 | console.log / 断点 | +| 逻辑执行顺序不对 | 断点调试 | +| API 请求失败 | Network 面板 | +| 页面样式不对 | Elements 面板(检查 CSS) | +| 性能问题 | Performance 面板 / console.time | +| 内存泄漏 | Memory 面板 | + +--- + +## 4. AI 时代的调试:让 AI 当你的助手 + +AI 工具(ChatGPT、Claude、Cursor 等)能大幅加速调试过程,但前提是你得知道怎么用。 + +### 4.1 AI 擅长什么? + +| AI 擅长 | AI 不擅长 | +|--------|----------| +| 解释错误信息的含义 | 理解你的业务逻辑 | +| 提供常见问题的解决方案 | 判断哪个方案最适合你的项目 | +| 生成调试代码片段 | 复现只在特定环境出现的 Bug | +| 分析代码中的潜在问题 | 理解复杂的系统上下文 | + +### 4.2 向 AI 提问的正确姿势 + +**差的提问**: +> "我的代码报错了,帮我看看" + +**好的提问**: +> "我在用 React 写一个表单组件,提交时报错 `TypeError: Cannot read properties of undefined (reading 'email')`。以下是相关代码:[贴代码]。我已经确认 API 返回的数据格式是正确的,问题可能出在前端数据处理。" + +**提问模板**: + +``` +1. 我在做什么:[背景] +2. 期望的行为:[应该怎样] +3. 实际的行为:[实际怎样] +4. 错误信息:[完整报错] +5. 相关代码:[贴代码] +6. 我已经尝试了:[排除了什么] +``` + +### 4.3 AI 调试的陷阱 + +::: warning AI 调试的三个坑 +1. **AI 可能"自信地胡说"**:AI 给的方案看起来很合理,但可能完全不对。永远要自己验证。 +2. **AI 不了解你的上下文**:它不知道你的项目结构、依赖版本、运行环境。你需要提供足够的上下文。 +3. **过度依赖 AI 会退化调试能力**:如果每次报错都直接丢给 AI,你永远学不会自己调试。建议先自己分析 5 分钟,再求助 AI。 +::: + +### 4.4 AI + 人工的最佳组合 + +``` +遇到 Bug + ↓ +第 1 步:自己读错误信息(1 分钟) + ↓ +第 2 步:自己提出假设(2 分钟) + ↓ +第 3 步:快速验证假设(2 分钟) + ↓ +卡住了?→ 把错误信息 + 代码 + 你的分析发给 AI + ↓ +AI 给出建议 → 你判断是否合理 → 验证 +``` + +--- + +## 5. 调试心态与习惯:从"救火"到"防火" + +最好的调试是不需要调试。养成好习惯,能从源头减少 Bug。 + +### 5.1 防御性编程 + +**核心思想**:写代码时就假设"一切都可能出错",提前做好防护。 + +```javascript +// 差:假设 data 一定存在 +const name = data.user.name + +// 好:防御性写法 +const name = data?.user?.name ?? '未知用户' +``` + +```python +# 差:假设文件一定能打开 +content = open('config.json').read() + +# 好:防御性写法 +try: + content = open('config.json').read() +except FileNotFoundError: + print("配置文件不存在,使用默认配置") + content = '{}' +``` + +### 5.2 写好日志 + +日志是"事后调试"的关键。线上环境不能打断点,只能靠日志。 + +| 日志级别 | 用途 | 举例 | +|---------|------|------| +| **DEBUG** | 开发时的详细信息 | 变量值、函数参数 | +| **INFO** | 正常的业务流程 | "用户登录成功"、"订单创建" | +| **WARN** | 不影响功能但需要注意 | "缓存未命中"、"重试第 2 次" | +| **ERROR** | 出错了,需要处理 | "数据库连接失败"、"API 超时" | + +::: tip 好日志的标准 +一条好的日志应该回答:**什么时候**、**在哪里**、**发生了什么**、**关键数据是什么**。 +``` +[2025-01-15 14:30:22] [ERROR] [OrderService] 创建订单失败 + 用户ID: 12345, 商品ID: 67890, 原因: 库存不足 +``` +::: + +### 5.3 调试检查清单 + +遇到 Bug 时,按这个顺序排查: + +1. **读错误信息**:错误类型、文件、行号 +2. **最近改了什么?**:用 `git diff` 看最近的改动 +3. **能复现吗?**:找到稳定的复现步骤 +4. **缩小范围**:用二分法或最小复现定位 +5. **提出假设并验证**:一次只改一个变量 +6. **修复后回归测试**:确保修复没有引入新问题 + +### 5.4 新手常踩的调试陷阱 + +| 陷阱 | 正确做法 | +|-----|---------| +| 不看报错就开始改代码 | 先完整阅读错误信息 | +| 同时改好几个地方 | 一次只改一处,验证后再改下一处 | +| 改完不测试就提交 | 每次修改后都运行测试 | +| 只在自己电脑上测试 | 考虑不同环境(浏览器、系统、网络) | +| 调试完不清理 console.log | 提交前删除所有调试代码 | +| 遇到问题就重启/重装 | 先理解问题原因,重启只是临时方案 | + +--- + +## 6. 总结 + +调试是一门手艺,需要刻意练习。回顾本章的核心要点: + +1. **调试是科学方法**:观察 → 假设 → 实验 → 验证,不是碰运气 +2. **错误信息是朋友**:学会从报错中提取"什么错、哪里错、为什么错" +3. **经典方法永不过时**:二分法、橡皮鸭、最小复现是所有调试的基础 +4. **工具要用对场景**:console.log 快速验证,断点深入分析,Network 排查接口 +5. **AI 是助手不是拐杖**:先自己分析,再让 AI 辅助,最后自己验证 +6. **防火胜于救火**:防御性编程、好的日志习惯能从源头减少 Bug + +::: tip 记住这句话 +**每个 Bug 都是一次学习机会。** 你修过的每一个 Bug,都在帮你建立"模式识别"能力——下次遇到类似问题,你会更快地定位到原因。 +::: + +--- + +## 延伸阅读 + +- [Chrome DevTools 官方文档](https://developer.chrome.com/docs/devtools/) — 浏览器调试工具的完整指南 +- [VS Code Debugging](https://code.visualstudio.com/docs/editor/debugging) — VS Code 断点调试教程 +- [How to Debug Anything](https://www.debuggingbook.org/) — 系统化调试方法论 diff --git a/docs/zh-cn/appendix/3-browser-and-frontend/frontend-project-architecture.md b/docs/zh-cn/appendix/3-browser-and-frontend/frontend-project-architecture.md new file mode 100644 index 0000000..1e134ad --- /dev/null +++ b/docs/zh-cn/appendix/3-browser-and-frontend/frontend-project-architecture.md @@ -0,0 +1,648 @@ +# 前端项目架构设计 + +::: tip 🎯 核心问题 +**文件越放越乱,代码越写越难找,如何设计一个清晰、可维护的前端项目结构?** 这就像问:你是把所有衣服都扔进一个箱子,还是按季节、类型、颜色分类整理?好的项目架构能让团队协作更高效,让代码维护更轻松。 +::: + +--- + +## 1. 为什么要关注项目架构? + +### 1.1 从小项目到大项目的演变 + +很多初学者刚开始写前端时,项目结构非常简单: + +``` +my-project/ +├── index.html +├── style.css +└── app.js +``` + +三个文件搞定一切,简单直接。但随着项目增长,问题开始出现: + +- **页面多了**:`page1.html`, `page2.html`... 文件散落在根目录 +- **组件多了**:按钮、弹窗、表单各自为政,复用困难 +- **工具函数多了**:到处复制粘贴,改一个地方要改十处 +- **样式冲突了**:全局 CSS 互相覆盖,调试困难 + +**问题的本质**:没有"章法",文件随意存放,就像把春夏秋冬的衣服都扔进一个箱子。 + +### 1.2 好的架构像整理好的衣柜 + +想象一个整理好的衣柜: + +| 区域 | 存放物品 | 特点 | +|------|----------|------| +| **挂衣区** | 外套、衬衫 | 常穿,方便取用 | +| **抽屉区** | 内衣、袜子 | 分类摆放,整齐 | +| **隔板区** | 毛衣、裤子 | 叠放,节省空间 | +| **顶层区** | 换季衣物 | 不常用,收纳起来 | + +**好的项目架构**就是把代码也这样组织:每一类文件有自己的"位置",团队成员都知道该去哪找、该往哪放。 + +::: tip 💡 通俗比喻:餐厅后厨的组织 +把前端项目想象成一家餐厅的后厨: + +- **`src/pages/`(页面区)** = 出餐口:每个订单对应一个成品菜 +- **`src/components/`(组件区)** = 备料台:切好的蔬菜、调好的酱料,随时可用 +- **`src/utils/`(工具区)** = 工具柜:刀、勺、温度计等通用工具 +- **`src/assets/`(食材区)** = 冷藏库:图片、字体、样式等原材料 +- **`src/services/`(服务层)** = 传菜窗口:与外部(服务员/后端)交互 + +**关键点**:每个区域职责明确,不会混乱。你不会在冷藏库里切菜,也不会把刀具扔进汤锅。 +::: + +--- + +## 2. 经典目录结构解析 + +### 2.1 标准目录结构(以 Vue/React 为例) + +一个中大型前端项目的典型结构如下: + +``` +my-frontend-project/ +├── public/ # 静态资源(不经过构建) +│ ├── favicon.ico +│ ├── index.html +│ └── robots.txt +├── src/ +│ ├── assets/ # 项目资源(会被构建工具处理) +│ │ ├── images/ +│ │ ├── fonts/ +│ │ └── styles/ +│ │ ├── variables.scss # 变量定义 +│ │ ├── mixins.scss # 混入样式 +│ │ └── global.css # 全局样式 +│ ├── components/ # 通用组件 +│ │ ├── common/ # 全局通用组件 +│ │ │ ├── Button/ +│ │ │ │ ├── index.vue +│ │ │ │ ├── Button.scss +│ │ │ │ └── Button.test.js +│ │ │ ├── Modal/ +│ │ │ └── Loading/ +│ │ └── business/ # 业务组件 +│ │ ├── UserCard/ +│ │ └── ProductList/ +│ ├── views/ 或 pages/ # 页面组件 +│ │ ├── Home/ +│ │ ├── About/ +│ │ └── User/ +│ │ ├── Profile/ +│ │ └── Settings/ +│ ├── router/ 或 navigation/ # 路由配置 +│ │ └── index.js +│ ├── stores/ 或 state/ # 状态管理 +│ │ ├── user.js +│ │ └── app.js +│ ├── services/ 或 api/ # API 服务 +│ │ ├── user.js +│ │ └── product.js +│ ├── utils/ 或 helpers/ # 工具函数 +│ │ ├── request.js # 请求封装 +│ │ ├── storage.js # 本地存储 +│ │ └── format.js # 格式化工具 +│ ├── hooks/ 或 composables/ # 组合式函数 +│ │ ├── useAuth.js +│ │ └── useLoading.js +│ ├── directives/ # 自定义指令 +│ ├── plugins/ # 插件配置 +│ ├── constants/ # 常量定义 +│ ├── types/ 或 @types/ # TypeScript 类型 +│ └── App.vue 或 App.jsx # 根组件 +│ └── main.js 或 main.ts # 入口文件 +├── tests/ # 测试文件 +│ ├── unit/ +│ └── e2e/ +├── .env # 环境变量 +├── .env.development +├── .env.production +├── vite.config.js # 构建配置 +├── package.json +└── README.md +``` + +::: tip 📊 从图解中你能看到什么? +**分层逻辑**: + +- **`public/` vs `src/assets/`**:前者直接复制到输出目录,后者会被构建工具处理(压缩、转译、添加哈希值) +- **`components/` vs `views/`**:组件是"零件",页面是"成品"。一个页面由多个组件组装而成 +- **`services/` 独立出来**:把 API 调用集中管理,方便统一处理错误、加载状态、请求拦截 + +**依赖方向**: + +``` +views/pages → components → utils/hooks + ↓ +services → stores +``` + +上层可以调用下层,但下层不应该依赖上层。 +::: + +### 2.2 按功能组织 vs 按类型组织 + +项目结构有两种主流的组织方式: + +#### 方式一:按类型组织(Type-based) + +``` +src/ +├── components/ +│ ├── Button.vue +│ ├── Modal.vue +│ └── Card.vue +├── views/ +│ ├── Home.vue +│ ├── User.vue +│ └── Product.vue +├── stores/ +│ ├── user.js +│ └── product.js +└── services/ + ├── user.js + └── product.js +``` + +**优点**: +- 结构清晰,同类文件在一起 +- 适合小型项目,一目了然 + +**缺点**: +- 修改一个功能要跨多个目录 +- 大型项目中文件过多,难以定位 + +#### 方式二:按功能组织(Feature-based) + +``` +src/ +├── features/ +│ ├── auth/ +│ │ ├── components/ +│ │ │ ├── LoginForm.vue +│ │ │ └── RegisterForm.vue +│ │ ├── stores/ +│ │ │ └── authStore.js +│ │ ├── services/ +│ │ │ └── authApi.js +│ │ ├── hooks/ +│ │ │ └── useAuth.js +│ │ └── index.js # 统一导出 +│ ├── user/ +│ │ ├── components/ +│ │ ├── stores/ +│ │ └── services/ +│ └── product/ +│ ├── components/ +│ ├── stores/ +│ └── services/ +├── shared/ # 共享资源 +│ ├── components/ +│ ├── utils/ +│ └── styles/ +└── App.vue +``` + +**优点**: +- 高内聚,修改一个功能在一个目录完成 +- 便于团队协作,不同人负责不同 feature +- 易于删除或重构,不会散落各处 + +**缺点**: +- 初期设计需要考虑 feature 划分 +- 共享组件需要额外考虑 + +::: tip 💡 如何选择? +| 项目规模 | 推荐方式 | 原因 | +|----------|----------|------| +| 小型项目(< 10 个页面) | 按类型组织 | 简单直接,快速上手 | +| 中大型项目(> 20 个页面) | 按功能组织 | 便于维护,团队协作 | +| 微前端/大型应用 | 按功能 + 模块拆分 | 独立部署,团队自治 | + +**实际建议**:很多项目采用"混合模式"——整体按功能组织,内部按类型细分。 +::: + +--- + +## 3. 各目录的职责与最佳实践 + +### 3.1 `components/` 组件目录 + +组件是前端项目的核心,良好的组件设计能大幅提升开发效率。 + +#### 组件分类 + +``` +components/ +├── common/ # 通用组件(跨项目可复用) +│ ├── Button/ +│ ├── Input/ +│ ├── Modal/ +│ └── Loading/ +├── business/ # 业务组件(项目特定) +│ ├── UserCard/ +│ ├── ProductItem/ +│ └── OrderTable/ +└── layout/ # 布局组件 + ├── Header/ + ├── Sidebar/ + └── Footer/ +``` + +#### 单文件组件结构 + +每个组件建议包含以下文件: + +``` +Button/ +├── index.vue # 主组件(或 .tsx/.jsx) +├── Button.scss # 样式(可选 CSS Modules) +├── Button.test.js # 单元测试 +├── Button.stories.js # Storybook 文档(可选) +├── types.ts # 类型定义(TS 项目) +└── index.ts # 统一导出 +``` + +::: details 📝 组件代码示例 +```vue + + + + + + +``` +::: + +### 3.2 `views/` 或 `pages/` 页面目录 + +页面是用户看到的"成品",通常对应路由。 + +``` +views/ +├── Home/ # 首页 +│ ├── index.vue +│ ├── components/ # 页面私有组件 +│ │ ├── HeroSection.vue +│ │ └── FeatureList.vue +│ └── hooks/ # 页面私有 hooks +│ └── useHomeData.js +├── User/ +│ ├── Profile/ +│ ├── Settings/ +│ └── OrderHistory/ +└── Product/ + ├── List/ + └── Detail/ +``` + +**最佳实践**: +- 页面组件保持"薄",逻辑下沉到 hooks 或 services +- 页面私有组件放在页面目录下,避免污染全局 +- 复杂页面可以进一步拆分子目录 + +### 3.3 `services/` 或 `api/` 服务层 + +集中管理所有 API 调用,统一处理请求/响应拦截。 + +``` +services/ +├── request.js # 请求实例配置(axios/fetch 封装) +├── user.js # 用户相关 API +├── product.js # 商品相关 API +├── order.js # 订单相关 API +└── index.js # 统一导出 +``` + +::: details 📝 服务层代码示例 +```javascript +// services/request.js +import axios from 'axios' +import { useAuthStore } from '@/stores/auth' + +const request = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL, + timeout: 10000 +}) + +// 请求拦截器 +request.interceptors.request.use( + (config) => { + const authStore = useAuthStore() + if (authStore.token) { + config.headers.Authorization = `Bearer ${authStore.token}` + } + return config + } +) + +// 响应拦截器 +request.interceptors.response.use( + (response) => response.data, + (error) => { + if (error.response?.status === 401) { + // 统一处理登录过期 + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +export default request +``` + +```javascript +// services/user.js +import request from './request' + +export const userApi = { + login: (data) => request.post('/auth/login', data), + register: (data) => request.post('/auth/register', data), + getProfile: () => request.get('/user/profile'), + updateProfile: (data) => request.put('/user/profile', data) +} +``` +::: + +### 3.4 `stores/` 状态管理 + +``` +stores/ +├── index.js # store 入口 +├── auth.js # 认证状态 +├── user.js # 用户信息 +├── app.js # 应用级状态(主题、语言等) +└── cart.js # 购物车状态 +``` + +**建议**: +- 按功能拆分 store,避免单个文件过大 +- 区分全局状态和局部状态,不要什么都放全局 +- 使用组合式 API(Pinia/Vuex 4+)更灵活 + +### 3.5 `utils/` 工具函数 + +``` +utils/ +├── format.js # 格式化(日期、金额等) +├── storage.js # 本地存储封装 +├── validate.js # 表单验证 +├── dom.js # DOM 操作 +├── date.js # 日期处理 +└── index.js # 统一导出 +``` + +**原则**: +- 纯函数优先,便于测试 +- 单一职责,一个函数只做一件事 +- 添加 JSDoc 注释,说明参数和返回值 + +::: details 📝 工具函数示例 +```javascript +// utils/storage.js +const STORAGE_PREFIX = 'myapp_' + +export const storage = { + get(key) { + const value = localStorage.getItem(STORAGE_PREFIX + key) + try { + return JSON.parse(value) + } catch { + return value + } + }, + + set(key, value) { + localStorage.setItem( + STORAGE_PREFIX + key, + typeof value === 'string' ? value : JSON.stringify(value) + ) + }, + + remove(key) { + localStorage.removeItem(STORAGE_PREFIX + key) + } +} +``` +::: + +### 3.6 `hooks/` 或 `composables/` 组合式函数 + +``` +hooks/ +├── useAuth.js # 认证逻辑 +├── useLoading.js # 加载状态 +├── usePagination.js # 分页逻辑 +├── useForm.js # 表单处理 +└── useWebsocket.js # WebSocket +``` + +::: details 📝 Hook 示例 +```javascript +// hooks/useLoading.js +import { ref } from 'vue' + +export function useLoading() { + const loading = ref(false) + + const withLoading = async (fn) => { + loading.value = true + try { + return await fn() + } finally { + loading.value = false + } + } + + return { loading, withLoading } +} + +// 使用 +const { loading, withLoading } = useLoading() +const fetchData = () => withLoading(async () => { + const data = await api.getData() + list.value = data +}) +``` +::: + +--- + +## 4. 知名开源项目的架构参考 + +### 4.1 Vue 3 官方仓库 + +``` +vue/ +├── packages/ +│ ├── vue/ # 核心包 +│ ├── reactivity/ # 响应式系统 +│ ├── runtime-core/ # 运行时核心 +│ ├── runtime-dom/ # DOM 运行时 +│ ├── compiler-sfc/ # 单文件组件编译器 +│ └── shared/ # 共享工具 +├── scripts/ # 构建脚本 +└── tsconfig.json +``` + +**特点**: +- Monorepo 结构,多个包统一管理 +- 按功能拆分 package,职责清晰 +- 共享工具提取到 shared 包 + +### 4.2 React 官方仓库 + +``` +react/ +├── packages/ +│ ├── react/ # React 核心 +│ ├── react-dom/ # DOM 渲染器 +│ ├── react-reconciler/ # 协调器 +│ ├── scheduler/ # 调度器 +│ └── shared/ # 共享代码 +├── fixtures/ # 测试用例 +└── scripts/ +``` + +**特点**: +- 核心与渲染器分离(react vs react-dom) +- reconciler 独立,支持多平台 +- scheduler 单独抽离,可独立使用 + +### 4.3 Ant Design Vue + +``` +ant-design-vue/ +├── components/ # 组件目录 +│ ├── button/ +│ ├── modal/ +│ └── ... +├── docs/ # 文档 +├── site/ # 官网 +├── tests/ # 测试 +└── typings/ # 类型定义 +``` + +**特点**: +- 组件与文档分离 +- 每个组件独立目录,包含 demo、test、style +- 统一的类型定义 + +### 4.4 Next.js(全栈框架) + +``` +my-nextjs-app/ +├── app/ # App Router(新版) +│ ├── page.js # 页面 +│ ├── layout.js # 布局 +│ ├── loading.js # 加载状态 +│ └── api/ # API 路由 +├── components/ # 组件 +├── lib/ # 工具函数 +├── public/ # 静态资源 +└── styles/ # 全局样式 +``` + +**特点**: +- 约定式路由,文件即路由 +- 内置 loading、error、layout 等约定文件 +- API 路由与页面共存 + +--- + +## 5. 架构设计原则与检查清单 + +### 5.1 核心原则 + +| 原则 | 说明 | 实践建议 | +|------|------|----------| +| **单一职责** | 一个模块只做一件事 | 组件、函数保持简洁 | +| **高内聚低耦合** | 相关代码放在一起,减少依赖 | 按功能组织目录 | +| **可预测性** | 代码行为符合直觉 | 命名清晰,结构一致 | +| **可测试性** | 便于编写单元测试 | 纯函数、依赖注入 | +| **可扩展性** | 新功能容易添加 | 预留扩展点,避免硬编码 | + +### 5.2 检查清单 + +**目录结构**: +- [ ] 是否有清晰的目录划分? +- [ ] 新成员能否快速找到文件位置? +- [ ] 是否避免了过深的嵌套(建议不超过 4 层)? + +**组件设计**: +- [ ] 组件是否单一职责? +- [ ] Props 是否清晰、可预测? +- [ ] 是否提取了可复用的逻辑到 hooks? + +**代码组织**: +- [ ] 是否避免了循环依赖? +- [ ] 工具函数是否纯函数优先? +- [ ] 常量、配置是否集中管理? + +**团队协作**: +- [ ] 是否有编码规范文档? +- [ ] 是否有文件命名约定? +- [ ] 代码审查是否关注架构问题? + +--- + +## 6. 总结 + +::: tip 💡 核心思想 +好的前端项目架构不是一成不变的,而是随着项目发展不断演进的。关键是建立清晰的**组织原则**和**命名约定**,让团队成员达成共识。 + +**记住这几点**: +1. **先简单后复杂**:小项目不要过度设计 +2. **按功能组织**:中大型项目推荐 Feature-based +3. **统一约定**:命名、结构、代码风格保持一致 +4. **持续重构**:定期审视架构,及时调整 + +**最终目标**:让代码像整理好的衣柜一样,想找什么立刻能找到,新成员也能快速上手。 +::: + +--- + +## 参考资源 + +- [Vue 风格指南](https://vuejs.org/style-guide/) +- [React 项目结构建议](https://react.dev/learn/thinking-in-react) +- [Bulletproof React - 架构指南](https://github.com/alan2207/bulletproof-react) +- [Feature Sliced Design](https://feature-sliced.design/) diff --git a/docs/zh-cn/appendix/3-browser-and-frontend/html-css-layout.md b/docs/zh-cn/appendix/3-browser-and-frontend/html-css-layout.md index 5e1c627..3553dea 100644 --- a/docs/zh-cn/appendix/3-browser-and-frontend/html-css-layout.md +++ b/docs/zh-cn/appendix/3-browser-and-frontend/html-css-layout.md @@ -434,69 +434,365 @@ Flexbox 是现代 CSS 最常用的布局方式。它让元素自动排列对齐 | `flex-wrap` | 是否换行 | `nowrap`、`wrap` | | `gap` | 元素间距 | `10px`、`1rem` | -### 3.7 SCSS:CSS 的"升级版" +### 3.7 CSS 预处理器:SCSS/SASS 与 LESS ::: tip 🎯 真实场景 你写了一个项目,CSS 文件有 2000 行。后来要改主题色,你发现: - 主色调 `#3b82f6` 出现了 50 次 -- 改一个颜色要全局搜索替换 -- 还要担心漏改了某个地方 +- 改一个颜色要全局搜索替换,还要担心漏改 +- 选择器写成 `.nav .nav-list .nav-item .nav-link` 又长又难维护 -**SCSS 解决的问题**:变量、嵌套、混入、模块化 +**CSS 预处理器**就是来解决这些问题的。它让 CSS 也能"编程":有变量、有嵌套、能复用代码。 ::: -**SCSS 示例**: +#### 3.7.1 什么是 CSS 预处理器? -```scss -// 1. 变量:定义主题色 -$primary-color: #3b82f6; +**用人话解释**:预处理器是一种"更聪明的 CSS"。你用更强大的语法写样式,然后它帮你**编译**成普通 CSS,浏览器就能正常识别了。 -// 2. 嵌套:父子关系一目了然 -.card { - background: white; - - h2 { - color: $primary-color; - } - - &:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - } -} -``` +**为什么要用?** -**编译后变成普通 CSS**: +| 痛点 | 原生 CSS | 预处理器 | +|------|----------|----------| +| 颜色重复出现 | 到处复制粘贴 | 定义变量,一处修改全局生效 | +| 选择器层级太深 | 写成一长串 | 嵌套语法,层级一目了然 | +| 相同样式重复写 | 复制粘贴 | 混入(Mixin),像函数一样复用 | + +#### 3.7.2 三大预处理器对比 + +| 特性 | 原生 CSS | **SCSS/SASS** | **LESS** | +|------|----------|---------------|----------| +| **变量写法** | `--primary` | `$primary` | `@primary` | +| **嵌套语法** | ❌ 不支持 | ✅ 支持 | ✅ 支持 | +| **混入(复用代码)** | ❌ 不支持 | ✅ `@mixin` | ✅ `.mixin()` | +| **学习难度** | 简单 | 中等 | 中等 | +| **流行程度** | - | ⭐⭐⭐ 最流行 | ⭐⭐ 较流行 | + +**简单记忆**: +- **SCSS**:用 `$` 符号,Bootstrap 5 在用,生态最好 +- **LESS**:用 `@` 符号,和 CSS 的 `@media` 写法一致,容易上手 + +#### 3.7.3 核心功能对比示例 + +##### 1. 变量:一处修改,全局生效 + +**场景**:主题色 `#3b82f6` 在 20 个地方用到,要改成红色。 + + + ```css -.card { - background: white; -} -.card h2 { - color: #3b82f6; -} -.card:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +/* 要改 20 处,容易漏 */ +.button { background: #3b82f6; } +.link { color: #3b82f6; } +.border { border-color: #3b82f6; } +``` + + + + +```scss +$primary: #3b82f6; + +.button { background: $primary; } +.link { color: $primary; } +.border { border-color: $primary; } +/* 改 $primary 一处即可 */ +``` + + + + +```less +@primary: #3b82f6; + +.button { background: @primary; } +.link { color: @primary; } +.border { border-color: @primary; } +/* 改 @primary 一处即可 */ +``` + + + + +##### 2. 嵌套:层级关系一目了然 + +**场景**:导航栏里有多层结构。 + + + + +```css +/* 写成一长串,难看出层级关系 */ +.navbar .nav-list .nav-item .nav-link { } +.navbar .nav-list .nav-item .nav-link:hover { } +``` + + + + +```scss +.navbar { + .nav-list { + .nav-item { + .nav-link { + &:hover { } /* & 表示父选择器 */ + } + } + } } ``` -**SCSS vs Less vs 原生 CSS**: + + -| 特性 | 原生 CSS | SCSS | Less | -|------|----------|------|------| -| 变量 | ✅ `--var` | ✅ `$var` | ✅ `@var` | -| 嵌套 | ❌ | ✅ | ✅ | -| 混入 | ❌ | ✅ `@mixin` | ✅ `.mixin()` | -| 学习曲线 | 简单 | 中等 | 中等 | +```less +.navbar { + .nav-list { + .nav-item { + .nav-link { + &:hover { } + } + } + } +} +``` + + + + +##### 3. 混入(Mixin):复用代码片段 + +**场景**:多个按钮都需要"居中显示"的样式。 + + + + +```css +/* 复制粘贴 3 次 */ +.btn-primary { + display: flex; + justify-content: center; + align-items: center; +} +.btn-secondary { + display: flex; + justify-content: center; + align-items: center; +} +``` + + + + +```scss +@mixin center { + display: flex; + justify-content: center; + align-items: center; +} + +.btn-primary { @include center; } +.btn-secondary { @include center; } +``` + + + + +```less +.center() { + display: flex; + justify-content: center; + align-items: center; +} + +.btn-primary { .center(); } +.btn-secondary { .center(); } +``` + + + + +#### 3.7.4 如何选择? + +| 情况 | 推荐选择 | +|------|----------| +| 刚开始学,项目小 | **原生 CSS**(先打好基础) | +| 项目用 Bootstrap 5 | **SCSS**(Bootstrap 源码是 SCSS) | +| 团队熟悉 `@` 符号 | **LESS**(和 CSS 的 `@media` 写法一致) | +| 需要复杂逻辑(循环、条件) | **SCSS**(功能更强大) | + +#### 3.7.5 在项目中使用 + +**Vite 项目(最简单)**: + +```bash +# 安装 sass +npm install -D sass + +# 直接使用 .scss 或 .less 文件 +``` ::: tip 💡 新手建议 -1. **先学好原生 CSS**:预处理器只是"语法糖",本质还是 CSS -2. **项目大了再用 SCSS**:小项目直接写 CSS 更简单 -3. **现代 CSS 已经支持变量**:`--primary-color: #3b82f6;` 原生就能用 +1. **先学好原生 CSS**:预处理器只是"语法糖",不懂 CSS 基础会越用越乱 +2. **小项目不用强上**:CSS 不到 200 行,直接写 CSS 更简单 +3. **从 SCSS 开始**:语法和 CSS 几乎一样,只是多了 `$` 变量 +4. **不要嵌套太深**:超过 3 层会让代码难维护 ::: +#### 3.7.6 不同技术栈的文件组织对比 + +**同样的项目,用不同技术栈,文件结构有什么不同?** + + + + +``` +my-website/ +├── index.html # 页面结构 +├── about.html +├── css/ +│ ├── reset.css # 重置样式 +│ ├── layout.css # 布局样式 +│ ├── components.css # 组件样式 +│ └── style.css # 主样式(可能上千行) +├── js/ +│ └── main.js +└── images/ + └── logo.png +``` + +**特点**: +- CSS 集中在一个或几个文件 +- 改样式要来回切换 HTML 和 CSS 文件 +- 样式容易互相冲突 + + + + +``` +src/ +├── components/ # 组件文件夹 +│ ├── Button/ +│ │ ├── Button.vue # 模板 + 样式 + 逻辑 +│ │ └── Button.test.js +│ ├── Header/ +│ │ └── Header.vue +│ └── Footer/ +│ └── Footer.vue +├── views/ # 页面文件夹 +│ ├── Home.vue +│ └── About.vue +├── App.vue # 根组件 +└── main.js # 入口文件 +``` + +**Button.vue 内部结构**: +```vue + + + + + +``` + + + + +``` +src/ +├── assets/ +│ └── styles/ +│ ├── _variables.scss # 变量:颜色、间距等 +│ ├── _mixins.scss # 混入:复用代码块 +│ ├── _functions.scss # 函数:颜色计算等 +│ └── global.scss # 全局样式入口 +├── components/ +│ ├── Button/ +│ │ └── Button.vue # 组件内用 @import 引入变量 +│ └── Card/ +│ └── Card.vue +├── views/ +│ ├── Home.vue +│ └── About.vue +├── App.vue +└── main.js +``` + +**_variables.scss**: +```scss +$primary: #3b82f6; +$secondary: #64748b; +$spacing-sm: 8px; +$spacing-md: 16px; +``` + +**Button.vue**: +```vue + +``` + + + + +``` +src/ +├── components/ +│ ├── Button.vue # 不需要 style 块 +│ ├── Card.vue +│ └── Header.vue +├── views/ +│ ├── Home.vue +│ └── About.vue +├── App.vue +└── main.js + +# 配置文件(根目录) +tailwind.config.js # 主题配置 +tailwind.css # 基础样式入口 +``` + +**Button.vue**(没有 style 块): +```vue + +``` + +**特点**: +- 没有单独的样式文件 +- 类名就是样式(`bg-blue-500` = 蓝色背景) +- 配置集中在 `tailwind.config.js` + + + + +**核心区别总结**: + +| 技术栈 | 样式文件位置 | 主题管理 | 代码复用 | +|--------|-------------|----------|----------| +| 原生 HTML+CSS | 集中式 `css/` 文件夹 | 搜索替换 | 复制粘贴 | +| Vue + CSS | 分散在 `.vue` 组件内 | 搜索替换 | 复制粘贴 | +| Vue + SCSS | 组件内 + `styles/` 公共文件 | 变量统一管理 | 混入复用 | +| Vue + Tailwind | 无(类名里) | `tailwind.config.js` | 类名组合 | + ### 3.8 如何记住这么多 CSS 属性? ::: tip 🎯 新手困惑 diff --git a/docs/zh-cn/appendix/4-server-and-backend/async-task-queues.md b/docs/zh-cn/appendix/4-server-and-backend/async-task-queues.md index 241d581..84e9737 100644 --- a/docs/zh-cn/appendix/4-server-and-backend/async-task-queues.md +++ b/docs/zh-cn/appendix/4-server-and-backend/async-task-queues.md @@ -1,3 +1,151 @@ # 异步任务队列与生产消费模型 -> 待实现 +::: tip 前言 +**用户点了"导出报表"按钮,然后盯着转圈的加载动画等了 30 秒——这合理吗?** 当一个操作需要几秒甚至几分钟才能完成时,让用户干等着显然不是好体验。异步任务队列就是解决这个问题的核心架构模式——把耗时操作丢到后台去处理,让用户立刻得到响应。 +::: + +**这篇文章会带你学什么?** + +学完这章后,你将获得: + +- **同步异步对比**:理解为什么某些操作必须异步化,以及异步化带来的用户体验提升 +- **生产消费模型**:掌握 Producer-Consumer 模式的核心思想和工作流程 +- **Worker 池机制**:了解任务如何被分发到多个 Worker 并行处理 +- **可靠性保障**:掌握任务重试、幂等性、死信队列等保障机制 +- **技术选型能力**:了解主流异步任务框架的特点和适用场景 + +| 章节 | 内容 | 核心概念 | +|-----|------|---------| +| **第 1 章** | 为什么需要异步 | 同步阻塞 vs 异步非阻塞 | +| **第 2 章** | 生产消费模型 | Producer、Queue、Consumer | +| **第 3 章** | Worker 工作池 | 并发处理、任务分发 | +| **第 4 章** | 可靠性保障 | 重试策略、幂等性、死信队列 | +| **第 5 章** | 框架选型 | Celery、Sidekiq、Bull、RQ | + +--- + +## 0. 全景图:为什么不能让用户"干等着"? + +想象你去餐厅点餐。好的餐厅会在你点完餐后立刻给你一个取餐号,然后你可以去找座位、玩手机,等餐好了再来取。而不是让你站在柜台前,盯着厨师做完整道菜。 + +Web 应用中有很多类似的"做菜"操作: + +- **发送邮件/短信**:调用第三方 API,可能需要几秒 +- **生成报表/PDF**:大量数据计算,可能需要几十秒 +- **图片/视频处理**:压缩、转码、加水印,可能需要几分钟 +- **数据同步**:跨系统数据同步,耗时不确定 + +::: tip 异步任务的核心思想 +把耗时操作从"请求-响应"的主流程中剥离出来,放到后台队列中异步处理。用户提交请求后立刻得到"已收到,正在处理"的响应,处理完成后通过通知、轮询或 WebSocket 告知结果。 +::: + +--- + +## 1. 同步 vs 异步:一个订单的故事 + +当用户提交一个订单时,后端需要做很多事情:扣减库存、创建订单记录、发送确认邮件、更新推荐系统、记录审计日志…… + +在同步模式下,这些操作串行执行,用户必须等所有操作完成才能看到结果。在异步模式下,只需要完成核心操作(扣减库存、创建订单),其余操作丢到队列里后台处理。 + + + +| 对比维度 | 同步处理 | 异步处理 | +|---------|---------|---------| +| 用户等待时间 | 所有操作总耗时 | 仅核心操作耗时 | +| 系统吞吐量 | 低(线程被阻塞) | 高(快速释放线程) | +| 失败影响 | 非核心失败导致整体失败 | 非核心失败不影响主流程 | +| 实现复杂度 | 简单 | 需要额外的队列基础设施 | +| 数据一致性 | 强一致 | 最终一致 | + +::: tip 什么时候该用异步? +三个判断标准:**耗时长**(超过 1-2 秒)、**非核心**(失败不应影响主流程)、**可延迟**(不需要立刻得到结果)。满足其中任意两个,就应该考虑异步化。 +::: + +--- + +## 2. 生产消费模型:任务的"流水线" + +异步任务队列的核心是经典的 **生产者-消费者模式(Producer-Consumer Pattern)**。这个模式有三个角色: + +- **生产者(Producer)**:产生任务的一方,通常是 Web 服务器处理用户请求时 +- **队列(Queue)**:存储待处理任务的缓冲区,通常用 Redis、RabbitMQ 等实现 +- **消费者(Consumer/Worker)**:从队列中取出任务并执行的工作进程 + + + +::: tip 队列的三大价值 +1. **解耦**:生产者不需要知道谁来处理任务,消费者不需要知道任务从哪来 +2. **削峰填谷**:突发流量时任务先堆积在队列中,消费者按自己的节奏处理 +3. **可靠性**:任务持久化在队列中,即使消费者崩溃也不会丢失 +::: + +| 组件 | 职责 | 常见实现 | +|------|------|---------| +| 消息中间件 | 存储和转发任务消息 | Redis、RabbitMQ、Kafka | +| 序列化器 | 将任务参数序列化/反序列化 | JSON、MessagePack、Pickle | +| 调度器 | 管理定时任务和延迟任务 | Cron、APScheduler、node-cron | +| 结果存储 | 保存任务执行结果 | Redis、数据库、S3 | + +--- + +## 3. 可靠性保障:任务不能"丢了"也不能"重复" + +在分布式环境中,网络抖动、服务重启、资源不足等问题随时可能发生。异步任务系统必须具备完善的可靠性保障机制。 + +最核心的两个问题:**任务丢失**(消费者处理到一半崩溃了)和**重复执行**(任务被投递了两次)。 + + + +::: tip 可靠性三板斧 +1. **ACK 机制**:消费者处理完任务后才发送确认(ACK),未确认的任务会被重新投递 +2. **重试策略**:任务失败后按策略重试,指数退避 + 抖动是最佳实践 +3. **幂等性设计**:同一个任务执行多次和执行一次的效果相同,通过唯一 ID 去重实现 +::: + +| 机制 | 解决的问题 | 实现方式 | +|------|-----------|---------| +| ACK 确认 | 任务丢失 | 处理完成后手动确认,超时未确认则重新投递 | +| 死信队列(DLQ) | 反复失败的"毒消息" | 重试超过上限后转入死信队列,人工介入处理 | +| 幂等性 | 重复执行 | 用任务唯一 ID 做去重,数据库唯一约束 | +| 优先级队列 | 任务饥饿 | 高优先级任务优先处理,避免被低优先级任务阻塞 | +| 超时控制 | 任务卡死 | 设置最大执行时间,超时自动终止并重试 | + +--- + +## 4. 框架选型:选择适合你的工具 + +不同语言生态有不同的异步任务框架,它们在功能丰富度、性能、易用性上各有侧重。选择框架时,首先考虑你的技术栈,然后根据项目规模和需求做决定。 + + + +::: tip 选型建议 +- **Python 项目**:中大型用 Celery,小型用 RQ +- **Node.js 项目**:首选 BullMQ(Bull 的下一代) +- **Ruby 项目**:Sidekiq 几乎是唯一选择 +- **Java 项目**:Spring 生态用 Spring Batch,高吞吐用 Kafka Streams +- **Go 项目**:Asynq(基于 Redis)或 Machinery + +如果你的项目已经在用 Redis,那么基于 Redis 的方案(Celery+Redis、BullMQ、Sidekiq)是最简单的起步方式。 +::: + +--- + +## 总结 + +异步任务队列是后端架构中不可或缺的基础设施。它让系统能够优雅地处理耗时操作,提升用户体验的同时提高系统吞吐量。 + +回顾本章的关键要点: + +1. **异步化的判断标准**:耗时长、非核心、可延迟,满足两个就该异步化 +2. **生产消费模型**:Producer → Queue → Consumer,三者解耦协作 +3. **Worker 池**:多个 Worker 并行消费,提高处理能力 +4. **可靠性保障**:ACK 确认 + 重试策略 + 幂等性,三者缺一不可 +5. **框架选型**:根据技术栈和项目规模选择,Redis 是最常见的消息中间件 + +## 延伸阅读 + +- [Celery 官方文档](https://docs.celeryq.dev/) - Python 最流行的分布式任务队列 +- [BullMQ 文档](https://docs.bullmq.io/) - Node.js 高性能任务队列 +- [Sidekiq Wiki](https://github.com/sidekiq/sidekiq/wiki) - Ruby 生态的任务处理标杆 +- [RabbitMQ Tutorials](https://www.rabbitmq.com/tutorials) - 消息中间件入门教程 +- [异步任务最佳实践](https://brandur.org/job-drain) - 任务队列的设计模式与陷阱 diff --git a/docs/zh-cn/appendix/4-server-and-backend/backend-project-architecture.md b/docs/zh-cn/appendix/4-server-and-backend/backend-project-architecture.md new file mode 100644 index 0000000..5ca5971 --- /dev/null +++ b/docs/zh-cn/appendix/4-server-and-backend/backend-project-architecture.md @@ -0,0 +1,751 @@ +# 后端项目架构设计 + +::: tip 🎯 核心问题 +**API 越写越多,代码越来越乱,如何设计一个清晰、可维护的后端项目结构?** 这就像问:你是把所有工具都扔进一个抽屉,还是按功能分类整理?好的项目架构能让团队协作更高效,让系统扩展更轻松。 +::: + +--- + +## 1. 为什么要关注后端项目架构? + +### 1.1 从小脚本到大系统的演变 + +很多初学者刚开始写后端时,代码结构非常简单: + +```python +# app.py - 所有代码在一个文件 +from flask import Flask, request, jsonify +import sqlite3 + +app = Flask(__name__) + +@app.route('/users', methods=['GET']) +def get_users(): + conn = sqlite3.connect('db.sqlite') + users = conn.execute('SELECT * FROM users').fetchall() + return jsonify(users) + +@app.route('/users', methods=['POST']) +def create_user(): + data = request.json + conn = sqlite3.connect('db.sqlite') + conn.execute('INSERT INTO users (name, email) VALUES (?, ?)', + (data['name'], data['email'])) + conn.commit() + return jsonify({'message': 'User created'}) + +# 还有订单、商品、支付...所有接口都在这个文件 +``` + +几百行代码搞定一切,简单直接。但随着业务发展,问题开始出现: + +- **接口多了**:一个文件几千行,找代码像"考古" +- **逻辑复杂了**:业务规则散落在各处,修改容易遗漏 +- **数据库操作重复**:到处写 SQL,改表结构要改几十处 +- **测试困难**:代码耦合严重,单元测试难以编写 + +**问题的本质**:没有"章法",所有的逻辑都堆在一起,就像把所有的工具、零件、说明书都扔进一个抽屉。 + +### 1.2 好的架构像整理好的车间 + +想象一个整理好的工厂车间: + +| 区域 | 功能 | 特点 | +|------|------|------| +| **原料区** | 存放原材料 | 分类摆放,标签清晰 | +| **加工区** | 生产加工 | 流水线作业,工序明确 | +| **质检区** | 质量检查 | 统一标准,严格把关 | +| **成品区** | 存放成品 | 整齐有序,易于出库 | +| **工具室** | 存放工具 | 按需借用,用完归还 | + +**好的后端架构**就是把代码也这样组织:每一层只关心自己的职责,数据像流水一样在各层之间传递。 + +::: tip 💡 通俗比喻:餐厅后厨的组织 +把后端系统想象成一家餐厅的后厨: + +- **`controllers/`(出餐口)** = 服务员接单:接收订单、核对信息、上菜 +- **`services/`(厨师团队)** = 厨师做菜:按照菜谱加工、协调各工序 +- **`repositories/`(仓库管理)** = 仓管取料:从仓库取食材、记录库存 +- **`models/`(菜谱标准)** = 菜谱定义:宫保鸡丁需要什么料、什么口味 +- **`utils/`(工具柜)** = 厨具存放:刀、勺、秤等通用工具 + +**关键点**:每个角色职责明确,不会越界。服务员不会自己炒菜,厨师不会擅自改菜谱。 +::: + +--- + +## 2. 经典分层架构详解 + +### 2.1 四层架构(Controller-Service-Repository-Model) + +最经典的后端分层架构如下: + +``` +my-backend-project/ +├── src/ +│ ├── controllers/ # 控制器层(Controller) +│ │ ├── userController.js +│ │ ├── orderController.js +│ │ └── index.js +│ ├── services/ # 业务逻辑层(Service) +│ │ ├── userService.js +│ │ ├── orderService.js +│ │ └── index.js +│ ├── repositories/ # 数据访问层(Repository/DAO) +│ │ ├── userRepository.js +│ │ └── index.js +│ ├── models/ # 数据模型层(Model/Entity) +│ │ ├── user.js +│ │ ├── order.js +│ │ └── index.js +│ ├── middlewares/ # 中间件 +│ │ ├── auth.js +│ │ ├── errorHandler.js +│ │ └── validator.js +│ ├── utils/ # 工具函数 +│ │ ├── logger.js +│ │ ├── response.js +│ │ └── validator.js +│ ├── config/ # 配置文件 +│ │ ├── database.js +│ │ ├── redis.js +│ │ └── index.js +│ ├── routes/ # 路由定义 +│ │ ├── userRoutes.js +│ │ ├── index.js +│ │ └── api.js +│ ├── jobs/ 或 workers/ # 定时任务/后台任务 +│ │ └── emailWorker.js +│ ├── events/ 或 subscribers/ # 事件监听 +│ │ └── userEvents.js +│ └── app.js # 应用入口 +├── tests/ # 测试文件 +│ ├── unit/ +│ ├── integration/ +│ └── e2e/ +├── migrations/ # 数据库迁移 +├── seeds/ # 种子数据 +├── docs/ # 文档 +├── .env # 环境变量 +├── package.json +└── README.md +``` + +::: tip 📊 从图解中你能看到什么? +**分层逻辑**: + +``` +┌─────────────────────────────────────────┐ +│ Controller 层(控制器层) │ ← 接待员:接收请求,返回响应 +│ - 接收 HTTP 请求 │ +│ - 参数校验、权限检查 │ +│ - 调用 Service │ +│ - 格式化响应 │ +├─────────────────────────────────────────┤ +│ Service 层(业务逻辑层) │ ← 厨师:处理核心业务 +│ - 业务逻辑编排 │ +│ - 事务管理 │ +│ - 调用 Repository │ +│ - 跨模块协调 │ +├─────────────────────────────────────────┤ +│ Repository 层(数据访问层) │ ← 仓管员:管理数据存取 +│ - 数据库操作 │ +│ - ORM 封装 │ +│ - 查询构建 │ +├─────────────────────────────────────────┤ +│ Model 层(数据模型层) │ ← 菜谱标准:定义数据结构 +│ - 实体定义(Entity) │ +│ - 类型定义 │ +│ - 业务规则验证 │ +└─────────────────────────────────────────┘ +``` + +**依赖方向**: +``` +Controller → Service → Repository → Model + ↓ + Middleware / Utils +``` + +上层依赖下层,下层不依赖上层。Model 是核心,所有层都可能依赖它。 +::: + +### 2.2 各层职责详解 + +#### Controller 层:请求的"接待员" + +Controller 是系统的"门面",负责接收 HTTP 请求并返回响应。 + +**职责**: +- 接收和解析请求参数 +- 调用相应的 Service 处理业务 +- 格式化响应数据 +- 处理 HTTP 相关逻辑(状态码、Header 等) + +**不应该做的事**: +- 直接操作数据库 +- 编写复杂业务逻辑 +- 处理事务 + +::: details 📝 Controller 代码示例(Node.js/Express) +```javascript +// controllers/userController.js +const userService = require('../services/userService') +const { success, error } = require('../utils/response') + +class UserController { + // 获取用户列表 + async list(req, res) { + try { + const { page = 1, limit = 10 } = req.query + const users = await userService.getUsers({ page, limit }) + return success(res, users) + } catch (err) { + return error(res, err.message, 500) + } + } + + // 获取单个用户 + async getById(req, res) { + try { + const { id } = req.params + const user = await userService.getUserById(id) + if (!user) { + return error(res, 'User not found', 404) + } + return success(res, user) + } catch (err) { + return error(res, err.message, 500) + } + } + + // 创建用户 + async create(req, res) { + try { + const userData = req.body + const newUser = await userService.createUser(userData) + return success(res, newUser, 201) + } catch (err) { + return error(res, err.message, 400) + } + } +} + +module.exports = new UserController() +``` +::: + +#### Service 层:业务的"厨师" + +Service 是系统的"大脑",包含核心业务逻辑。 + +**职责**: +- 实现业务规则和流程 +- 协调多个 Repository 完成复杂操作 +- 管理事务 +- 数据转换和计算 + +**不应该做的事**: +- 直接处理 HTTP 请求/响应 +- 直接操作数据库(通过 Repository) + +::: details 📝 Service 代码示例 +```javascript +// services/userService.js +const userRepository = require('../repositories/userRepository') +const orderRepository = require('../repositories/orderRepository') +const emailService = require('./emailService') +const { hashPassword } = require('../utils/crypto') + +class UserService { + // 获取用户列表 + async getUsers({ page, limit }) { + const offset = (page - 1) * limit + const [users, total] = await Promise.all([ + userRepository.findAll({ limit, offset }), + userRepository.count() + ]) + + return { + data: users, + pagination: { + page, + limit, + total, + totalPages: Math.ceil(total / limit) + } + } + } + + // 获取用户详情(包含订单信息) + async getUserById(id) { + const user = await userRepository.findById(id) + if (!user) return null + + // 获取用户订单统计 + const orderStats = await orderRepository.getStatsByUserId(id) + + return { + ...user, + orderStats + } + } + + // 创建用户(包含事务和邮件通知) + async createUser(userData) { + // 检查邮箱是否已存在 + const existingUser = await userRepository.findByEmail(userData.email) + if (existingUser) { + throw new Error('Email already exists') + } + + // 密码加密 + const hashedPassword = await hashPassword(userData.password) + + // 创建用户 + const newUser = await userRepository.create({ + ...userData, + password: hashedPassword + }) + + // 发送欢迎邮件(异步,不阻塞) + emailService.sendWelcomeEmail(newUser.email).catch(console.error) + + return newUser + } +} + +module.exports = new UserService() +``` +::: + +#### Repository 层:数据的"仓管员" + +Repository 负责所有与数据存储相关的操作。 + +**职责**: +- 数据库的增删改查 +- ORM 映射 +- 查询优化 + +**不应该做的事**: +- 包含业务逻辑 +- 处理事务(由 Service 控制) + +::: details 📝 Repository 代码示例 +```javascript +// repositories/userRepository.js +const { User } = require('../models') + +class UserRepository { + // 查询所有用户 + async findAll({ limit, offset }) { + return await User.findAll({ + limit, + offset, + attributes: { exclude: ['password'] } // 不返回密码 + }) + } + + // 根据 ID 查询 + async findById(id) { + return await User.findByPk(id, { + attributes: { exclude: ['password'] } + }) + } + + // 根据邮箱查询 + async findByEmail(email) { + return await User.findOne({ where: { email } }) + } + + // 创建用户 + async create(data) { + return await User.create(data) + } + + // 更新用户 + async update(id, data) { + const user = await User.findByPk(id) + if (!user) return null + return await user.update(data) + } + + // 删除用户 + async delete(id) { + const user = await User.findByPk(id) + if (!user) return null + await user.destroy() + return true + } + + // 统计用户数量 + async count() { + return await User.count() + } +} + +module.exports = new UserRepository() +``` +::: + +#### Model 层:数据的"定义" + +Model 定义数据结构和业务规则。 + +::: details 📝 Model 代码示例(Sequelize) +```javascript +// models/user.js +const { DataTypes } = require('sequelize') +const { sequelize } = require('../config/database') + +const User = sequelize.define('User', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: { + type: DataTypes.STRING(100), + allowNull: false, + validate: { + len: [2, 100] + } + }, + email: { + type: DataTypes.STRING(255), + allowNull: false, + unique: true, + validate: { + isEmail: true + } + }, + password: { + type: DataTypes.STRING(255), + allowNull: false + }, + status: { + type: DataTypes.ENUM('active', 'inactive', 'banned'), + defaultValue: 'active' + } +}, { + tableName: 'users', + timestamps: true, // 自动添加 createdAt 和 updatedAt + indexes: [ + { fields: ['email'] }, + { fields: ['status'] } + ] +}) + +module.exports = User +``` +::: + +--- + +## 3. 其他重要目录 + +### 3.1 `middlewares/` 中间件 + +中间件是请求处理流程中的"过滤器"。 + +``` +middlewares/ +├── auth.js # 认证中间件 +├── errorHandler.js # 错误处理 +├── validator.js # 参数校验 +├── rateLimiter.js # 限流 +├── logger.js # 请求日志 +└── cors.js # 跨域处理 +``` + +::: details 📝 中间件示例 +```javascript +// middlewares/auth.js +const jwt = require('jsonwebtoken') + +const authMiddleware = (req, res, next) => { + const token = req.headers.authorization?.split(' ')[1] + + if (!token) { + return res.status(401).json({ message: 'No token provided' }) + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET) + req.user = decoded + next() + } catch (err) { + return res.status(401).json({ message: 'Invalid token' }) + } +} + +module.exports = authMiddleware +``` +::: + +### 3.2 `routes/` 路由 + +集中管理所有 API 路由。 + +```javascript +// routes/userRoutes.js +const express = require('express') +const router = express.Router() +const userController = require('../controllers/userController') +const authMiddleware = require('../middlewares/auth') + +// 公开路由 +router.get('/', userController.list) +router.get('/:id', userController.getById) + +// 需要认证的路由 +router.post('/', authMiddleware, userController.create) +router.put('/:id', authMiddleware, userController.update) +router.delete('/:id', authMiddleware, userController.delete) + +module.exports = router +``` + +```javascript +// routes/index.js +const express = require('express') +const router = express.Router() + +router.use('/users', require('./userRoutes')) +router.use('/orders', require('./orderRoutes')) +router.use('/products', require('./productRoutes')) + +module.exports = router +``` + +### 3.3 `config/` 配置 + +集中管理所有配置,支持多环境。 + +```javascript +// config/index.js +const env = process.env.NODE_ENV || 'development' + +const configs = { + development: { + port: 3000, + database: { + host: 'localhost', + port: 5432, + name: 'myapp_dev' + }, + redis: { + host: 'localhost', + port: 6379 + } + }, + production: { + port: process.env.PORT || 80, + database: { + host: process.env.DB_HOST, + port: process.env.DB_PORT, + name: process.env.DB_NAME + } + } +} + +module.exports = configs[env] +``` + +### 3.4 `utils/` 工具 + +``` +utils/ +├── logger.js # 日志工具 +├── response.js # 响应封装 +├── crypto.js # 加密解密 +├── date.js # 日期处理 +└── validator.js # 验证工具 +``` + +--- + +## 4. 按功能组织(Feature-based) + +对于中大型项目,可以采用按功能组织的方式: + +``` +src/ +├── features/ +│ ├── users/ +│ │ ├── users.controller.js +│ │ ├── users.service.js +│ │ ├── users.repository.js +│ │ ├── users.model.js +│ │ ├── users.routes.js +│ │ ├── users.validator.js +│ │ └── index.js # 统一导出 +│ ├── orders/ +│ │ ├── orders.controller.js +│ │ ├── orders.service.js +│ │ └── ... +│ └── products/ +│ ├── products.controller.js +│ └── ... +├── shared/ # 共享资源 +│ ├── middlewares/ +│ ├── utils/ +│ └── config/ +└── app.js +``` + +**优点**: +- 高内聚,一个功能的所有代码在一起 +- 便于团队协作,不同人负责不同 feature +- 易于删除或重构 + +--- + +## 5. 知名开源项目的架构参考 + +### 5.1 Express.js 官方示例 + +``` +express-example/ +├── bin/ # 启动脚本 +├── public/ # 静态资源 +├── routes/ # 路由 +├── views/ # 视图模板 +├── app.js # 应用配置 +└── package.json +``` + +**特点**:简单直接,适合小型项目。 + +### 5.2 NestJS(企业级 Node.js 框架) + +``` +nestjs-project/ +├── src/ +│ ├── modules/ # 功能模块 +│ │ ├── users/ +│ │ │ ├── users.controller.ts +│ │ │ ├── users.service.ts +│ │ │ ├── users.module.ts +│ │ │ └── dto/ +│ │ └── orders/ +│ ├── common/ # 共享模块 +│ ├── config/ # 配置 +│ └── main.ts # 入口 +``` + +**特点**: +- 强制模块化结构 +- 内置依赖注入 +- 适合大型项目 + +### 5.3 Django(Python) + +``` +django-project/ +├── project_name/ # 项目配置 +├── apps/ +│ ├── users/ # 用户应用 +│ │ ├── models.py +│ │ ├── views.py +│ │ ├── serializers.py +│ │ └── urls.py +│ └── orders/ # 订单应用 +├── templates/ +├── static/ +└── manage.py +``` + +**特点**: +- 约定优于配置 +- MTV(Model-Template-View)模式 +- 应用可复用 + +### 5.4 Spring Boot(Java) + +``` +spring-boot-project/ +├── src/main/java/ +│ └── com/example/ +│ ├── controller/ +│ ├── service/ +│ ├── repository/ +│ ├── entity/ +│ ├── dto/ +│ ├── config/ +│ └── Application.java +├── src/main/resources/ +│ ├── application.yml +│ └── mapper/ +└── src/test/ +``` + +**特点**: +- 严格的分层架构 +- 注解驱动开发 +- 强大的生态 + +--- + +## 6. 架构设计原则与检查清单 + +### 6.1 核心原则 + +| 原则 | 说明 | 实践建议 | +|------|------|----------| +| **单一职责** | 一个模块只做一件事 | Controller 只处理 HTTP,Service 只处理业务 | +| **依赖倒置** | 依赖抽象而非具体实现 | 使用接口/抽象类 | +| **开闭原则** | 对扩展开放,对修改关闭 | 新增功能不修改原有代码 | +| **DRY** | 不要重复自己 | 提取公共逻辑到 utils 或基类 | +| **KISS** | 保持简单 | 不要过度设计 | + +### 6.2 检查清单 + +**分层检查**: +- [ ] Controller 是否只处理 HTTP 相关逻辑? +- [ ] Service 是否包含核心业务逻辑? +- [ ] Repository 是否只负责数据访问? +- [ ] 层与层之间是否通过明确的接口交互? + +**代码质量**: +- [ ] 是否有统一的错误处理机制? +- [ ] 是否使用环境变量管理配置? +- [ ] 是否有日志记录? +- [ ] 是否编写了单元测试? + +**安全**: +- [ ] 敏感配置是否放入环境变量? +- [ ] 是否有输入验证? +- [ ] 是否有认证和授权? +- [ ] 密码是否加密存储? + +--- + +## 7. 总结 + +::: tip 💡 核心思想 +好的后端架构应该像一家组织良好的餐厅: + +- **分工明确**:每个角色知道自己的职责 +- **流程清晰**:数据像流水一样在各层之间传递 +- **易于扩展**:新增功能不会破坏现有结构 +- **便于测试**:各层可以独立测试 + +**记住这几点**: +1. **分层是手段,不是目的**:不要为了分层而分层 +2. **按功能组织**:中大型项目推荐 Feature-based +3. **统一约定**:命名、结构、错误处理保持一致 +4. **持续重构**:定期审视架构,及时调整 + +**最终目标**:让代码像整理好的车间一样,想找什么立刻能找到,新功能容易添加,旧代码容易维护。 +::: + +--- + +## 参考资源 + +- [NestJS 文档](https://docs.nestjs.com/) +- [Express 最佳实践](https://expressjs.com/en/advanced/best-practice-security.html) +- [Bulletproof Node.js](https://github.com/santiq/bulletproof-nodejs) +- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) diff --git a/docs/zh-cn/appendix/4-server-and-backend/file-storage.md b/docs/zh-cn/appendix/4-server-and-backend/file-storage.md index d2181a8..9507669 100644 --- a/docs/zh-cn/appendix/4-server-and-backend/file-storage.md +++ b/docs/zh-cn/appendix/4-server-and-backend/file-storage.md @@ -1,3 +1,162 @@ # 文件存储与对象存储 -> 待实现 +::: tip 前言 +**用户上传了一张头像,你把它存在服务器的 `/uploads` 目录下——然后服务器磁盘满了,或者你加了第二台服务器,用户发现头像时有时无。** 文件存储看似简单,但在分布式环境下却是一个需要认真对待的架构问题。对象存储就是互联网时代解决这个问题的标准答案。 +::: + +**这篇文章会带你学什么?** + +学完这章后,你将获得: + +- **存储类型认知**:理解块存储、文件存储、对象存储的区别和适用场景 +- **对象存储核心概念**:掌握 Bucket、Object、Key、Pre-signed URL 等核心概念 +- **上传方案设计**:学会客户端直传 vs 服务端中转的方案选型 +- **CDN 加速原理**:理解 CDN 如何加速静态资源的全球分发 +- **最佳实践**:掌握文件命名、权限控制、生命周期管理等实战技巧 + +| 章节 | 内容 | 核心概念 | +|-----|------|---------| +| **第 1 章** | 存储类型对比 | 块存储、文件存储、对象存储 | +| **第 2 章** | 对象存储核心概念 | Bucket、Object、Key、元数据 | +| **第 3 章** | 文件上传方案 | 客户端直传、Pre-signed URL | +| **第 4 章** | CDN 加速 | 边缘节点、缓存策略、回源 | +| **第 5 章** | 最佳实践 | 命名规范、权限、生命周期 | + +--- + +## 0. 全景图:为什么不能把文件存在服务器本地? + +刚开始做项目时,把用户上传的文件存在服务器本地目录是最直觉的做法。但随着项目发展,你会遇到一系列问题: + +- **磁盘空间有限**:服务器磁盘总会满,扩容麻烦 +- **多服务器不共享**:负载均衡后,用户请求可能打到不同服务器,文件找不到 +- **没有备份**:服务器挂了,文件就丢了 +- **没有 CDN**:全球用户访问同一台服务器,速度慢 + +::: tip 对象存储的核心价值 +对象存储(如 AWS S3、阿里云 OSS)解决了所有这些问题:**容量无限、全球可访问、自动备份、天然支持 CDN**。它已经成为互联网应用存储文件的事实标准。 +::: + +--- + +## 1. 存储类型对比:块、文件、对象 + +计算机世界有三种主要的存储方式,它们解决不同层次的问题。 + + + +| 维度 | 块存储 | 文件存储 | 对象存储 | +|------|--------|---------|---------| +| 数据单位 | 固定大小的块 | 文件 + 目录 | 对象(Key-Value) | +| 访问协议 | iSCSI/FC | NFS/SMB | HTTP REST API | +| 性能 | 最高(毫秒级) | 中等 | 较低(但够用) | +| 扩展性 | 有限 | 中等 | 近乎无限 | +| 成本 | 最高 | 中等 | 最低 | +| 典型场景 | 数据库 | 共享文件 | 图片/视频/备份 | + +::: tip 简单记忆 +- **块存储**像硬盘——给数据库用 +- **文件存储**像网络共享文件夹——给多台服务器共享配置用 +- **对象存储**像网盘——给用户上传的图片、视频用 +::: + +--- + +## 2. 对象存储核心概念 + +对象存储的数据模型非常简单:**Bucket(桶)** 是容器,**Object(对象)** 是文件,每个对象通过唯一的 **Key(键)** 来标识。 + +``` +my-app-bucket/ ← Bucket(桶) +├── avatars/user-123.jpg ← Object Key +├── avatars/user-456.png ← Object Key +├── reports/2024/q1-report.pdf ← Object Key("目录"只是 Key 的前缀) +└── uploads/temp/file.zip ← Object Key +``` + +| 概念 | 说明 | 示例 | +|------|------|------| +| Bucket | 存储容器,全局唯一命名 | `my-app-prod`、`company-assets` | +| Object | 存储的文件本体 + 元数据 | 一张图片、一个 PDF | +| Key | 对象的唯一标识符 | `avatars/user-123.jpg` | +| 元数据 | 对象的附加信息 | Content-Type、自定义标签 | +| ACL | 访问控制列表 | public-read、private | +| Pre-signed URL | 临时授权访问链接 | 有效期 15 分钟的上传/下载链接 | + +::: tip 对象存储没有真正的"目录" +`avatars/user-123.jpg` 中的 `avatars/` 不是目录,只是 Key 的前缀。对象存储是扁平结构,所有对象在同一层级。控制台显示的"文件夹"只是按前缀分组的视觉效果。 +::: + +--- + +## 3. 文件上传方案:谁来传文件? + +文件上传有两种主流方案:服务端中转和客户端直传。对于大多数场景,**客户端直传**是更优的选择。 + + + +::: tip 客户端直传的优势 +1. **节省服务器带宽**:文件不经过你的服务器,直接到 OSS +2. **避免超时**:大文件上传不会触发 Nginx/网关的超时限制 +3. **降低服务器负载**:服务器只需要签发凭证,不需要处理文件流 +4. **支持断点续传**:OSS 原生支持分片上传,前端可以实现断点续传 + +实现步骤:前端请求后端获取 Pre-signed URL → 前端用这个 URL 直接上传到 OSS → OSS 回调通知后端 +::: + +--- + +## 4. CDN 加速:让全球用户都快 + +当你的用户遍布全球时,从单一源站下载文件会很慢。CDN(Content Delivery Network)通过在全球部署边缘节点,将文件缓存到离用户最近的节点,大幅降低访问延迟。 + + + +| CDN 概念 | 说明 | +|---------|------| +| 边缘节点 | 分布在全球各地的缓存服务器 | +| 回源 | 边缘节点没有缓存时,向源站请求文件 | +| 缓存命中率 | 请求被边缘节点直接响应的比例,越高越好 | +| TTL | 缓存有效期,过期后需要重新回源 | +| 缓存刷新 | 主动清除边缘节点的缓存,让新文件生效 | + +::: tip CDN 最佳实践 +- **文件名加 hash**:`logo.a3f2b1.png` 而不是 `logo.png`,这样更新文件时不需要刷新缓存 +- **设置合理的 TTL**:静态资源(JS/CSS/图片)设长 TTL(1年),HTML 设短 TTL(5分钟) +- **开启 Gzip/Brotli 压缩**:文本类资源压缩后体积减少 60-80% +::: + +--- + +## 5. 最佳实践 + +| 实践 | 说明 | 示例 | +|------|------|------| +| Key 命名规范 | 用有意义的前缀组织文件 | `{type}/{date}/{uuid}.{ext}` | +| 避免热点 Key | 不要用递增数字开头 | 用 UUID 或 hash 前缀 | +| 权限最小化 | Bucket 默认 private | 只对需要公开的文件设置 public-read | +| 生命周期规则 | 自动清理过期文件 | 临时文件 7 天后自动删除 | +| 跨域配置 | 前端直传需要配置 CORS | 允许你的域名 PUT/POST | +| 服务端加密 | 敏感文件开启 SSE | SSE-S3 或 SSE-KMS | + +--- + +## 总结 + +文件存储是每个 Web 应用都会遇到的基础问题。对象存储以其无限容量、低成本、高可用的特性,成为了互联网应用的标准选择。 + +回顾本章的关键要点: + +1. **三种存储类型**:块存储给数据库、文件存储给共享、对象存储给用户文件 +2. **对象存储模型**:Bucket + Key + Object,扁平结构,HTTP API 访问 +3. **客户端直传**:Pre-signed URL 方案,文件不经过服务器,高效省资源 +4. **CDN 加速**:边缘节点缓存 + 文件名 hash,让全球用户都快 +5. **安全与管理**:权限最小化、生命周期规则、服务端加密 + +## 延伸阅读 + +- [AWS S3 开发者指南](https://docs.aws.amazon.com/s3/) - 对象存储的标杆文档 +- [阿里云 OSS 最佳实践](https://help.aliyun.com/document_detail/31853.html) - 国内最常用的对象存储 +- [MinIO 文档](https://min.io/docs/minio/linux/index.html) - 开源的 S3 兼容对象存储 +- [Cloudflare R2](https://developers.cloudflare.com/r2/) - 零出口费用的对象存储 +- [Pre-signed URL 详解](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html) - 客户端直传的核心机制 diff --git a/docs/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure.md b/docs/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure.md index 25b3ea5..e11cbc4 100644 --- a/docs/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure.md +++ b/docs/zh-cn/appendix/4-server-and-backend/rate-limiting-backpressure.md @@ -1,3 +1,131 @@ # 限流与背压控制 -> 待实现 +::: tip 前言 +**双十一零点,几亿用户同时涌入——服务器扛得住吗?** 任何系统都有处理能力的上限。当请求量超过系统承载能力时,如果不加控制,结果就是所有人都用不了。限流和背压就是保护系统不被"压垮"的两道防线。 +::: + +**这篇文章会带你学什么?** + +学完这章后,你将获得: + +- **限流必要性**:理解为什么需要主动拒绝部分请求来保护系统 +- **限流算法**:掌握令牌桶、漏桶、滑动窗口三种核心算法的原理和差异 +- **背压机制**:理解当上游速度超过下游时的处理策略 +- **多层限流**:了解从客户端到网关到服务的多层限流架构 +- **实战能力**:知道在什么场景下选择什么限流策略 + +| 章节 | 内容 | 核心概念 | +|-----|------|---------| +| **第 1 章** | 为什么需要限流 | 雪崩效应、服务保护 | +| **第 2 章** | 限流算法 | 令牌桶、漏桶、滑动窗口 | +| **第 3 章** | 背压控制 | 缓冲区、丢弃策略、弹性扩容 | +| **第 4 章** | 多层限流架构 | 客户端、网关、服务端 | +| **第 5 章** | 实战与选型 | Nginx、Redis、Sentinel | + +--- + +## 0. 全景图:为什么要"拒绝"用户? + +这听起来很反直觉——我们不是应该服务好每一个用户吗?但现实是:**不拒绝一部分请求,所有请求都会失败**。 + +想象一个只能坐 100 人的餐厅,突然涌进来 1000 人。如果不限流,结果不是 1000 人都能吃上饭,而是厨房崩溃、服务员瘫痪,1000 人谁都吃不上。正确的做法是在门口排队限流,让 100 人先进去,其余人等候。 + +::: tip 限流的核心目标 +- **保护系统**:防止过载导致服务完全不可用 +- **公平分配**:确保已接受的请求能正常处理 +- **优雅降级**:被限流的请求收到明确的 429 状态码,而不是超时或 500 错误 +::: + +--- + +## 1. 限流算法:三种经典方案 + +限流的核心问题是:**在单位时间内,最多允许多少个请求通过?** 不同的算法在精确度、突发流量处理、实现复杂度上各有取舍。 + + + +| 算法 | 原理 | 突发流量 | 精确度 | 实现复杂度 | +|------|------|---------|--------|-----------| +| 令牌桶 | 固定速率放令牌,请求消耗令牌 | 允许(桶中有存量) | 高 | 中 | +| 漏桶 | 请求排队,固定速率处理 | 不允许(完全平滑) | 高 | 中 | +| 滑动窗口 | 统计窗口内请求数 | 部分允许 | 较高 | 低 | +| 固定窗口 | 按时间窗口计数 | 边界处可能突发 | 低 | 最低 | + +::: tip 选哪个算法? +- **API 限流**:令牌桶最常用,允许合理的突发流量 +- **流量整形**:漏桶适合需要恒定输出速率的场景 +- **简单计数**:滑动窗口实现简单,适合大多数 Web 应用 +::: + +--- + +## 2. 背压控制:当上游比下游快 + +限流解决的是"外部请求太多"的问题,而**背压(Backpressure)**解决的是"内部组件速度不匹配"的问题。 + +当生产者产生数据的速度持续超过消费者处理数据的速度时,中间的缓冲区会不断膨胀,最终导致内存溢出或数据丢失。背压机制就是让消费者能够"反向通知"生产者减速。 + + + +::: tip 背压的四种策略 +1. **丢弃(Drop)**:缓冲区满时丢弃新数据或旧数据,适合实时性要求高但允许丢失的场景 +2. **阻塞(Block)**:让生产者暂停,等消费者处理完再继续,适合数据不能丢失的场景 +3. **采样(Sample)**:只处理部分数据,适合高频数据流 +4. **弹性扩容(Scale)**:动态增加消费者数量,适合云原生环境 +::: + +--- + +## 3. 多层限流架构 + +生产环境中,限流不是在某一个点做就够了,而是需要**多层防护**,每一层解决不同粒度的问题。 + +| 层级 | 位置 | 限流粒度 | 工具 | +|------|------|---------|------| +| 客户端 | 前端/App | 按钮防抖、请求节流 | lodash.throttle、debounce | +| CDN/WAF | 边缘节点 | IP 级别、地域级别 | Cloudflare Rate Limiting | +| API 网关 | 入口网关 | 路由级别、用户级别 | Nginx limit_req、Kong | +| 服务端 | 应用内部 | 接口级别、资源级别 | Sentinel、Resilience4j | +| 数据库 | 存储层 | 连接数、QPS | 连接池配置、慢查询熔断 | + +::: tip 限流的 HTTP 规范 +被限流的请求应该返回 `429 Too Many Requests` 状态码,并在响应头中包含: +- `Retry-After`: 建议客户端多久后重试(秒数或日期) +- `X-RateLimit-Limit`: 限流上限 +- `X-RateLimit-Remaining`: 剩余配额 +- `X-RateLimit-Reset`: 配额重置时间 +::: + +--- + +## 4. 实战选型 + +| 场景 | 推荐方案 | 说明 | +|------|---------|------| +| Nginx 入口限流 | `limit_req_zone` | 基于漏桶算法,配置简单 | +| 分布式限流 | Redis + Lua 脚本 | 令牌桶或滑动窗口,多实例共享计数 | +| Java 微服务 | Sentinel / Resilience4j | 支持熔断、降级、热点限流 | +| Node.js API | express-rate-limit | 简单易用,支持 Redis 存储 | +| Go 服务 | golang.org/x/time/rate | 标准库令牌桶实现 | + +--- + +## 总结 + +限流和背压是保护系统稳定性的两道关键防线。限流控制外部流量的涌入速度,背压协调内部组件的处理速度。 + +回顾本章的关键要点: + +1. **限流的必要性**:不拒绝部分请求,所有请求都会失败 +2. **三种核心算法**:令牌桶(允许突发)、漏桶(完全平滑)、滑动窗口(简单精确) +3. **背压机制**:丢弃、阻塞、采样、扩容四种策略 +4. **多层防护**:从客户端到数据库,每层解决不同粒度的问题 +5. **429 规范**:被限流时返回标准状态码和限流头信息 + +## 延伸阅读 + +- [Stripe 的限流实践](https://stripe.com/blog/rate-limiters) - 支付系统的限流设计 +- [Nginx limit_req 文档](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html) - Nginx 限流模块 +- [Alibaba Sentinel](https://sentinelguard.io/) - 面向分布式服务的流量控制组件 +- [Resilience4j](https://resilience4j.readme.io/) - Java 轻量级容错库 +- [Token Bucket 算法详解](https://en.wikipedia.org/wiki/Token_bucket) - 令牌桶算法的数学原理 diff --git a/docs/zh-cn/appendix/4-server-and-backend/request-journey.md b/docs/zh-cn/appendix/4-server-and-backend/request-journey.md index 5e4d9f2..54c4397 100644 --- a/docs/zh-cn/appendix/4-server-and-backend/request-journey.md +++ b/docs/zh-cn/appendix/4-server-and-backend/request-journey.md @@ -1,3 +1,295 @@ # 一个请求的完整旅程 -> 待实现 +::: tip 前言 +**当你在浏览器里输入一个网址按下回车,到页面显示出来,中间到底发生了什么?** 这个问题是面试经典题,更是理解整个 Web 架构的钥匙。搞懂这条链路,你就能理解前端、后端、网络、数据库是怎么协作的。 +::: + +**这篇文章会带你学什么?** + +学完这章后,你将获得: + +- **全链路视角**:理解一个 HTTP 请求从发出到返回的完整过程 +- **各层职责认知**:DNS、TCP、负载均衡、Web 服务器、应用服务器、数据库各自做什么 +- **问题定位能力**:请求慢或失败时,知道从哪一层开始排查 +- **性能优化思路**:每一层都有优化空间,知道优化点在哪里 + +| 章节 | 内容 | 核心概念 | +|-----|------|---------| +| **第 1 章** | 浏览器发起请求 | DNS 解析、TCP 连接、HTTP 请求 | +| **第 2 章** | 网络传输 | 路由、CDN、负载均衡 | +| **第 3 章** | 服务器处理 | Web 服务器、应用逻辑、数据库查询 | +| **第 4 章** | 响应返回 | 序列化、压缩、渲染 | +| **第 5 章** | 全链路优化 | 缓存、连接复用、异步处理 | + +--- + +## 0. 全景图:一个请求经历了什么? + +用一个比喻来理解:你在网上下单买书,这个过程和 HTTP 请求惊人地相似。 + +| 请求阶段 | 买书类比 | 技术对应 | +|---------|---------|---------| +| 输入网址 | 你说"我要去某某书店" | 浏览器解析 URL | +| DNS 解析 | 查地图找到书店地址 | 域名 → IP 地址 | +| TCP 连接 | 走到书店门口,推门进去 | 三次握手建立连接 | +| 发送请求 | 告诉店员"我要《xxx》这本书" | HTTP 请求报文 | +| 服务器处理 | 店员去仓库找书、查库存、算价格 | 应用逻辑 + 数据库查询 | +| 返回响应 | 店员把书递给你 | HTTP 响应报文 | +| 浏览器渲染 | 你打开书开始阅读 | HTML/CSS/JS 解析渲染 | + + + +--- + +## 1. 浏览器发起请求 + +### 1.1 URL 解析 + +当你输入 `https://api.example.com/books?id=123` 时,浏览器会把它拆解成几个部分: + +| 部分 | 值 | 含义 | +|-----|-----|------| +| 协议 | `https` | 用加密方式通信 | +| 域名 | `api.example.com` | 服务器的"名字" | +| 路径 | `/books` | 要访问的资源 | +| 查询参数 | `id=123` | 附加条件 | + +### 1.2 DNS 解析:域名 → IP 地址 + +计算机不认识域名,只认识 IP 地址(如 `93.184.216.34`)。DNS 就是互联网的"电话簿"。 + +``` +浏览器缓存 → 系统缓存 → 路由器缓存 → ISP DNS → 根域名服务器 + ↓ 命中就直接用,不命中就往下查 +``` + +::: tip DNS 缓存的意义 +如果每次请求都从根域名服务器查起,全球互联网会被 DNS 查询压垮。所以每一层都有缓存,大部分请求在浏览器或系统层就能解析完成。 +::: + +### 1.3 TCP 三次握手 + +找到 IP 地址后,浏览器需要和服务器"建立连接"。TCP 用三次握手确保双方都准备好了: + +``` +客户端 → 服务器:你好,我想连接(SYN) +服务器 → 客户端:好的,我准备好了(SYN + ACK) +客户端 → 服务器:收到,开始通信(ACK) +``` + +如果是 HTTPS,还需要额外的 TLS 握手来协商加密方式。 + +### 1.4 发送 HTTP 请求 + +连接建立后,浏览器发送 HTTP 请求报文: + +```http +GET /books?id=123 HTTP/1.1 +Host: api.example.com +Accept: application/json +Authorization: Bearer eyJhbGci... +User-Agent: Chrome/120.0 +``` + +| 组成部分 | 内容 | +|---------|------| +| 请求行 | 方法(GET)+ 路径 + 协议版本 | +| 请求头 | 元信息:身份认证、期望的数据格式等 | +| 请求体 | POST/PUT 请求才有,携带要提交的数据 | + +--- + +## 2. 网络传输:请求在路上 + +### 2.1 路由转发 + +请求离开你的电脑后,会经过多个路由器的转发,就像快递经过多个中转站: + +``` +你的电脑 → 家庭路由器 → 运营商网络 → 骨干网 → 目标机房 +``` + +每个路由器根据 IP 地址决定"下一跳"往哪里转发。可以用 `traceroute` 命令查看请求经过了哪些节点。 + +### 2.2 CDN 加速 + +如果目标网站使用了 CDN(内容分发网络),请求可能不需要到达源服务器: + +| 场景 | 走向 | +|-----|------| +| 请求静态资源(图片、CSS、JS) | CDN 边缘节点直接返回 | +| 请求动态数据(API) | 穿透 CDN,到达源服务器 | + +CDN 的本质是"把内容提前放到离用户最近的地方"。 + +### 2.3 负载均衡 + +大型网站不会只有一台服务器。负载均衡器负责把请求分配到多台服务器上: + +``` +用户请求 → 负载均衡器 → 服务器 A(30% 流量) + → 服务器 B(30% 流量) + → 服务器 C(40% 流量) +``` + +常见的分配策略: + +| 策略 | 原理 | 适用场景 | +|-----|------|---------| +| 轮询 | 依次分配 | 服务器配置相同 | +| 加权轮询 | 按权重分配 | 服务器配置不同 | +| IP 哈希 | 同一用户固定到同一台 | 需要会话保持 | +| 最少连接 | 分给当前连接最少的 | 请求处理时间差异大 | + +--- + +## 3. 服务器处理:厨房里发生了什么 + +请求到达服务器后,会经过多层处理。 + +### 3.1 Web 服务器(Nginx / Apache) + +第一个接收请求的通常是 Web 服务器,它负责: + +| 职责 | 说明 | +|-----|------| +| 静态文件服务 | 直接返回 HTML、CSS、JS、图片 | +| 反向代理 | 把 API 请求转发给后端应用 | +| SSL 终止 | 处理 HTTPS 加密解密 | +| 请求过滤 | 拦截恶意请求、限流 | + +### 3.2 应用服务器处理 + +Web 服务器把请求转发给应用服务器(Node.js、Spring、Django 等),处理流程: + +``` +请求进入 → 中间件链 → 路由匹配 → 控制器 → 服务层 → 数据访问层 +``` + +**中间件**做的事情: + +1. 解析请求体(JSON、表单数据) +2. 验证身份(检查 Token) +3. 检查权限(这个用户能访问这个接口吗?) +4. 记录日志(谁在什么时候访问了什么) + +### 3.3 数据库查询 + +大部分请求最终都要和数据库打交道: + +``` +应用代码:SELECT * FROM books WHERE id = 123 + ↓ +数据库引擎:解析 SQL → 查询优化 → 执行计划 → 读取数据 + ↓ +返回结果:{ id: 123, title: "xxx", price: 59.9 } +``` + +::: tip 数据库是最常见的性能瓶颈 +网络传输通常是毫秒级,应用逻辑也很快,但一个没有索引的数据库查询可能要几秒甚至几十秒。所以"慢请求"大概率是数据库查询慢。 +::: + +--- + +## 4. 响应返回:数据的归途 + +### 4.1 构造 HTTP 响应 + +服务器处理完后,构造响应报文: + +```http +HTTP/1.1 200 OK +Content-Type: application/json +Content-Encoding: gzip +Cache-Control: max-age=3600 + +{"id": 123, "title": "xxx", "price": 59.9} +``` + +| 组成部分 | 内容 | +|---------|------| +| 状态行 | 协议版本 + 状态码(200 成功、404 未找到、500 服务器错误) | +| 响应头 | 数据格式、缓存策略、压缩方式等 | +| 响应体 | 实际的数据内容(JSON、HTML 等) | + +### 4.2 数据压缩 + +服务器通常会用 gzip 或 brotli 压缩响应体,减少传输量: + +| 压缩算法 | 压缩率 | 速度 | +|---------|--------|------| +| gzip | 约 70% | 快 | +| brotli | 约 80% | 较慢但压缩更好 | + +一个 100KB 的 JSON,压缩后可能只有 20-30KB。 + +### 4.3 浏览器渲染 + +浏览器收到响应后: + +1. **解析 HTML** → 构建 DOM 树 +2. **解析 CSS** → 构建样式树 +3. **合并** → 生成渲染树 +4. **布局** → 计算每个元素的位置和大小 +5. **绘制** → 把像素画到屏幕上 + + + +--- + +## 5. 全链路优化:每一层都能更快 + +### 5.1 各层优化手段 + +| 层级 | 优化手段 | 效果 | +|-----|---------|------| +| DNS | DNS 预解析、使用快速 DNS 服务 | 减少 DNS 查询时间 | +| 网络 | CDN、HTTP/2、连接复用 | 减少传输延迟 | +| 服务器 | 缓存(Redis)、异步处理 | 减少处理时间 | +| 数据库 | 索引、查询优化、读写分离 | 减少查询时间 | +| 前端 | 懒加载、代码分割、资源压缩 | 减少渲染时间 | + +### 5.2 缓存:最有效的优化 + +缓存存在于请求链路的每一层: + +``` +浏览器缓存 → CDN 缓存 → 反向代理缓存 → 应用缓存(Redis)→ 数据库缓存 +``` + +::: tip 缓存的本质 +用空间换时间。把计算过的结果存起来,下次直接用,不用重新算。缓存命中率每提高 10%,系统性能可能提升数倍。 +::: + +### 5.3 请求失败时的排查思路 + +| 现象 | 可能的问题层 | 排查方法 | +|-----|------------|---------| +| 完全无响应 | DNS / 网络 | ping、nslookup | +| 连接超时 | 网络 / 服务器宕机 | telnet、curl | +| 返回 4xx | 客户端请求有误 | 检查 URL、参数、Token | +| 返回 5xx | 服务器内部错误 | 查看服务器日志 | +| 响应很慢 | 数据库 / 应用逻辑 | 查看慢查询日志、APM 工具 | + +--- + +## 6. 总结 + +一个 HTTP 请求的完整旅程: + +1. **浏览器**:解析 URL → DNS 查询 → TCP 连接 → 发送请求 +2. **网络**:路由转发 → CDN 判断 → 负载均衡分发 +3. **服务器**:Web 服务器接收 → 中间件处理 → 业务逻辑 → 数据库查询 +4. **返回**:构造响应 → 压缩 → 网络传输 → 浏览器渲染 + +::: tip 理解全链路的价值 +当你能在脑中画出请求的完整链路时,遇到任何问题都能快速定位到是哪一层出了问题。这是从"初级开发"到"能独立排查问题"的关键跨越。 +::: + +--- + +## 延伸阅读 + +- [HTTP 权威指南](https://developer.mozilla.org/zh-CN/docs/Web/HTTP) — MDN 的 HTTP 文档 +- [High Performance Browser Networking](https://hpbn.co/) — 浏览器网络性能优化 +- [What happens when...](https://github.com/alex/what-happens-when) — 经典的"输入 URL 后发生了什么"详解 diff --git a/docs/zh-cn/appendix/4-server-and-backend/search-engines.md b/docs/zh-cn/appendix/4-server-and-backend/search-engines.md index 8d440b9..b289856 100644 --- a/docs/zh-cn/appendix/4-server-and-backend/search-engines.md +++ b/docs/zh-cn/appendix/4-server-and-backend/search-engines.md @@ -1,3 +1,153 @@ # 搜索引擎原理 -> 待实现 +::: tip 前言 +**你在淘宝搜"红色连衣裙",0.1 秒内从几十亿商品中找到了最相关的结果——这背后是怎么做到的?** 搜索引擎是互联网最核心的基础设施之一,从 Google 到电商站内搜索,它的核心原理都是一样的:倒排索引 + 相关性排序。 +::: + +**这篇文章会带你学什么?** + +学完这章后,你将获得: + +- **倒排索引**:理解搜索引擎最核心的数据结构 +- **分词技术**:了解中文分词的挑战和常见方案 +- **相关性排序**:掌握 TF-IDF 和 BM25 的基本原理 +- **Elasticsearch**:了解最流行的搜索引擎的架构和使用场景 +- **搜索优化**:掌握同义词、纠错、高亮等实用搜索功能 + +| 章节 | 内容 | 核心概念 | +|-----|------|---------| +| **第 1 章** | 倒排索引 | 正排索引 vs 倒排索引 | +| **第 2 章** | 分词与分析 | 中文分词、停用词、词干提取 | +| **第 3 章** | 相关性排序 | TF-IDF、BM25 | +| **第 4 章** | Elasticsearch | 分布式架构、分片、副本 | +| **第 5 章** | 搜索优化 | 同义词、纠错、自动补全 | + +--- + +## 0. 全景图:搜索的本质是什么? + +搜索的本质是一个**信息检索(Information Retrieval)**问题:给定一个查询,从海量文档中找到最相关的结果,并按相关性排序返回。 + +这个过程分为两个阶段: + +- **索引阶段(离线)**:提前把所有文档处理好,建立高效的查找结构 +- **查询阶段(在线)**:用户输入关键词时,快速找到匹配的文档并排序 + +::: tip 为什么不能用数据库 LIKE 查询? +`SELECT * FROM products WHERE name LIKE '%红色连衣裙%'` 看起来能搜索,但它需要**全表扫描**——逐行检查每条记录。当数据量达到百万级时,这种查询会慢到不可用。倒排索引把这个 O(n) 的操作变成了 O(1) 的查找。 +::: + +--- + +## 1. 倒排索引:搜索引擎的"心脏" + +传统数据库用的是**正排索引**:从文档 ID 找到文档内容。而搜索引擎用的是**倒排索引**:从关键词找到包含它的文档列表。 + + + +| 索引类型 | 方向 | 查找方式 | 适用场景 | +|---------|------|---------|---------| +| 正排索引 | 文档 → 内容 | 知道 ID,查内容 | 数据库主键查询 | +| 倒排索引 | 关键词 → 文档列表 | 知道关键词,查文档 | 全文搜索 | + +::: tip 倒排索引的构建过程 +1. **文档收集**:获取所有需要被搜索的文档 +2. **分词(Tokenization)**:将文档拆分为一个个词语 +3. **建立映射**:记录每个词语出现在哪些文档中(以及出现位置、频率等) +4. **持久化存储**:将索引写入磁盘,支持快速查找 +::: + +--- + +## 2. 分词与文本分析 + +分词是搜索引擎的第一步,也是中文搜索的最大挑战。英文天然以空格分词,但中文没有分隔符——"乒乓球拍卖了"可以分成"乒乓球/拍卖/了"或"乒乓/球拍/卖/了"。 + +| 分词方式 | 说明 | 示例 | +|---------|------|------| +| 标准分词 | 按空格和标点切分(英文) | "hello world" → ["hello", "world"] | +| 中文分词 | 基于词典或模型切分 | "搜索引擎" → ["搜索", "引擎"] | +| N-gram | 按固定长度滑动窗口切分 | "搜索" → ["搜索", "索引"] | +| 自定义词典 | 添加业务专有词汇 | "iPhone16ProMax" 作为一个词 | + +::: tip 文本分析管道 +分词只是文本分析的一步,完整的管道包括: +1. **字符过滤**:去除 HTML 标签、特殊字符 +2. **分词**:将文本拆分为词语(Token) +3. **停用词过滤**:去除"的"、"了"、"是"等无意义的高频词 +4. **同义词扩展**:将"手机"扩展为"手机、电话、移动电话" +5. **词干提取**:将 "running" 还原为 "run"(英文) +::: + +--- + +## 3. 相关性排序:哪个结果最"相关"? + +找到匹配的文档只是第一步,更重要的是**排序**——把最相关的结果排在最前面。 + +| 算法 | 原理 | 特点 | +|------|------|------| +| TF-IDF | 词频(TF) × 逆文档频率(IDF) | 经典算法,简单有效 | +| BM25 | TF-IDF 的改进版,加入文档长度归一化 | Elasticsearch 默认算法 | +| 向量检索 | 将文档和查询转为向量,计算余弦相似度 | 支持语义搜索 | + +::: tip TF-IDF 直觉理解 +- **TF(词频)**:一个词在文档中出现越多次,这个文档越可能与该词相关 +- **IDF(逆文档频率)**:一个词在越少的文档中出现,它的区分度越高 +- "的"在所有文档中都出现(IDF 低),所以搜索"的"没有意义 +- "Elasticsearch"只在少数文档中出现(IDF 高),搜索它能精确定位 +::: + +--- + +## 4. Elasticsearch:最流行的搜索引擎 + +Elasticsearch 是目前最流行的开源搜索引擎,基于 Apache Lucene 构建,提供分布式、RESTful API 的全文搜索能力。 + +| 概念 | 说明 | +|------|------| +| Index | 类似数据库的"表",存储同类文档 | +| Document | 一条记录,JSON 格式 | +| Shard | 分片,将索引拆分到多个节点 | +| Replica | 副本,提供高可用和读扩展 | +| Mapping | 字段类型定义,类似数据库 Schema | +| Analyzer | 文本分析器,定义分词规则 | + +::: tip ES vs 数据库 +Elasticsearch 不是用来替代数据库的,而是作为搜索层与数据库配合使用。典型架构:数据写入数据库 → 同步到 ES → 搜索请求走 ES → 详情请求走数据库。 +::: + +--- + +## 5. 搜索优化:让搜索更"聪明" + +| 优化手段 | 说明 | 效果 | +|---------|------|------| +| 同义词 | "手机"也能搜到"电话" | 提高召回率 | +| 拼写纠错 | "iphoen" 自动纠正为 "iphone" | 容错性 | +| 自动补全 | 输入"苹"提示"苹果手机" | 提升体验 | +| 高亮 | 搜索结果中标红匹配词 | 直观展示 | +| 权重调整 | 标题匹配权重 > 内容匹配 | 提高精确度 | +| 过滤与聚合 | 按价格区间、品牌筛选 | 缩小范围 | + +--- + +## 总结 + +搜索引擎是互联网应用的核心基础设施。理解倒排索引、分词、相关性排序这三个核心概念,就掌握了搜索引擎的本质。 + +回顾本章的关键要点: + +1. **倒排索引**:从关键词到文档的反向映射,是搜索引擎的核心数据结构 +2. **分词是基础**:中文分词是搜索质量的关键,需要选择合适的分词器 +3. **BM25 排序**:基于词频和文档频率的相关性评分,是 ES 的默认算法 +4. **ES 架构**:分片 + 副本实现分布式和高可用 +5. **搜索优化**:同义词、纠错、补全让搜索更智能 + +## 延伸阅读 + +- [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html) - 最权威的 ES 参考 +- [Elasticsearch 权威指南](https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html) - 中文入门指南 +- [Apache Lucene](https://lucene.apache.org/) - ES 底层的搜索引擎库 +- [MeiliSearch](https://www.meilisearch.com/) - 轻量级搜索引擎,适合中小项目 +- [Typesense](https://typesense.org/) - 开源的即时搜索引擎 diff --git a/docs/zh-cn/appendix/index.md b/docs/zh-cn/appendix/index.md index fdf4e6d..4c7b8be 100644 --- a/docs/zh-cn/appendix/index.md +++ b/docs/zh-cn/appendix/index.md @@ -49,9 +49,14 @@ description="从汇编到高级语言,理解编程语言的演进与分类" /> +